内存缓存LruCache实现原理
生活随笔
收集整理的這篇文章主要介紹了
内存缓存LruCache实现原理
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
自己項(xiàng)目中一直都是用的開源的xUtils框架,包括 BitmapUtils、DbUtils、ViewUtils和HttpUtils四大模塊,這四大模塊都是項(xiàng)目中比較常用的。最近決定研究一下 xUtils的源碼,用了這么久總得知道它的實(shí)現(xiàn)原理吧。我是先從先從BitmapUtils模塊開始的。BitmapUtils和大多數(shù)圖片加載框架一 樣,都是基于內(nèi)存-文件-網(wǎng)絡(luò)三級(jí)緩存。也就是加載圖片的時(shí)候首先從內(nèi)存緩存中取,如果沒有再從文件緩存中取,如果文件緩存沒有取到,就從網(wǎng)絡(luò)下載圖片并 且加入內(nèi)存和文件緩存。
這篇帖子先分析內(nèi)存緩存是如何實(shí)現(xiàn)的。好吧開始進(jìn)入正題。BitmapUtils內(nèi)存緩存的核心類LruMemoryCache,LruMemoryCache代碼和v4包的LruCache一樣,只是加了一 個(gè)存儲(chǔ)超期的處理,這里分析LruCache源碼。LRU即Least Recently Used,近期最少使用算法。也就是當(dāng)內(nèi)存緩存達(dá)到設(shè)定的最大值時(shí)將內(nèi)存緩存中近期最少使用的對(duì)象移除,有效的避免了OOM的出現(xiàn)。
? ? ? ??講到LruCache不得不提一下LinkedHashMap,因?yàn)長ruCache中Lru算法的實(shí)現(xiàn)就是通過LinkedHashMap來實(shí)現(xiàn) 的。LinkedHashMap繼承于HashMap,它使用了一個(gè)雙向鏈表來存儲(chǔ)Map中的Entry順序關(guān)系,這種順序有兩種,一種是LRU順序,一 種是插入順序,這可以由其構(gòu)造函數(shù)public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,對(duì)于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,還做些調(diào)整 Entry順序鏈表的工作。LruCache中將LinkedHashMap的順序設(shè)置為LRU順序來實(shí)現(xiàn)LRU緩存,每次調(diào)用get(也就是從內(nèi)存緩存 中取圖片),則將該對(duì)象移到鏈表的尾端。調(diào)用put插入新的對(duì)象也是存儲(chǔ)在鏈表尾端,這樣當(dāng)內(nèi)存緩存達(dá)到設(shè)定的最大值時(shí),將鏈表頭部的對(duì)象(近期最少用到 的)移除。關(guān)于LinkedHashMap詳解請(qǐng)前往http://www.cnblogs.com/children/archive/2012/10/02/2710624.html。
? ? ? ? 下面看下LruCache的源碼,我都注釋的很詳細(xì)了。
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; //當(dāng)前cache的大小34 private int maxSize; //cache最大大小3536 private int putCount; //put的次數(shù)37 private int createCount; //create的次數(shù)38 private int evictionCount; //回收的次數(shù)39 private int hitCount; //命中的次數(shù)40 private int missCount; //未命中次數(shù)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設(shè)置為true來實(shí)現(xiàn)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獲取相應(yīng)的item,或者創(chuàng)建返回相應(yīng)的item。相應(yīng)的item會(huì)移動(dòng)到隊(duì)列的尾部,62 如果item的value沒有被cache或者不能被創(chuàng)建,則返回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對(duì)象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 如果未命中,則試圖創(chuàng)建一個(gè)對(duì)象,這里create方法返回null,并沒有實(shí)現(xiàn)創(chuàng)建對(duì)象的方法86 如果需要事項(xiàng)創(chuàng)建對(duì)象的方法可以重寫create方法。因?yàn)閳D片緩存時(shí)內(nèi)存緩存沒有命中會(huì)去87 文件緩存中去取或者從網(wǎng)絡(luò)下載,所以并不需要?jiǎng)?chuàng)建。88 /89 V createdValue = create(key);90 if (createdValue == null) {91 return null;92 }93 //假如創(chuàng)建了新的對(duì)象,則繼續(xù)往下執(zhí)行94 synchronized (this) {95 createCount++;96 //將createdValue加入到map中,并且將原來鍵為key的對(duì)象保存到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 //加入新創(chuàng)建的對(duì)象之后需要重新計(jì)算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 //每次新加入對(duì)象都需要調(diào)用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加上預(yù)put對(duì)象的大小 133 previous = map.put(key, value); 134 if (previous != null) { 135 //如果之前存在鍵為key的對(duì)象,則size應(yīng)該減去原來對(duì)象的大小 136 size -= safeSizeOf(key, previous); 137 } 138 } 139 140 if (previous != null) { 141 entryRemoved(false, key, previous, value); 142 } 143 //每次新加入對(duì)象都需要調(diào)用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 此方法根據(jù)maxSize來調(diào)整內(nèi)存cache的大小,如果maxSize傳入-1,則清空緩存中的所有對(duì)象 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 //如果當(dāng)前size小于maxSize或者map沒有任何對(duì)象,則結(jié)束循環(huán) 163 if (size <= maxSize || map.isEmpty()) { 164 break; 165 } 166 //移除鏈表頭部的元素,并進(jìn)入下一次循環(huán) 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++; //回收次數(shù)+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 從內(nèi)存緩存中根據(jù)key值移除某個(gè)對(duì)象并返回該對(duì)象 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 用來計(jì)算單個(gè)對(duì)象的大小,這里默認(rèn)返回1,一般需要重寫該方法來計(jì)算對(duì)象的大小 256 xUtils中創(chuàng)建LruMemoryCache時(shí)就重寫了sizeOf方法來計(jì)算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 清空內(nèi)存緩存 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 } 看完代碼是不是覺得內(nèi)存緩存的實(shí)現(xiàn)其實(shí)很簡單?
總結(jié)
以上是生活随笔為你收集整理的内存缓存LruCache实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高Python运行效率的六个窍门
- 下一篇: bat 批处理 常用命令和乱码问题