zoukankan      html  css  js  c++  java
  • LruCache 缓存

    内存缓存(LruCache):
    系统提供的LruCache类是非常适合用作缓存Bitmap任务的,
    它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
    注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,
    然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。
    http://developer.android.com/reference/android/util/LruCache.html

    /*
     * Copyright (C) 2011 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package android.util;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
     * 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
     * 如果你cache的某个值需要明确释放,重写entryRemoved()
     * 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回
     * 默认cache大小是测量的item的数量,重写sizeof计算不同item的
     *  大小。
     *   int cacheSize = 4 * 1024 * 1024; // 4MiB 
     *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { 
     *       protected int sizeOf(String key, Bitmap value) { 
     *           return value.getByteCount(); 
     *       } 
     *   }}
     *   不允许key或者value为null
     *   当get(),put(),remove()返回值为null时,key相应的项不在cache中
     */
    public class LruCache<K, V> {
        private final LinkedHashMap<K, V> map;
    
        /** Size of this cache in units. Not necessarily the number of elements. */
        private int size; //已经存储的大小
        private int maxSize; //规定的最大存储空间
    
        private int putCount; //put的次数
        private int createCount;//create的次数
        private int evictionCount;//回收的次数
        private int hitCount;//命中的次数
        private int missCount;//丢失的次数
    
        /**
         * @param maxSize for caches that do not override {@link #sizeOf}, this is
         *     the maximum number of entries in the cache. For all other caches,
         *     this is the maximum sum of the sizes of the entries in this cache.
         */
        public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            this.maxSize = maxSize;
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true); // LinkedHashMap的初始化放在构造器中,
                                                                //这里将LinkedHashMap的accessOrder设置为true,为插入顺序,默认是访问顺序
        }
    
        /**
         * Sets the size of the cache.
         * @param maxSize The new maximum size.
         *
         * @hide
         */
        public void resize(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
    
            synchronized (this) {
                this.maxSize = maxSize;
            }
            trimToSize(maxSize);
        }
    
        /**
         * Returns the value for {@code key} if it exists in the cache or can be
         * created by {@code #create}. If a value was returned, it is moved to the
         * head of the queue. This returns null if a value is not cached and cannot
         * be created.
         * 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
         * 如果item的value没有被cache或者不能被创建,则返回null。
         */
        public final V get(K key) {
            if (key == null) { //不允许空键  
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);//调用LinkedHashMap的get方法  
                if (mapValue != null) {
                    hitCount++;
                    return mapValue;
                }
                missCount++;
            }
    
            /*
               * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             * 如果丢失了就试图创建一个item
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++; //如果创建成功,那么create次数加1  
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }
    
        /**
         * Caches {@code value} for {@code key}. The value is moved to the head of
         * the queue.
         *
         * @return the previous value mapped by {@code key}.
         */
        public final V put(K key, V value) {
            if (key == null || value == null) { //键值不允许为空  
                throw new NullPointerException("key == null || value == null");
            }
    
            V previous;
            synchronized (this) {
                putCount++;
                size += safeSizeOf(key, value);//统计放入的值的大小,然后增加size的记录值  
                previous = map.put(key, value);//把新值放入缓存map中,然后获得旧值  
                if (previous != null) { //旧值不为空,由于替换了旧值,所以需要把缓存数据总大小减去这个旧值的大小    
                    size -= safeSizeOf(key, previous);
                }
            }
            //还要调用entryRemoved()方法来让子类去处理不用的旧值previous,可能按照自己的方式去释放它。  
            //当然了,子类也可以不实现这个方法。  
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    
        /**
         * Remove the eldest entries until the total of remaining entries is at or
         * below the requested size.
         *
         * @param maxSize the maximum size of the cache before returning. May be -1
         *            to evict even 0-sized elements.
         把最少访问的老数据删除,直到总数据大小在上限的范围之内. 
         数据上限. 值可能是-1,这样就会删除所有的缓存数据.
         */
        public void trimToSize(int maxSize) {
            while (true) { //不断删除linkedHashMap头部entry,也就是最近最少访问的条目,直到size小于最大容量  
                K key;
                V value;
                synchronized (this) {
                    if (size < 0 || (map.isEmpty() && size != 0)) {
                        throw new IllegalStateException(getClass().getName()
                                + ".sizeOf() is reporting inconsistent results!");
                    }
    
                    if (size <= maxSize) { //直到容量小于最大容量为止  
                        break;
                    }
    
                    Map.Entry<K, V> toEvict = map.eldest(); //获取最少访问的entry
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);//删除最少访问的entry  
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    
        /**
         * Removes the entry for {@code key} if it exists.
         *
         * @return the previous value mapped by {@code key}.
         */
        public final V remove(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V previous;
            synchronized (this) {
                previous = map.remove(key);//调用LinkedHashMap的remove方法  
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, null);
            }
    
            return previous;
        }
    
        /**
         * Called for entries that have been evicted or removed. This method is
         * invoked when a value is evicted to make space, removed by a call to
         * {@link #remove}, or replaced by a call to {@link #put}. The default
         * implementation does nothing.
         *
         * <p>The method is called without synchronization: other threads may
         * access the cache while this method is executing.
         *
         * @param evicted true if the entry is being removed to make space, false
         *     if the removal was caused by a {@link #put} or {@link #remove}.
         * @param newValue the new value for {@code key}, if it exists. If non-null,
         *     this removal was caused by a {@link #put}. Otherwise it was caused by
         *     an eviction or a {@link #remove}.
         */
        protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
    
        /**
         * Called after a cache miss to compute a value for the corresponding key.
         * Returns the computed value or null if no value can be computed. The
         * default implementation returns null.
         *
         * <p>The method is called without synchronization: other threads may
         * access the cache while this method is executing.
         *
         * <p>If a value for {@code key} exists in the cache when this method
         * returns, the created value will be released with {@link #entryRemoved}
         * and discarded. This can occur when multiple threads request the same key
         * at the same time (causing multiple values to be created), or when one
         * thread calls {@link #put} while another is creating a value for the same
         * key.
         */
        protected V create(K key) {
            return null;
        }
    
        private int safeSizeOf(K key, V value) {
            int result = sizeOf(key, value);
            if (result < 0) {
                throw new IllegalStateException("Negative size: " + key + "=" + value);
            }
            return result;
        }
    
        /**
         * Returns the size of the entry for {@code key} and {@code value} in
         * user-defined units.  The default implementation returns 1 so that size
         * is the number of entries and max size is the maximum number of entries.
         *
         * <p>An entry's size must not change while it is in the cache.
         * 这个方法用于计算每个条目的大小,子类必须得复写这个类。
         */
        protected int sizeOf(K key, V value) {
            return 1;
        }
    
        /**
         * Clear the cache, calling {@link #entryRemoved} on each removed entry.
         */
        public final void evictAll() {
            trimToSize(-1); // -1 will evict 0-sized elements
        }
    
        /**
         * For caches that do not override {@link #sizeOf}, this returns the number
         * of entries in the cache. For all other caches, this returns the sum of
         * the sizes of the entries in this cache.
         */
        public synchronized final int size() {
            return size;
        }
    
        /**
         * For caches that do not override {@link #sizeOf}, this returns the maximum
         * number of entries in the cache. For all other caches, this returns the
         * maximum sum of the sizes of the entries in this cache.
         */
        public synchronized final int maxSize() {
            return maxSize;
        }
    
        /**
         * Returns the number of times {@link #get} returned a value that was
         * already present in the cache.
         */
        public synchronized final int hitCount() {
            return hitCount;
        }
    
        /**
         * Returns the number of times {@link #get} returned null or required a new
         * value to be created.
         */
        public synchronized final int missCount() {
            return missCount;
        }
    
        /**
         * Returns the number of times {@link #create(Object)} returned a value.
         */
        public synchronized final int createCount() {
            return createCount;
        }
    
        /**
         * Returns the number of times {@link #put} was called.
         */
        public synchronized final int putCount() {
            return putCount;
        }
    
        /**
         * Returns the number of values that have been evicted.
         */
        public synchronized final int evictionCount() {
            return evictionCount;
        }
    
        /**
         * Returns a copy of the current contents of the cache, ordered from least
         * recently accessed to most recently accessed.
         */
        public synchronized final Map<K, V> snapshot() {
            return new LinkedHashMap<K, V>(map);
        }
    
        @Override public synchronized final String toString() {
            int accesses = hitCount + missCount;
            int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
            return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                    maxSize, hitCount, missCount, hitPercent);
        }
    }

    总结:
    1.LruCache封装了LinkedHashMap,提供了LRU缓存的功能;
    2.LruCache通过trimToSize方法自动删除最近最少访问的键值对;
    3.LruCache不允许空键值;
    4.LruCache线程安全;
    5.继承LruCache时,必须要复写sizeof方法,用于计算每个条目的大小。

  • 相关阅读:
    【浅谈数位计数一类问题】
    2.2、idea 中写spark sql
    javascript事件之鼠标滚轮(mousewheel)和DOMMouseScroll事件
    JS 判断鼠标滚轮的上下滚动
    gulp 用法 小结
    'gulp'不是内部或者外部命令,也不是可运行的程序
    Gulp的安装与配置
    前端构建工具gulpjs的使用介绍及技巧
    【转】弧度和角度的转换
    前端构建工具gulpjs的使用介绍及技巧
  • 原文地址:https://www.cnblogs.com/xueqiang911226/p/3958259.html
Copyright © 2011-2022 走看看