内存缓存LruCache实现原理
生活随笔
收集整理的這篇文章主要介紹了
内存缓存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 Project3 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 at7 8 http://www.apache.org/licenses/LICENSE-2.09 10 Unless required by applicable law or agreed to in writing, software11 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 and14 limitations under the License.15 /1617 package android.support.v4.util;1819 import java.util.LinkedHashMap;20 import java.util.Map;2122 /**23 Static library version of {@link android.util.LruCache}. Used to write apps24 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 the26 framework’s implementation. See the framework SDK documentation for a class27 overview.28 /29 public class LruCache {30 private final LinkedHashMap map;3132 /** Size of this cache in units. Not necessarily the number of elements. /33 private int size; //當前cache的大小34 private int maxSize; //cache最大大小3536 private int putCount; //put的次數37 private int createCount; //create的次數38 private int evictionCount; //回收的次數39 private int hitCount; //命中的次數40 private int missCount; //未命中次數4142 /43 @param maxSize for caches that do not override {@link #sizeOf}, this is44 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來實現LRU53 this.map = new LinkedHashMap(0, 0.75f, true);54 }5556 /57 Returns the value for {@code key} if it exists in the cache or can be58 created by {@code #create}. If a value was returned, it is moved to the59 head of the queue. This returns null if a value is not cached and cannot60 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 }6869 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 }7980 /81 Attempt to create a value. This may take a long time, and the map82 may be different when create() returns. If a conflicting value was83 added to the map while create() was working, we leave that value in84 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的對象保存到mapValue97 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 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 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 The method is called without synchronization: other threads may 228 access the cache while this method is executing. 229 230 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 An entry’s size must not change while it is in the cache. 255 用來計算單個對象的大小,這里默認返回1,一般需要重寫該方法來計算對象的大小 256 xUtils中創建LruMemoryCache時就重寫了sizeOf方法來計算bitmap的大小 257 mMemoryCache = new LruMemoryCache(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 snapshot() { 337 return new LinkedHashMap(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 } 看完代碼是不是覺得內存緩存的實現其實很簡單?
總結
以上是生活随笔為你收集整理的内存缓存LruCache实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高Python运行效率的六个窍门
- 下一篇: bat 批处理 常用命令和乱码问题