• 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;
            }
        }
    }
  • 相关阅读:
    在idea的控制台进行git pull 或者git push的时候每次都要输入用户名和密码的解决办法
    js中和html中onclick绑定函数要不要加括号的问题
    ElementUI checkbox组件中的indeterminate 状态
    js之常见问题--for循环中为什么点击总是弹出最后一个i
    vue中watch监听浏览器窗口大小的改变
    Duplicate keys detected: '0'. This may cause an update error.
    数组对象去重总结
    【转载】git 回退版本
    K8s
    go 记录日志到Elk
  • 原文地址:https://www.cnblogs.com/androidsuperman/p/8427742.html
Copyright © 2020-2023  润新知