公子姓王

导航

Java容器解析系列(17) LruCache详解

在之前讲LinkedHashMap的时候,我们说起可以用来实现LRU(least recent used)算法,接下来我看一下其中的一个具体实现-----android sdk 中的LruCache.

关于Lru算法,请参考漫画:什么是LRU算法?

talk is cheap, I am gonna show you something really expensive.

package android.util;// 该类是从Android sdk 中摘录

public class LruCache<K, V> {

    private final LinkedHashMap<K, V> map;// 这里的 LinkedHashMap 为 android.jar 中的类,与jdk中的有所区别,这里不做区分
    private int size;// 当前 cache 的大小
    private int maxSize;// cache 最大容量
    private int putCount;// put() 调用的次数
    private int createCount;// get()未命中,成功构建新 key:value 的次数
    private int evictionCount;// 因cache大小超过容量限制,将 key:value 从 cache 中驱逐的次数
    private int hitCount;// get()命中的次数
    private int missCount;// get()未命中的次数

    // 构造时设置最大容量
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        // 这里传入的 LinkedHashMap 的 accessOrder 为true,表示 其中的链表中的结点顺序为 "按访问顺序"
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    // 重新设置 cache 最大容量
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {// 同步访问
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;// 命中次数 + 1
                return mapValue;
            }
            missCount++;// 未命中次数 + 1
        }
        V createdValue = create(key);// 通过 key 构建一个value(比如从文件中读入value)
        if (createdValue == null) {// 创建失败
            return null;
        }
        synchronized (this) {
            createCount++;// 构建 value 成功次数 + 1
            mapValue = map.put(key, createdValue);// 添加到 LinkedHashMap 中
            if (mapValue != null) {
                // LinkedHashMap 原来有该key的值(在构建value的时候,被其他线程添加进去的),这里重新把原来的值放进去,cache大小没有增加
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);// cache 大小增加
            }
        }
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);// 大小增加了,保证在最大容量范围内
            return createdValue;
        }
    }

    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++;// put 次数
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);// 替换已有的value
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        trimToSize(maxSize);
        return previous;
    }

    // 移除LRU键值对,保证 cache 的大小在 maxSize 范围内
    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) {
                    break;
                }
                Map.Entry<K, V> toEvict = map.eldest();// 最老的结点,返回head结点,也即 LRU结点
                if (toEvict == null) {// 没有可以删除的key:value了
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);// 大小降低
                evictionCount++;// 驱逐次数 + 1
            }
            entryRemoved(true, key, value, null);
        }
    }

    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous;
        synchronized (this) {
            previous = map.remove(key);// 通过 LinkedHashMap 移除
            if (previous != null) {
                size -= safeSizeOf(key, previous);// cache总大小降低
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;
    }

    // key:oldValue 被 移除 或 驱逐 时回调
    protected void entryRemoved(boolean evicted/*是否是被驱逐(因cache大小超过容量限制被删除)*/,
                                K key, V oldValue, V newValue) {
    }

    // 根据指定 key,构建 value
    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;
    }

    // key:value 键值对大小,用于计算cache大小
    // 可根据情况自定义,默认为 1
    // 该 key:value 在 cache 中时大小不应该变化
    protected int sizeOf(K key, V value) {
        return 1;
    }

    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    // 各种方法,返回成员变量,省略
    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);
    }

}

posted on 2019-05-29 10:54  公子姓王  阅读(222)  评论(0编辑  收藏  举报