读源码(三)—— Android LruCache

Step1、示例

考虑用图片来模拟太麻烦,就弄个简单的类吧:

public class LruCacheTestActivity extends DefaultActivity {

    static final String tag = "lru";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                test();
            }
        });
    }

    class Node {
        int v1;
        int v2;
        double d1;
        double d2;

        public Node(int v1, int v2, double d1, double d2) {
            this.v1 = v1;
            this.v2 = v2;
            this.d1 = d1;
            this.d2 = d2;
        }

        int getSize() {
            return 24;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "v1=" + v1 +
                    ", v2=" + v2 +
                    ", d1=" + d1 +
                    ", d2=" + d2 +
                    '}';
        }
    }

    private void test() {
        int cacheSize = 100;
        LruCache<Integer, Node> lruCache = new LruCache<Integer, Node>(cacheSize) {
            @Override
            protected int sizeOf(@NonNull Integer key, @NonNull Node value) {
                return value.getSize();
            }

            @Override
            protected void entryRemoved(boolean evicted, @NonNull Integer key, @NonNull Node oldValue, @Nullable Node newValue) {
                Log.d(tag, "entryRemoved: " + evicted + ", " + key + ", " + oldValue + ", " + newValue);
            }
        };

        for (int i = 0; i < 4; i++) {
            lruCache.put(i, new Node(i, i, i, i));
        }

        lruCache.put(4, new Node(4, 4, 4, 4));
        lruCache.put(5, new Node(5, 5, 5, 5));

    }
}

结果输出:
2020-04-10 13:28:15.345 24018-24018/com.amurocloud.testapp D/lru: entryRemoved: true, 0, Node{v1=0, v2=0, d1=0.0, d2=0.0}, null
2020-04-10 13:28:15.345 24018-24018/com.amurocloud.testapp D/lru: entryRemoved: true, 1, Node{v1=1, v2=1, d1=1.0, d2=1.0}, null

Node里面随便塞了些数据,数据大小就是4 * 2 + 8 * 2 = 24(不考虑引用等其他内存占用,单纯为了测试),如果什么都不干的话,从输出可以看出最早进入缓存的2个元素被删除了,剩余4个的大小正好是24 * 4 = 96 < 100,符合预期。
但是我们修改下test方法,改成:

for (int i = 0; i < 4; i++) {
    lruCache.put(i, new Node(i, i, i, i));
}

lruCache.get(0);

lruCache.put(4, new Node(4, 4, 4, 4));
lruCache.put(5, new Node(5, 5, 5, 5));

结果输出:
2020-04-10 13:29:16.695 24142-24142/com.amurocloud.testapp D/lru: entryRemoved: true, 1, Node{v1=1, v2=1, d1=1.0, d2=1.0}, null
2020-04-10 13:29:16.695 24142-24142/com.amurocloud.testapp D/lru: entryRemoved: true, 2, Node{v1=2, v2=2, d1=2.0, d2=2.0}, null

可见0元素没有被删除,而其后的两个节点被删除了,cache内部还是4个元素。

其实这个实现的过程就是LruCache的缓存策略,即Lru–>(Least recent used)最少最近使用算法。
那LruCache具体如何实现这个功能的呢,我们开始看源码。

Step2、构造函数

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    //缓存实体就是个LinkedHashMap,注意最后一个参数传的true,看下面LHM的代码我们可以知道,这里创建的是一个按照最近访问顺序来排列的LHM
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

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

//LinkedHashMap核心调用的是HashMap的构造函数
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}

//LinkedHashMap覆写了HashMap的init方法,创建了一个头节点来记录顺序
@Override
void init() {
    header = new Entry<>(-1, null, null, null);
    header.before = header.after = header;
}

那么LinkedHashMap和普通HashMap的区别是什么呢,看下面一张图:
在这里插入图片描述
对比HashMap的话看得更明白:
在这里插入图片描述
就是比HashMap多了两个指针before,after,来标识元素的顺序关系,另外还多了个头节点来标识队首,很经典的设计啊~

Step3、增删改查

一、增加或修改元素

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);
        //如果已经存在的元素,把刚才加上去size还原
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
	
	//检查缓存容量的核心方法
    trimToSize(maxSize);
    return previous;
}

//其实就是个非负检查……
private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

//这个就是我们在示例里覆写的方法,告诉LruCache如何获取元素的大小
protected int sizeOf(K key, V value) {
    return 1;
}

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 || map.isEmpty()) {
                break;
            }
			// 得到的就是双向链表表头header的下一个Entry
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
		//回调给外部
        entryRemoved(true, key, value, null);
    }
}

整体来说添加元素的代码比较简单,就是每次添加完元素后检查空间,如果空间超出阈值就把队尾(最久没有被访问)的数据删除。
深入看下LinkedHashMap的put方法:

//LHM调用的也是HashMap的put方法,但是覆写了addEntry和createEntry方法
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //节点已经存在的的情况下,更新value
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            //LHM覆写
            e.recordAccess(this);
            return oldValue;
        }
    }
	//增加新节点
    modCount++;
    //LHM覆写
    addEntry(hash, key, value, i);
    return null;
}
//LHM覆写了HashMap的recordAccess方法,可以看到,如果设置了按访问顺序排序,当修改节点value时,会把这个元素从原来位置删除后插入到队尾。
void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();
        addBefore(lm.header);
    }
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    super.addEntry(hash, key, value, bucketIndex);

    // Remove eldest entry if instructed
    //这个方法默认返回false,所以外部不覆写的话,不用管
    Entry<K,V> eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    }
}

//HashMap的addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
	//扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}
//LHM覆写的createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMap.Entry<K,V> old = table[bucketIndex];
    Entry<K,V> e = new Entry<>(hash, key, value, old);
    table[bucketIndex] = e;
    e.addBefore(header);
    size++;
}
//Entry的addBefore方法,这里existingEntry传的是header,也就是把这个元素放入到了队尾
private void addBefore(Entry<K,V> existingEntry) {
    after  = existingEntry; // e.after = header
    before = existingEntry.before; //e.before = header.before
    before.after = this; // header.before.after = e
    after.before = this; //header.before = e
}

总结就是新元素除了会正常放入到hashmap外,还会插入到队列的队尾,如果设置了按照访问顺序排序,更新Value时也会执行同样的操作。

二、访问元素

public final V get(@NonNull K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }
	//LHM中存在key,拿出元素并返回,同时根据前面描述的LHM的机制,此时这个节点会被更新到队尾
    V mapValue;
    synchronized (this) {
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }
        missCount++;
    }

    /*
     * Attempt to create a value. This may take a long time, and the map
     * may be different when create() returns. If a conflicting value was
     * added to the map while create() was working, we leave that value in
     * the map and release the created value.
     */
	//这里设计了一个在key对应节点不存在时,允许用户自己创建一个新节点的回调,正常返回null,不影响主流程
    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;
    }
}

//LHM覆写了HashMap的get方法,核心就是上面分析过的recordAccess方法,在设置了按照访问顺序排序后,一旦被get就会被放到队尾中。
public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
}

三、删除元素

public final V remove(@NonNull K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V previous;
    synchronized (this) {
    	//实际调用的是HashMap的remove方法
        previous = map.remove(key);
        //删除元素存在时,更新容量
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }

    return previous;
}

//HashMap的remove方法
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}

final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;

    while (e != null) {
        Entry<K,V> next = e.next;
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)
                table[i] = next;
            else
                prev.next = next;
            //LHM覆写了这个方法做队列的调整    
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}
//LHM覆写的方法
void recordRemoval(HashMap<K,V> m) {
    remove();
}
//remove方法在Entry里,就是把被remove节点的前后连上
private void remove() {
    before.after = after; //removeElement.before.after = remove.after
    after.before = before; //removelement.after.before = remove.before
}

就这些,代码不多,核心还是LinkedHashMap。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值