内存缓存LruCache实现原理
自己项目中一直都是用的开源的xUtils框架,包括BitmapUtils、DbUtils、ViewUtils和HttpUtils四大模块,这四大模块都是项目中比较常用的。最近决定研究一下xUtils的源码,用了这么久总得知道它的实现原理吧。我是先从先从BitmapUtils模块开始的。BitmapUtils和大多数图片加载框架一样,都是基于内存-文件-网络三级缓存。也就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取,如果文件缓存没有取到,就从网络下载图片并且加入内存和文件缓存。
这篇帖子先分析内存缓存是如何实现的。好吧开始进入正题。
BitmapUtils内存缓存的核心类LruMemoryCache,LruMemoryCache代码和v4包的LruCache一样,只是加了一个存储超期的处理,这里分析LruCache源码。LRU即Least Recently Used,近期最少使用算法。也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。
讲到LruCache不得不提一下LinkedHashMap,因为LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。关于LinkedHashMap详解请前往http://www.cnblogs.com/children/archive/2012/10/02/2710624.html。
下面看下LruCache的源码,我都注释的很详细了。
1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.support.v4.util; 18 19 import java.util.LinkedHashMap; 20 import java.util.Map; 21 22 /** 23 * Static library version of {@link android.util.LruCache}. Used to write apps 24 * that run on API levels prior to 12. When running on API level 12 or above, 25 * this implementation is still used; it does not try to switch to the 26 * framework's implementation. See the framework SDK documentation for a class 27 * overview. 28 */ 29 public class LruCache<K, V> { 30 private final LinkedHashMap<K, V> map; 31 32 /** Size of this cache in units. Not necessarily the number of elements. */ 33 private int size; //当前cache的大小 34 private int maxSize; //cache最大大小 35 36 private int putCount; //put的次数 37 private int createCount; //create的次数 38 private int evictionCount; //回收的次数 39 private int hitCount; //命中的次数 40 private int missCount; //未命中次数 41 42 /** 43 * @param maxSize for caches that do not override {@link #sizeOf}, this is 44 * the maximum number of entries in the cache. For all other caches, 45 * this is the maximum sum of the sizes of the entries in this cache. 46 */ 47 public LruCache(int maxSize) { 48 if (maxSize <= 0) { 49 throw new IllegalArgumentException("maxSize <= 0"); 50 } 51 this.maxSize = maxSize; 52 //将LinkedHashMap的accessOrder设置为true来实现LRU 53 this.map = new LinkedHashMap<K, V>(0, 0.75f, true); 54 } 55 56 /** 57 * Returns the value for {@code key} if it exists in the cache or can be 58 * created by {@code #create}. If a value was returned, it is moved to the 59 * head of the queue. This returns null if a value is not cached and cannot 60 * be created. 61 * 通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部, 62 * 如果item的value没有被cache或者不能被创建,则返回null。 63 */ 64 public final V get(K key) { 65 if (key == null) { 66 throw new NullPointerException("key == null"); 67 } 68 69 V mapValue; 70 synchronized (this) { 71 mapValue = map.get(key); 72 if (mapValue != null) { 73 //mapValue不为空表示命中,hitCount+1并返回mapValue对象 74 hitCount++; 75 return mapValue; 76 } 77 missCount++; //未命中 78 } 79 80 /* 81 * Attempt to create a value. This may take a long time, and the map 82 * may be different when create() returns. If a conflicting value was 83 * added to the map while create() was working, we leave that value in 84 * the map and release the created value. 85 * 如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法 86 * 如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去 87 * 文件缓存中去取或者从网络下载,所以并不需要创建。 88 */ 89 V createdValue = create(key); 90 if (createdValue == null) { 91 return null; 92 } 93 //假如创建了新的对象,则继续往下执行 94 synchronized (this) { 95 createCount++; 96 //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue 97 mapValue = map.put(key, createdValue); 98 if (mapValue != null) { 99 // There was a conflict so undo that last put 100 //如果mapValue不为空,则撤销上一步的put操作。 101 map.put(key, mapValue); 102 } else { 103 //加入新创建的对象之后需要重新计算size大小 104 size += safeSizeOf(key, createdValue); 105 } 106 } 107 108 if (mapValue != null) { 109 entryRemoved(false, key, createdValue, mapValue); 110 return mapValue; 111 } else { 112 //每次新加入对象都需要调用trimToSize方法看是否需要回收 113 trimToSize(maxSize); 114 return createdValue; 115 } 116 } 117 118 /** 119 * Caches {@code value} for {@code key}. The value is moved to the head of 120 * the queue. 121 * 122 * @return the previous value mapped by {@code key}. 123 */ 124 public final V put(K key, V value) { 125 if (key == null || value == null) { 126 throw new NullPointerException("key == null || value == null"); 127 } 128 129 V previous; 130 synchronized (this) { 131 putCount++; 132 size += safeSizeOf(key, value); //size加上预put对象的大小 133 previous = map.put(key, value); 134 if (previous != null) { 135 //如果之前存在键为key的对象,则size应该减去原来对象的大小 136 size -= safeSizeOf(key, previous); 137 } 138 } 139 140 if (previous != null) { 141 entryRemoved(false, key, previous, value); 142 } 143 //每次新加入对象都需要调用trimToSize方法看是否需要回收 144 trimToSize(maxSize); 145 return previous; 146 } 147 148 /** 149 * @param maxSize the maximum size of the cache before returning. May be -1 150 * to evict even 0-sized elements. 151 * 此方法根据maxSize来调整内存cache的大小,如果maxSize传入-1,则清空缓存中的所有对象 152 */ 153 private void trimToSize(int maxSize) { 154 while (true) { 155 K key; 156 V value; 157 synchronized (this) { 158 if (size < 0 || (map.isEmpty() && size != 0)) { 159 throw new IllegalStateException(getClass().getName() 160 + ".sizeOf() is reporting inconsistent results!"); 161 } 162 //如果当前size小于maxSize或者map没有任何对象,则结束循环 163 if (size <= maxSize || map.isEmpty()) { 164 break; 165 } 166 //移除链表头部的元素,并进入下一次循环 167 Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); 168 key = toEvict.getKey(); 169 value = toEvict.getValue(); 170 map.remove(key); 171 size -= safeSizeOf(key, value); 172 evictionCount++; //回收次数+1 173 } 174 175 entryRemoved(true, key, value, null); 176 } 177 } 178 179 /** 180 * Removes the entry for {@code key} if it exists. 181 * 182 * @return the previous value mapped by {@code key}. 183 * 从内存缓存中根据key值移除某个对象并返回该对象 184 */ 185 public final V remove(K key) { 186 if (key == null) { 187 throw new NullPointerException("key == null"); 188 } 189 190 V previous; 191 synchronized (this) { 192 previous = map.remove(key); 193 if (previous != null) { 194 size -= safeSizeOf(key, previous); 195 } 196 } 197 198 if (previous != null) { 199 entryRemoved(false, key, previous, null); 200 } 201 202 return previous; 203 } 204 205 /** 206 * Called for entries that have been evicted or removed. This method is 207 * invoked when a value is evicted to make space, removed by a call to 208 * {@link #remove}, or replaced by a call to {@link #put}. The default 209 * implementation does nothing. 210 * 211 * <p>The method is called without synchronization: other threads may 212 * access the cache while this method is executing. 213 * 214 * @param evicted true if the entry is being removed to make space, false 215 * if the removal was caused by a {@link #put} or {@link #remove}. 216 * @param newValue the new value for {@code key}, if it exists. If non-null, 217 * this removal was caused by a {@link #put}. Otherwise it was caused by 218 * an eviction or a {@link #remove}. 219 */ 220 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} 221 222 /** 223 * Called after a cache miss to compute a value for the corresponding key. 224 * Returns the computed value or null if no value can be computed. The 225 * default implementation returns null. 226 * 227 * <p>The method is called without synchronization: other threads may 228 * access the cache while this method is executing. 229 * 230 * <p>If a value for {@code key} exists in the cache when this method 231 * returns, the created value will be released with {@link #entryRemoved} 232 * and discarded. This can occur when multiple threads request the same key 233 * at the same time (causing multiple values to be created), or when one 234 * thread calls {@link #put} while another is creating a value for the same 235 * key. 236 */ 237 protected V create(K key) { 238 return null; 239 } 240 241 private int safeSizeOf(K key, V value) { 242 int result = sizeOf(key, value); 243 if (result < 0) { 244 throw new IllegalStateException("Negative size: " + key + "=" + value); 245 } 246 return result; 247 } 248 249 /** 250 * Returns the size of the entry for {@code key} and {@code value} in 251 * user-defined units. The default implementation returns 1 so that size 252 * is the number of entries and max size is the maximum number of entries. 253 * 254 * <p>An entry's size must not change while it is in the cache. 255 * 用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小 256 * xUtils中创建LruMemoryCache时就重写了sizeOf方法来计算bitmap的大小 257 * mMemoryCache = new LruMemoryCache<MemoryCacheKey, Bitmap>(globalConfig.getMemoryCacheSize()) { 258 * @Override 259 * protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) { 260 * if (bitmap == null) return 0; 261 * return bitmap.getRowBytes() * bitmap.getHeight(); 262 * } 263 * }; 264 * 265 */ 266 protected int sizeOf(K key, V value) { 267 return 1; 268 } 269 270 /** 271 * Clear the cache, calling {@link #entryRemoved} on each removed entry. 272 * 清空内存缓存 273 */ 274 public final void evictAll() { 275 trimToSize(-1); // -1 will evict 0-sized elements 276 } 277 278 /** 279 * For caches that do not override {@link #sizeOf}, this returns the number 280 * of entries in the cache. For all other caches, this returns the sum of 281 * the sizes of the entries in this cache. 282 */ 283 public synchronized final int size() { 284 return size; 285 } 286 287 /** 288 * For caches that do not override {@link #sizeOf}, this returns the maximum 289 * number of entries in the cache. For all other caches, this returns the 290 * maximum sum of the sizes of the entries in this cache. 291 */ 292 public synchronized final int maxSize() { 293 return maxSize; 294 } 295 296 /** 297 * Returns the number of times {@link #get} returned a value. 298 */ 299 public synchronized final int hitCount() { 300 return hitCount; 301 } 302 303 /** 304 * Returns the number of times {@link #get} returned null or required a new 305 * value to be created. 306 */ 307 public synchronized final int missCount() { 308 return missCount; 309 } 310 311 /** 312 * Returns the number of times {@link #create(Object)} returned a value. 313 */ 314 public synchronized final int createCount() { 315 return createCount; 316 } 317 318 /** 319 * Returns the number of times {@link #put} was called. 320 */ 321 public synchronized final int putCount() { 322 return putCount; 323 } 324 325 /** 326 * Returns the number of values that have been evicted. 327 */ 328 public synchronized final int evictionCount() { 329 return evictionCount; 330 } 331 332 /** 333 * Returns a copy of the current contents of the cache, ordered from least 334 * recently accessed to most recently accessed. 335 */ 336 public synchronized final Map<K, V> snapshot() { 337 return new LinkedHashMap<K, V>(map); 338 } 339 340 @Override public synchronized final String toString() { 341 int accesses = hitCount + missCount; 342 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; 343 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", 344 maxSize, hitCount, missCount, hitPercent); 345 } 346 }
看完代码是不是觉得内存缓存的实现其实很简单?
我喜欢,驾驭着代码在风驰电掣中创造完美!我喜欢,操纵着代码在随必所欲中体验生活!我喜欢,书写着代码在时代浪潮中完成经典!每一段新的代码在我手中诞生对我来说就象观看刹那花开的感动!