LruCache的使用及原理

简介: 采用LRU算法实现的话就是将最老的数据删掉。利用LRU缓存,我们能够提高系统的性能. 一,是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。
采用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;
        }
    }
}

 

 

 

相关文章
|
6月前
|
存储 缓存 算法
图解LinkedHashMap原理
图解LinkedHashMap原理
62 0
|
存储 缓存 算法
【算法】不使用LinkedHashMap实现一个LRU缓存
【算法】不使用LinkedHashMap实现一个LRU缓存
105 0
HashMap源码学习:红黑树原理详解
HashMap源码学习:红黑树原理详解
67 0
|
存储 缓存 算法
Android内存缓存LruCache源码解析
内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,当一个新数据要添加到LruCache而此时缓存大小要满时,队尾的数据就有可能会被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:<strong>把最近最少使用的数据从队列中移除,把内存分配给最新进入的数据。</strong>
|
存储 算法 Java
HashMap都在用,原理你真的了解吗?
HashMap虽然日常大家都在用。网上对hashMap原理讲解的博客文章也是数不胜数,想要彻底掌握其底层原理和实现流程;还是得结合jdk源码一步一步跟踪。
127 0
HashMap都在用,原理你真的了解吗?
|
缓存 算法 安全
如何使用 LinkedHashMap 实现 LRU 缓存?
在上一篇文章里,我们聊到了 HashMap 的实现原理和源码分析,在源码分析的过程中,我们发现一些 LinkedHashMap 相关的源码,当时没有展开,现在它来了。 那么,LinkedHashMap 与 HashMap 有什么区别呢?其实,LinkedHashMap 的使用场景非常明确 —— LRU 缓存。今天,我们就来讨论 LinkedHashMap 是如何实现 LRU 缓存的。
191 0
|
存储 缓存 NoSQL
漫谈 LevelDB 数据结构(三):LRU 缓存( LRUCache)
漫谈 LevelDB 数据结构(三):LRU 缓存( LRUCache)
406 0
漫谈 LevelDB 数据结构(三):LRU 缓存( LRUCache)
|
存储 缓存 算法
【设计数据结构】实现一个 LRUCache
【设计数据结构】实现一个 LRUCache
|
存储 Java Android开发
HashMap 的产生与原理
数组:一片物理上连续的大小确定的储存空间。 好处:根据下标快速的查找和修改里面的内容。 缺点:大小确定,无法修改。添加新的元素或者删除元素比较麻烦。
169 0
HashMap 的产生与原理
|
存储
LinkedHashMap 源码分析,底层竟这么简单!
LinkedHashMap 是一个键有序的 HashMap,可以将 LinkedHashMap 理解为 LinkList + HashMap。 所以研究 LinkedHashMap之前要先看 HashMap代码,这里不再赘述。 其实 LinkedHashMap无非就是通过链表结构将存储在 HashMap中的数据通过 beofre,after连接起来
114 0
LinkedHashMap 源码分析,底层竟这么简单!