美文网首页
LinkedHashMap源码浅析(三):LruCache浅解

LinkedHashMap源码浅析(三):LruCache浅解

作者: 小川君 | 来源:发表于2018-09-17 09:16 被阅读0次

    LRU(Least Recently Used)最近最少使用,是Android提供的一个缓存类,内部则由LinkedHashMap实现

    先来看LruCache中的属性:

    size 当前所缓存的对象数量
    maxSize 当前LruCache所能承受的容量
    putCount 添加对象的次数
    createCount 添加的次数
    evictionCount
    hitCount命中次数,根据key能够正确查到指定的value的次数
    missCount 未命中次数,根据key未能查到指定的value的次数
    map 是一个LinkedHashMap 存放对象,是LruCache的核心

    构造器
        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);
        }
    

    传入一个最大容量,并初始化map;

    put
        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);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    

    可以看到这里面加了一个同步锁synchronized,我们来看看里面,首先putCount++,然后通过safeSizeOf计算传入对象的大小,并加入到size,然后再将传入的对象添加到集合map中,如果传入的key值在集合中有对应的元素,还需要对原始元素的大小进行计算,最后减去原始元素的值,同步块之外还有两个方法entryRemoved() trimToSize(),加上前面说的计算对象大小的方法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;
        }
        protected int sizeOf(K key, V value) {
            return 1;
        }    
    

    可以看到safeSizeOf()中具体计算元素大小的地方是sizeOf(),而此方法默认返回为1,但是我们可以重写此方法,通过我们具体的业务来进行具体的元素类型大小的计算,
    LinkedHashMap#entryRemoved():

        protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
    

    默认是个空方法,但是在网上找了些介绍文字:

    put发生key冲突时被调用,evicted = false,key为传入的key值,oldValue为该key所对应的原始value值,newValue为传入的value值
    trimToSize的时候会被调用一次,就是最后一次被删除的最少访问数据带回来,evicted = true,newVlue = null
    remove的时候,存在对应的key,并且被成功删除后调用,evicted = false, newVlue = null
    再者这个方法的应用场景,我没有找到,虽然返回了原始值,但是原始值也可能被提前回收了,

    LruCache#trimToSize():

        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();
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    

    整体是一个死循环,结束循环的条件是

    当前集合中的元素总大小 < 设置集合的总容量
    map.eldest() 返回当前集合的头结点,如果头结点为空,即集合为空,直接返回循环

    综上可以看出此方法的作用是:当集合的元素的总大小大于指定的元素大小使,用来删除一些元素来空出一部分空间的操作。而删除的节点是从头结点开始的,符合访问顺序的特性,删除最近没有被访问的节点

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

    前一部分是正常的从集合中遍历指定的key值,如果存在指定的key值,hitCount++,并返回找到的key值,如果没有找到则missCount++,并且通过create()创建一个value值,如果这个值不为空则createValue++并将此key以及value放入到集合中。
    create()是一个空方法,我们可以自己去继承实现

            LruCache<String,Bitmap> lruCache = new LruCache<String,Bitmap>(50){
    
                //自己实现的计算元素大小的方法
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return super.sizeOf(key, value);
                }
            
                // 重新定义集合的最大容量
                @Override
                public void resize(int maxSize) {
                    super.resize(maxSize);
                }
                // 如果没有找到此key对应的value,通过此方法可以创建一个value值
                @Override
                protected Bitmap create(String key) {
                    return super.create(key);
                }
            };
    

    相关文章

      网友评论

          本文标题:LinkedHashMap源码浅析(三):LruCache浅解

          本文链接:https://www.haomeiwen.com/subject/xrajgftx.html