移动设备开发中,因为移动设备(手机等)的内存有限,所以使用有效的缓存技术是必要的.android提供来一个缓存工具类LruCache,开发中我们会经经常使用到,以下来他是怎样实现的.

在package android.util包里面有对LruCache定义的java文件.为了能准确的理解LruCache,我们先来看看原文的说明:

 * A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
* become eligible for garbage collection.

简单翻译:LruCache缓存数据是採用持有数据的强引用来保存一定数量的数据的.每次用到(获取)一个数据时,这个数据就会被移动(一个保存数据的)队列的头部,当往这个缓存里面增加一个新的数据时,假设这个缓存已经满了,就会自己主动删除这个缓存队列里面最后一个数据,这样一来使得这个删除的数据没有强引用而可以被gc回收.

从上面的翻译,能够知道LruCache的工作原理.以下来一步一步说明他的详细实现:

(1)怎样实现保存的数据是有一定顺序的,而且使用过一个存在的数据,这个数据就会被移动到数据队列的头部.这里採用的是LinkedHashMap.

我们知道LinkedHashMap是保存一个键值对数据的,而且能够维护这些数据对应的顺序的.一般能够保证存储的数据依照存入的顺序或者使用的顺序的.以下来看看LruCache的构造方法:

    public LruCache(int maxSize) {//指定缓存数据的数量
if (maxSize <= 0) {//必须大于0
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//创建一个LinkedHashMap,而且依照訪问数据的顺序排序
}

(2)如上面的构造方法能够知道,在创建一个LruCache就须要指定其缓存数据的数量.这里要详解一下这个缓存数据的"数量"究竟是指什么:是指缓存数据对象的个数呢,还是缓存数据所占用的内存总量呢?

答案是:都是. 能够是缓存数据的个数,也能够使缓存数据所占用内存总量,当然也能够是其它.究竟是什么,须要看你的LruCache怎样重写这种方法:sizeOf(K key, V value)

    protected int sizeOf(K key, V value) {//子类覆盖这种方法来计算出自己的缓存对于每个保存的数据所占用的量
return 1;//默认返回1,这说明:默认情况下缓存的数量就是指缓存数据的总个数(每个数据都是1).
}

那假设我使用LruCache来保存bitmap的图片,而且希望缓存的容量是4M那这么做?

在原文的说明中,android给来这样一个实例:

 * <p>By default, the cache size is measured in the number of entries. Override
* {@link #sizeOf} to size the cache in different units. For example, this cache
* is limited to 4MiB of bitmaps:
* <pre> {@code
* int cacheSize = 4 * 1024 * 1024; // 4MiB
* LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {//保存bitmap的LruCache,容量是4M
* protected int sizeOf(String key, Bitmap value) {
* return value.getByteCount();//计算每个缓存的图片所占用内存大小
* }
* }}</pre>

(3)那么LruCache怎样,何时推断是否缓存已经满来,而且须要移除不经常使用的数据呢?

事实上在LruCache里面有一个方法:trimToSize()就是用来检測一次当前是否已经满,假设满来就自己主动移除一个数据,一直到不满为止:

    public void trimToSize(int maxSize) {//默认情况下传入是上面说的最大容量的值 this.maxSize
while (true) {//死循环.保证一直到不满为止
K key;
V value;
synchronized (this) {//线程安全保证
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
} if (size <= maxSize) {//假设不满,就跳出循环
break;
} Map.Entry<K, V> toEvict = map.eldest();//取出最后的数据(最不经常使用的数据)
if (toEvict == null) {
break;
} key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);//移除这个数据
size -= safeSizeOf(key, value);//容量降低
evictionCount++;//更新自己主动移除数据的数量(次数)
} entryRemoved(true, key, value, null);//用来通知这个数据已经被移除,假设你须要知道一个数据何时被移除你须要从写这种方法entryRemoved
}
}

上面的源代码中我给出了说明,非常好理解.这里要注意的是trimToSize这种方法是public的,说明事实上我们自己能够调用这种方法的.这一点非常重要.记住他,你会用到的.

以下的问题是:trimToSize这种方法何时调用呢?

trimToSize这种方法在LruCache里面多个方法里面会被调用来检測是否已经满了,比方在往LruCache里面增加一个新的数据的方法put里面,还有在通过get(K key)这种方法获取一个数据的时候等,都会调用trimToSize来检測一次.下了来看看put里面怎样调用:

    public final V put(K key, V value) {//增加一个新的数据
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
} V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
} if (previous != null) {//增加反复位置的数据,则移除老的数据
entryRemoved(false, key, previous, value);
} trimToSize(maxSize);//检測缓存的数据是否已经满
return previous;
}

(4)细心的人在看了上面的源代码能够发现,原来对 LruCache的操作都加了synchronized来保证线程安全,是的,LruCache就是线程安全的,其它的方法也都使用来synchronized

(5)事实上你应该立即有一个疑问:假设LruCache中已经删除了一个数据,但是如今又调用LruCache的get方法获取这个数据怎么办?来看看源代码是否有解决问题:

    public final V get(K key) {//获取一个数据
if (key == null) {
throw new NullPointerException("key == null");
} V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {//取得这个数据
hitCount++;//成取得数据的次数
return mapValue;//成功取得这个数据
}
missCount++;//取得数据失败次数
} /*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/ V createdValue = create(key);//尝试创建这个数据
if (createdValue == null) {
return null;//创建数据失败
} synchronized (this) {//增加这个又一次创建的数据
createCount++;//从新创建数据次数
mapValue = map.put(key, createdValue); if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
} if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);//检測是否满
return createdValue;
}
}

从上面的分析能够知道,我们能够从写create方法来又一次创建已经不存在的数据.这种方法默认情况是什么也不做的,所以须要你自己做

    protected V create(K key) {
return null;
}

android之LruCache源代码解析的更多相关文章

  1. Android属性动画源代码解析(超详细)

    本文假定你已经对属性动画有了一定的了解,至少使用过属性动画.下面我们就从属性动画最简单的使用开始. ObjectAnimator .ofInt(target,propName,values[]) .s ...

  2. Android:Volley源代码解析

    简单实例 Volley是一个封装HttpUrlConnection和HttpClient的网络通信框架,集AsyncHttpClient和Universal-Image-Loader的长处于了一身.既 ...

  3. Android源代码解析之(七)--&gt;LruCache缓存类

    转载请标明出处:一片枫叶的专栏 android开发过程中常常会用到缓存.如今主流的app中图片等资源的缓存策略通常是分两级.一个是内存级别的缓存,一个是磁盘级别的缓存. 作为android系统的维护者 ...

  4. Android源代码解析之(十三)--&gt;apk安装流程

    转载请标明出处:一片枫叶的专栏 上一篇文章中给大家分析了一下android系统启动之后调用PackageManagerService服务并解析系统特定文件夹.解析apk文件并安装的过程,这个安装过程实 ...

  5. Android EventBus源代码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  6. 源代码解析Android中View的layout布局过程

    Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算.布局.画图的整体机制可參见博文 < Android中Vie ...

  7. Android xUtils3源代码解析之网络模块

    本文已授权微信公众号<非著名程序猿>原创首发,转载请务必注明出处. xUtils3源代码解析系列 一. Android xUtils3源代码解析之网络模块 二. Android xUtil ...

  8. Android View体系(八)从源代码解析View的layout和draw流程

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

  9. Android SVG动画PathView源代码解析与使用教程(API 14)

    使用的是一个第三方库android-pathview主要是一个自己定义View--PathView.跟全部自己定义View一样,重写了三个构造方法. 而且终于调用三个參数的构造方法,在里面获取自己定义 ...

随机推荐

  1. 百度编辑器ueditor简单易用

    最近刚刚被分配任务给一个新闻发布,它采用了富文本编辑器.本人也被用于,只是这一次我选择了百度的富文本编辑器ueditor1_4_3-utf8-jsp版. 事实上看ueditor功能非常强大,只是百度的 ...

  2. structure and interpretation of Computer programs -- Foreword

    Foreword  前言 Educators, generals, dieticians, psychologists, and parents program.   Armies, students ...

  3. 旧版本mysql下载大全,爽~~

    http://mirror.cogentco.com/pub/mysql/MySQL-4.1/ http://mirror.cogentco.com/pub/mysql/MySQL-5.0/ http ...

  4. WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abor ...

  5. RAID级别与规范

    1.RAID 0 RAID 0是最早出现的RAID模式,即Data Stripping数据分条技术.RAID 0是组建磁盘阵列中最简单的一种形式,只需要2块以上的硬盘即可,成本低,可以提高整个磁盘的性 ...

  6. 【AC大牛陈鸿的ACM总结贴】【ID AekdyCoin】人家当初也一样是菜鸟

    acm总结帖_By AekdyCoin 各路大牛都在中国大陆的5个赛区结束以后纷纷发出了退役帖,总结帖,或功德圆满,或死不瞑目,而这也许又会造就明年的各种"炸尸"风波.为了考虑在发 ...

  7. QQ登录-第三方SDK的接入总结

    由于项目的需要,使用了[QQ登录]SDK 的相关功能!   1.集成[QQ登录]SDK   [QQ登录]SDK下载地址: http://wiki.open.qq.com/wiki/website/SD ...

  8. C语言指针和数组知识总结(下)

    一.数组指针: 数组指针就是一个指针,只不过它指向的是一个数组.可以通过如下方式来定义 typedef int Array[5]; //数组类型 Array* m;      //数组定义 还有一种更 ...

  9. 3DShader之立方体环境映射(cubic environment mapping)

    前面讲了球形环境映射,然而目前采用更多的是立方体环境映射.国际惯例:上图先: 1.反射: 2.折射 3.fresnel(反射+折射) 4.色散 好了,大概讲下原理, 立方体纹理我就不多讲了,它以一个3 ...

  10. Swift - 访问通讯录联系人(使用系统提供的通讯录交互界面)

    1,通讯录访问介绍 通讯录(或叫地址簿,电话簿)是一个数据库,里面储存了联系人的相关信息.要实现访问通讯录有如下两种方式: (1)AddressBook.framework框架 : 没有界面,通过代码 ...