Android中使用的LruCache
easaa
7年前
<p>LRU是Least Recently Used的缩写,即“最近最少使用”,说明LRU缓存算法的淘汰策略是把最近最少使用的数据移除,让出内存给最新读取的数据。下面看一下Android中的LruCache。</p> <h2>android.util.LruCache</h2> <p>这个LruCache在android.util包下,是API level 12引入的,对于API level 12之前的系统可以使用support library中的LruCache。先来看看android.util.LruCache的源码。</p> <p>首先是成员变量:</p> <pre> <code class="language-java">private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount;</code></pre> <p>LruCache内部使用一个LinkedHashMap作为存储容器,并对各种操作进行计次。</p> <p>构造器:</p> <pre> <code class="language-java">public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); }</code></pre> <p>构造器的参数maxSize用于指定缓存的最大容量,并初始化一个LinkedHashMap,顺便看看这个LinkedHashMap的构造函数:</p> <pre> <code class="language-java">public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); init(); this.accessOrder = accessOrder; }</code></pre> <p>initialCapacity即初始容量设为0,装填因子loadFactor设为0.75,accessOrder设为true,即链表中的元素按照最近最少访问到最多访问排序。这里设置的装填因子为0.75,设置其它值行不行呢?在LinkedHashMap这个构造器中只是将loadFactor作为参数传给了父类构造器,该父类构造器如下:</p> <pre> <code class="language-java">public HashMap(int capacity, float loadFactor) { this(capacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Load factor: " + loadFactor); } /* * Note that this implementation ignores loadFactor; it always uses * a load factor of 3/4. This simplifies the code and generally * improves performance. */ }</code></pre> <p>调用了HashMap的构造器,可以看到只是对loadFactor进行了合法检查,除此之外没有其他调用或赋值操作,Note中解释了,这个loadFactor没用,装填因子永远使用3/4,也就是0.75。所以在构造LinkedHashMap时,设了装填因子也没什么用。</p> <p>继续看LruCache,resize方法更新链表容量,调用trimToSize方法。</p> <pre> <code class="language-java">public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); }</code></pre> <p>先看get方法,key为空会抛异常,取出对应的value,若不为空则命中次数hitCount加1并return这个value,否则missCount加1。该value为空时继续向下执行,根据key尝试创建value,如果创建返回的createdValue是null,那就确实没有该值,若创建操作返回的createdValue不为null,则尝试把createdValue放回map,若存在旧值则返回旧值,否则返回这个createdValue。</p> <pre> <code class="language-java">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++; } 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; } }</code></pre> <p>put方法将键值对放入map,重新计算大小之后调用trimToSize方法,删除访问次数最少的元素。</p> <pre> <code class="language-java">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; }</code></pre> <p>trimToSize方法中会一直尝试删除队首元素即访问次数最少的元素,直到size不超过最大容量或将要删除的对象为空。</p> <pre> <code class="language-java">public void trimToSize(int 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); } }</code></pre> <h2>android.support.v4.util.LruCache</h2> <p>support v4包中的LruCache可以用于API level 12之前的系统,和android.util包的LruCache的区别是在trimToSize中获取将要删除元素的方法不一样:</p> <ul> <li>android.util.LruCache</li> </ul> <pre> <code class="language-java">Map.Entry<K, V> toEvict = map.eldest();</code></pre> <ul> <li>android.support.v4.util.LruCache</li> </ul> <pre> <code class="language-java">Map.Entry<K, V> toEvict = map.entrySet().iterator().next();</code></pre> <p>LinkedHashMap的eldest()方法已经被标注为@hide,所以使用android.support.v4.util.LruCache更加保险一点。</p> <p> </p> <p>来自:http://www.jianshu.com/p/f32e5d435d6c</p> <p> </p>