缓存(LruCache)机制

LruCache

1.变量

private final LinkedHashMap<K, V> map;

private int size;//已经存储的数据大小
private int maxSize;//最大存储大小

private int putCount;//调用put的次数
private int createCount;//调用create的次数
private int evictionCount;//收回的次数
private int hitCount;//取出数据的成功次数
private int missCount;//取出数据的丢失次数

2.构造函数

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

3.保存到缓存

public final V put(K key, V value) {
    ...
    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);
    }

    trimToSize(maxSize);
    return previous;
}

safeSizeOf()

private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

sizeOf()

protected int sizeOf(K key, V value) {
    return 1;
}

trimToSize()

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized (this) {
            ...
            if (size <= maxSize) {
                break;
            }

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

entryRemoved()是空函数

4.从缓存中取

public final V get(K key) {
    ...
    V mapValue;
    synchronized (this) {
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }
        missCount++;
    }

    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        createCount++;
        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;
    }
}

其中,map.put/get调用的都是LinkedHashMap中的方法,下面我们来看

LinkedHashMap

1.构造函数

public LinkedHashMap() {
    init();
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, false);
}

public LinkedHashMap(
        int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    init();
    this.accessOrder = accessOrder;
}

public LinkedHashMap(Map<? extends K, ? extends V> map) {
    this(capacityForInitSize(map.size()));
    constructorPutAll(map);
}

LruCache构造函数中调用的是

this.map = new LinkedHashMap<K, V>(0, 0.75f, true);

init()

@Override void init() {
    header = new LinkedEntry<K, V>();
}
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
    LinkedEntry<K, V> nxt;
    LinkedEntry<K, V> prv;

    /** Create the header entry */
    LinkedEntry() {
        super(null, null, 0, null);
        nxt = prv = this;
    }

    /** Create a normal entry */
    LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
                LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
        super(key, value, hash, next);
        this.nxt = nxt;
        this.prv = prv;
    }
}

2.获取缓存

LruCache中的put调用的就是HashMap的put,get方法在LinkedHashMap中复写

@Override public V get(Object key) {
    /*
     * This method is overridden to eliminate the need for a polymorphic
     * invocation in superclass at the expense of code duplication.
     */
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null)
            return null;
        if (accessOrder)
            makeTail((LinkedEntry<K, V>) e);
        return e.value;
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
            e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }
    }
    return null;
}

makeTail()

private void makeTail(LinkedEntry<K, V> e) {
    // Unlink e
    e.prv.nxt = e.nxt;
    e.nxt.prv = e.prv;

    // Relink e as tail
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    e.nxt = header;
    e.prv = oldTail;
    oldTail.nxt = header.prv = e;
    modCount++;
}

这样,键值对被剥离出来,放到了链表的尾巴上。这样的结果就是不管用户是调用get()还是put()都会将操作的那个实体放在链表的最后位置,那么最不常用的就会放在最首位的下一个节点,对应的实体为header.nex,也就是放在header的下一节点. 

总结:

  • 1.LruCache 是通过 LinkedHashMap 构造方法的第三个参数的 accessOrder=true 实现了 LinkedHashMap 的数据排序基于访问顺序 (最近访问的数据会在链表尾部),在容量溢出的时候,将链表头部的数据移除。从而,实现了 LRU 数据缓存机制。

  • **2.**LruCache 在内部的get、put、remove包括 trimToSize 都是安全的(因为都上锁了)。

  • **3.**LruCache 自身并没有释放内存,将 LinkedHashMap 的数据移除了,如果数据还在别的地方被引用了,还是有泄漏问题,还需要手动释放内存。

  • **4.**覆写 entryRemoved 方法能知道 LruCache 数据移除是是否发生了冲突,也可以去手动释放资源。

  • 5.maxSize 和 sizeOf(K key, V value) 方法的覆写息息相关,必须相同单位。( 比如 maxSize 是7MB,自定义的 sizeOf 计算每个数据大小的时候必须能算出与MB之间有联系的单位 )

ps:纯原创,参考文章如下

https://github.com/LittleFriendsGroup/AndroidSdkSourceAnalysis/blob/master/article/LruCache%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md

面试回答:

1)LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用

2)如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少

3)size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为mazSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小

4)除异常外,首先会判断size是否超过maxSize,如果超过了就取出最先插入的缓存,如果不为空就删掉,并把size减去该项所占的大小。这个操作将一直循环下去,直到size比maxSize小或者缓存为空

原文地址:https://www.cnblogs.com/anni-qianqian/p/6900264.html