LruCache的使用及原理
采用LRU算法实现的话就是将最老的数据删掉。利用LRU缓存,我们能够提高系统的性能.
一,是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。
二,LinkedHashMap本身有一个方法用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashMap相当于一个linkedlist),所以,我们需要override这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。
源码分析:
1 /** 2 * Static library version of {@link android.util.LruCache}. Used to write apps 3 * that run on API levels prior to 12. When running on API level 12 or above, 4 * this implementation is still used; it does not try to switch to the 5 * framework's implementation. See the framework SDK documentation for a class 6 * overview. 7 */ 8 public class LruCache<K, V> { 9 /**缓存 map 集合,要用LinkedHashMap */ 10 private final LinkedHashMap<K, V> map; 11 12 /**缓存大小 */ 13 private int size; 14 /**最大缓存大小*/ 15 private int maxSize; 16 /**put的次数*/ 17 private int putCount; 18 /**create的次数*/ 19 private int createCount; 20 /**回收的次数*/ 21 private int evictionCount; 22 /**命中的次数*/ 23 private int hitCount; 24 /**丢失的次数*/ 25 private int missCount; 26 27 /** 28 *构造方法,maxSize最大缓存大小,初始化LinkedHashMap 29 */ 30 public LruCache(int maxSize) { 31 if (maxSize <= 0) { 32 throw new IllegalArgumentException("maxSize <= 0"); 33 } 34 this.maxSize = maxSize; 35 //将LinkedHashMap的accessOrder设置为true来实现LRU 36 //false 插入顺序 37 //true 访问顺序 38 this.map = new LinkedHashMap<K, V>(0, 0.75f, true); 39 } 40 41 /** 42 * 重新设置最大缓存大小 43 * @param maxSize 最大缓存大小. 44 */ 45 public void resize(int maxSize) { 46 if (maxSize <= 0) { 47 throw new IllegalArgumentException("maxSize <= 0"); 48 } 49 50 synchronized (this) { 51 this.maxSize = maxSize; 52 } 53 trimToSize(maxSize); 54 } 55 56 /** 57 如果缓存中存在或者被创建过,返回该值,如果已经返回了,会被移动到队列头部,如果没有被缓存和创建,会被返回null 58 */ 59 public final V get(K key) { 60 if (key == null) { 61 throw new NullPointerException("key == null"); 62 } 63 64 V mapValue; 65 synchronized (this) { 66 mapValue = map.get(key); 67 if (mapValue != null) { 68 hitCount++; 69 return mapValue; 70 } 71 missCount++; 72 } 73 74 /* 75 *如果丢失了就试图创建一个item 76 */ 77 78 V createdValue = create(key); 79 if (createdValue == null) { 80 return null; 81 } 82 83 synchronized (this) { 84 createCount++; 85 mapValue = map.put(key, createdValue); 86 87 if (mapValue != null) { 88 // There was a conflict so undo that last put 89 map.put(key, mapValue); 90 } else { 91 size += safeSizeOf(key, createdValue); 92 } 93 } 94 95 if (mapValue != null) { 96 entryRemoved(false, key, createdValue, mapValue); 97 return mapValue; 98 } else { 99 //每次新加入对象都需要调用trimToSize方法看是否需要回收 100 trimToSize(maxSize); 101 return createdValue; 102 } 103 } 104 105 /** 106 *会被移动到队列头部 107 * @return the previous value mapped by {@code key}. 108 */ 109 public final V put(K key, V value) { 110 if (key == null || value == null) { 111 throw new NullPointerException("key == null || value == null"); 112 } 113 114 V previous; 115 synchronized (this) { 116 putCount++; 117 //size加上预put对象的大小 118 size += safeSizeOf(key, value); 119 previous = map.put(key, value); 120 if (previous != null) { 121 //如果之前存在键为key的对象,则size应该减去原来对象的大小 122 size -= safeSizeOf(key, previous); 123 } 124 } 125 126 if (previous != null) { 127 entryRemoved(false, key, previous, value); 128 } 129 //每次新加入对象都需要调用trimToSize方法看是否需要回收 130 trimToSize(maxSize); 131 return previous; 132 } 133 134 /** 135 * 删除最老的条目,直到其余条目的总数达到或低于要求的大小。 136 */ 137 public void trimToSize(int maxSize) { 138 while (true) { 139 K key; 140 V value; 141 synchronized (this) { 142 if (size < 0 || (map.isEmpty() && size != 0)) { 143 throw new IllegalStateException(getClass().getName() 144 + ".sizeOf() is reporting inconsistent results!"); 145 } 146 //如果当前size小于maxSize或者map没有任何对象,则结束循环 147 if (size <= maxSize || map.isEmpty()) { 148 break; 149 } 150 //移除链表头部的元素,并进入下一次循环 151 Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); 152 key = toEvict.getKey(); 153 value = toEvict.getValue(); 154 map.remove(key); 155 size -= safeSizeOf(key, value); 156 //回收次数+1 157 evictionCount++; 158 } 159 160 entryRemoved(true, key, value, null); 161 } 162 } 163 164 /** 165 *从内存缓存中根据key值移除某个对象并返回该对象 166 */ 167 public final V remove(K key) { 168 if (key == null) { 169 throw new NullPointerException("key == null"); 170 } 171 172 V previous; 173 synchronized (this) { 174 previous = map.remove(key); 175 if (previous != null) { 176 size -= safeSizeOf(key, previous); 177 } 178 } 179 180 if (previous != null) { 181 entryRemoved(false, key, previous, null); 182 } 183 184 return previous; 185 } 186 187 /** 188 * Called for entries that have been evicted or removed. This method is 189 * invoked when a value is evicted to make space, removed by a call to 190 * {@link #remove}, or replaced by a call to {@link #put}. The default 191 * implementation does nothing. 192 *当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用, 193 * 或者替换item值时put调用,默认实现什么都没做 194 * <p>The method is called without synchronization: other threads may 195 * access the cache while this method is executing. 196 * 197 * @param evicted true if the entry is being removed to make space, false 198 * if the removal was caused by a {@link #put} or {@link #remove}. 199 * true---为释放空间被删除;false---put或remove导致 200 * @param newValue the new value for {@code key}, if it exists. If non-null, 201 * this removal was caused by a {@link #put}. Otherwise it was caused by 202 * an eviction or a {@link #remove}. 203 */ 204 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} 205 206 /** 207 * Called after a cache miss to compute a value for the corresponding key. 208 * Returns the computed value or null if no value can be computed. The 209 * default implementation returns null. 210 * 211 * <p>The method is called without synchronization: other threads may 212 * access the cache while this method is executing. 213 * 214 * <p>If a value for {@code key} exists in the cache when this method 215 * returns, the created value will be released with {@link #entryRemoved} 216 * and discarded. This can occur when multiple threads request the same key 217 * at the same time (causing multiple values to be created), or when one 218 * thread calls {@link #put} while another is creating a value for the same 219 * key. 220 * 当某Item丢失时会调用到,返回计算的相应的value或者null 221 */ 222 protected V create(K key) { 223 return null; 224 } 225 226 private int safeSizeOf(K key, V value) { 227 int result = sizeOf(key, value); 228 if (result < 0) { 229 throw new IllegalStateException("Negative size: " + key + "=" + value); 230 } 231 return result; 232 } 233 234 /** 235 * Returns the size of the entry for {@code key} and {@code value} in 236 * user-defined units. The default implementation returns 1 so that size 237 * is the number of entries and max size is the maximum number of entries. 238 * 239 * <p>An entry's size must not change while it is in the cache. 240 *这个方法要特别注意,跟我们实例化LruCache的maxSize要呼应,怎么做到呼应呢,比如maxSize的大小为缓存 241 *的个数,这里就是return 1就ok,如果是内存的大小,如果5M,这个就不能是个数了,就需要覆盖这个方法,返回每个缓存 242 *value的size大小,如果是Bitmap,这应该是bitmap.getByteCount(); 243 */ 244 protected int sizeOf(K key, V value) { 245 return 1; 246 } 247 248 /** 249 * Clear the cache, calling {@link #entryRemoved} on each removed entry. 250 * 清理缓存 251 */ 252 public final void evictAll() { 253 trimToSize(-1); // -1 will evict 0-sized elements 254 } 255 256 /** 257 * For caches that do not override {@link #sizeOf}, this returns the number 258 * of entries in the cache. For all other caches, this returns the sum of 259 * the sizes of the entries in this cache. 260 * 缓存大小 261 */ 262 public synchronized final int size() { 263 return size; 264 } 265 266 /** 267 * For caches that do not override {@link #sizeOf}, this returns the maximum 268 * number of entries in the cache. For all other caches, this returns the 269 * maximum sum of the sizes of the entries in this cache. 270 * 缓存最大值 271 */ 272 public synchronized final int maxSize() { 273 return maxSize; 274 } 275 276 /** 277 * Returns the number of times {@link #get} returned a value that was 278 * already present in the cache. 279 *返回{@link#get}返回值的次数,该值为 280 *已存在于缓存中。 281 */ 282 public synchronized final int hitCount() { 283 return hitCount; 284 } 285 286 /** 287 * Returns the number of times {@link #get} returned null or required a new 288 * value to be created. 289 *返回创建或者返回null的次数 290 */ 291 public synchronized final int missCount() { 292 return missCount; 293 } 294 295 /** 296 * Returns the number of times {@link #create(Object)} returned a value. 297 返回创建一个元素的次数 298 */ 299 public synchronized final int createCount() { 300 return createCount; 301 } 302 303 /** 304 * Returns the number of times {@link #put} was called. 305 调用put返回次数 306 */ 307 public synchronized final int putCount() { 308 return putCount; 309 } 310 311 /** 312 * Returns the number of values that have been evicted. 313 * 返回已被逐出的值的数目。 314 */ 315 public synchronized final int evictionCount() { 316 return evictionCount; 317 } 318 319 /** 320 * Returns a copy of the current contents of the cache, ordered from least 321 * recently accessed to most recently accessed. 322 * 返回缓存拷贝,排序规则为最近最多访问 323 */ 324 public synchronized final Map<K, V> snapshot() { 325 return new LinkedHashMap<K, V>(map); 326 } 327 328 /** 329 * 返回maxSize hits misses hitRate值 330 * LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%% 331 */ 332 @Override public synchronized final String toString() { 333 int accesses = hitCount + missCount; 334 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; 335 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", 336 maxSize, hitCount, missCount, hitPercent); 337 } 338 }
实际使用:
public class BitmapCacheActivity extends Activity { private ImageView iv_picture; private BitmapCache<String, Bitmap> mMemoryCache; private BitmapCache.BitmapRemovedCallBack<String> mEnteryRemovedCallBack = new BitmapCache.BitmapRemovedCallBack<String>() { @Override public void onBitmapRemoved(String key) { //处理回收bitmap前,清空相关view的bitmap操作 mMemoryCache.remove(key); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bitmapcache); iv_picture = findViewById(R.id.iv_picture); // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 // BitmapCache通过构造函数传入缓存值,以bit为单位。 int memClass = ((ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); // 使用单个应用最大可用内存值的1/8作为缓存的大小。 int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new BitmapCache<>(cacheSize, mEnteryRemovedCallBack); loadBitmap(R.mipmap.ic_launcher_round, iv_picture); } /** * bitmap添加到缓存中去 * @param key * @param bitmap */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从缓存中获取bitmap * @param key * @return */ public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /** * 加载bitmap * @param resId * @param imageView */ public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); //从缓存里面获取,没有设置默认的,然后在进行一步缓存操作 if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.person_image_empty); BitmapLoadingTask task = new BitmapLoadingTask(imageView); task.execute(resId); } } class BitmapLoadingTask extends AsyncTask<Integer, Void, Bitmap> { private ImageView imageView; public BitmapLoadingTask(ImageView imageView) { this.imageView = imageView; } // 在后台加载图片。 @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); //显示在bitmap上 imageView.setImageBitmap(bitmap); } public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } } }