• 缓存(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小或者缓存为空

  • 相关阅读:
    93. Restore IP Addresses
    mysql复制那点事(2)-binlog组提交源码分析和实现
    49. Group Anagrams
    43. Multiply Strings
    66. Plus One
    100. Same Tree
    MySQL 加锁处理分析
    mysql死锁问题分析
    数据库事务的四大特性以及事务的隔离级别
    MySQL常见的三种存储引擎(InnoDB、MyISAM、MEMORY)
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/6900264.html
Copyright © 2020-2023  润新知