LruCache详解以及实战

首先呢?看这篇文章之前大家先对LinkedHashMap以及LruCache了解一下先,先看下面两篇文章哈:

Java LinkedHashMap工作原理及实现 | Yikun
Android高效加载大图、多图解决方案,有效避免程序OOM - CSDN博客

先会用再分析是最好的!!!

源码行动开始,看下面(到这里假设你已经是LinkedHashMap和LruCache有所了解的哦)

源码分析

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    //这个缓存大小的单位,不一定是元素的个数(这句话的意思是默认是返回元素的个数sizeOf()方法默认返回1,但如果我们根据需求重写可能会返回容量等)
    private int size;  //当前缓存的值(即是当前缓存的值)
    private int maxSize;//最大值,可以通过构造方法或者resize方法传进来

    private int putCount;//添加到缓存中的个数
    private int createCount; //创建的个数
    private int evictionCount; //被移除的个数
    private int hitCount; //命中个数,其实就是累计的查询次数
    private int missCount;//丢失个数

    /**
     * @param maxSize 缓存的最大值
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //初始化构造方法,看得出它是基于LinkedHashMap的,还有就是按照访问顺序
        //(即根据时间先后)进行排列的而不是访问元素次数多少排列
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    /**
     * 重新设置缓存最大值-跟调用trimToSize()方法效果一样的
     */
    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对应的值存在(或者之后create(String key)创建),则返回这个对应值
     //,并且这个值会被加到会被移动队列头部(linkedHashMap是双向链表,所以头看起来也是尾,
     //但从正常角度来看,把最近访问的元素都会保存到tail指针那里去,即我们说的尾巴,
     //写这个文章的外国佬对这个理解不一样,他认为tail指针所在的那边也是队列头来的);
     //返回null情况有两种,一种是缓存中没有对应的值,另外一种是create(String key)方法没有创建值出来
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;//映射值,这里是指的是map的value值
        synchronized (this) {
        //加把锁是为了防止多线程访问时候map.get(key)操作过多,造成hash演算查找负担过大
            mapValue = map.get(key);
            if (mapValue != null) {  //存在的话
                hitCount++;//命中值加1,其实就是累计查询值+1
                return mapValue;                        //---注释1---
            }
            //mapValue值不存在,说明已经丢失了,这里可能很多小伙伴会问,
            //为什么不可以value为null呢? 虽然LinkedHashMap 的 key/value 值都可以为null的,
            //但很精妙就是LruCache的put方法已经规定key/value都不可以为null存入去,
            //丢失的可能是我们调用了bitmap.recycle()回收了等情况丢失了
            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.
         */
         //上面意思:尝试去通过create(String key)方法去创建一个value,这可能需
         //要花费一点时间去创建,如果创建不成功,就返回null;如果创建成功,
         //那么除了返回这个新的非null值,还会把这个值留在map中

        //这里很多人都会问:为什么synchronized 锁来创建呢?
        //创建不锁的话,多线程访问就会创建很多个createdValue 出来啦。
        //是的,你说的没错,但很多时候我们都是单线程访问,即使是多线程操作,
        //我们也很少重写create(String key) 方法,
        //所以我个人认为他这样设计是很好的
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

         //锁这里我决定是担心多线程访问同时(ms差值忽略)put的太多次导致hash演算
         //压力,加一把锁好一点的啦
        synchronized (this) {
            createCount++; //创造加1,累积创建的value值
            //mapValue 是旧值,createdValue是新值
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                // 上面意思:有冲突所以撤销之前的操作
                //为什么会有冲突呢?这里就是多线程调用createdValue(String key)
                //导致createdValue 很多个,如果之前map已经存在了key对应的value ,
                //说明是多线程操作的;很多小伙伴会问:为什么不可以呢?
                //哇咔咔,你在看看以前的代码,之前要是有值(看---注释1---),
                //早已经调用return结束了,这里有值就说明了是
                //createdValue(String key)创建出来的非null值,
                //至于为什么会多个,因为多线程同时调用create(key)创造出来的,
                //但这里是同步代码块(加了synchronized锁),
                //在一个时间只能一条线程进入访问,并且能够来到这里,
                //说明同步代码块不是第一次被访问了(但值的确是第一个线程的)
                map.put(key, mapValue);
            } else {
                 //说明之前这个key对应的value是空的,
                 //所以这个缓存值应该累积一下,       
                 //还有就是:第一个线程(或者说第一个访问这个同步代码块的线程)走的就是这个方法
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //这个方法void entryRemoved(boolean evicted, K key, V oldValue, V newValue) 是这样的,
            //这个方法调用了多次,但这里是很特殊的,大家看到下面方法里面的参数
            //createdValue, mapValue位置有点小变化,因为它不想调用trimToSize()
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //控制 size<=maxSize,下面会详说
            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}.
     */
     //上面的意思是 它会把新值value移到队列头(这里指的是tail指针那边的队列头部,
     //我们也可以理解为队列尾巴,毕竟tail的中文意思就是尾巴),不必纠结这个,
     //因为LinkedHashMap是双向链表

     //返回的是map的先前存储的值previous
    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);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
         //下面会讲解,为了使得size<maxSize
        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.
     */
     //上面意思:在size>=maxSize时候,移除最久不访问的元素(即那些不常访问的元素),
     //这里注意哦,是一个一个删除;当size<maxSize停止
     //如果maxsize为-1时候,就会清空lruCache里面的元素出去
    public void trimToSize(int maxSize) {
        while (true) {
            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) {
                    //当size<=maxSize 就停止啦
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }
                //这里就是toEvict 不为 null 了
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);//不断削减
                evictionCount++;//削减数+1
            }
             //下面分析这个办法
            entryRemoved(true, key, value, null);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     */
     //删除这个key对应的值,无论value是否为null,都会删除
     // 当value不在entry可以找到,key也是被map抹除
     //注意:value为null存储在map中是可以存储以及找到的,这里不要混淆哈
     //map可以key/value为null,但LruCache不容许,因为LruCache的put做了处理
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            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}.
     */
     //说了那么多,其实意思就是remove或者驱逐(evicted)出来,
     //我们没有通知垃圾回收器去回收它,如果你想回收,就需要自己重写这个办法.
     //至于怎么用,下面这篇文章最后讲解了
     // http://blog.csdn.net/jxxfzgy/article/details/44885623
    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.
     */
     //这个方法默认返回1,开发中99%会重写这个办法,还有1%是忘了重写了
    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);
    }
}

实战

看完了源码分析,肯定需要实践一下的,看这篇文章最后那里:
LruCache详解之 Android 内存优化 - CSDN博客

分析玩LruCache之后,希望你可以看看下面几篇文章:
1.Android高效加载大图、多图解决方案,有效避免程序OOM - CSDN博客
2.Android DiskLruCache完全解析,硬盘缓存的最佳方案 - CSDN博客
3. Android照片墙应用实现,再多的图片也不怕崩溃 - CSDN博客

猜你喜欢

转载自blog.csdn.net/simplebam/article/details/79574368
今日推荐