LruCache-一个工具类

336 阅读2分钟

介绍

一个缓存类,持有缓存对象的强引用。每当使用到一个缓存的value,会把它放到数据容器的顶部。当缓存存满,数据容器尾部的缓存value被驱逐。

注意点

  • 如果你的缓存对象需要显示释放,重写entryRemoved
  • 如果读取缓存发生未命中而且需要为此次读取生成一个对象返回,需要重写create。这样的话,使用者可以认为即使缓存未命中,夜总会返回一个value。
  • 默认,缓存的size是计算的entry的个数。你可以通过重写sizeOf实现缓存的size是每个entery对应value的大小相加。
  • LruCache不予许null作为key或者value。当出现return为null,代表没有这个key对应的value
  • LruCache提供的操作是线程安全的。

阅读

LinkedHashMap是Lrucache的核心数据容器,LruCache最近最少使用策略也是通过LinedHashMap天然实现。我们这里不去谈LinkedHashMap的存储,只看LurCache的初始化,get/put/remove操作。

初始化

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);
    }

只是初始化maxsize字段,没做什么特别的事情。

get

public final V get(K key) {
        // 验证key不能为null
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        //进行读取动作,通过synchronized保证并发
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
        // 未命中时,create一个value。若未重写create,返回null
        // 这个方法可能发生并发操作。若发生并发,永远抛弃createValue
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        // 可以发现,任何涉及对linkedhashmap进行操作的时候,
        // 需要进行synchronization处理
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // 这里就是上面说的,并发的时候,抛弃createValue
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

put

public final V put(K key, V value) {
        // 不支持key value 为null。
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            // 不管三七二十一先加入linkedhashmap,最后判断是否溢出
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        // 这里判断溢出,回收。
        trimToSize(maxSize);
        return previous;
    }

remove

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;
    }

trimToSize 最少算法在这里被涉及到

**public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                // linkedhashmap的eldest就提供了最少使用的entry。
                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }**

总结

看了这几个操作,是否感觉空空?是的,这几个操作也就是注意synchronization,固定的entryRmove通知,再无其他。而最少使用的实现,看LinkedHashMap