LRUCache

LRUCache是Android中实现内存缓存相关的组件类,当缓存满时其使用最近最少使用策略来淘汰相关的元素,以控制缓存大小。本文主要基于LRUCache相关源码分析LRUCache的创建、缓存的添加、获取、删除流程。

LRUCache创建

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

其创建时需要设置最大的缓存大小,此外会创建一个LinkedHashMap来存储缓存对象的引用。
当我们使用LRUCache时一般如下:

	int cacheSize = 4 * 1024 * 1024; // 4MiB   
	LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap(cacheSize) {protected int sizeOf(String key, Bitmap value) {           
		return value.getByteCount();       
	}   
}

添加缓存

添加缓存调用LRUCahe的put(key,value)方法,put方法先通过sizeof函数计算当前待添加节点所占内存大小,然后将其添加到map中,重新计算当前缓存大小,如果旧节点非空,则调用entryRemove方法通知该旧节点已移除,以方便使用者做清理操作。最后再调用trimToSize()方法检查并移除已超过maxSize的最老的节点。

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

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

获取缓存

获取缓存调用LRUCache的get方法,其会根据key去map中查找对应值,如果没有,则会调用create方法尝试创建默认值,如果创建了默认值,则会返回默认值,并重新走一遍trimToSize,保证内存缓存大小在maxSize限制内。

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

        /*
         * 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.
         */

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

删除缓存

删除缓存主要涉及三件事:

  1. 从map中删除该节点
  2. entryRemove方法通知使用者该节点已被移除
  3. 重新计算当前缓存的大小
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;
    }

LinkedHashMap如何实现LRU算法

LinkedHashMap继承于HashMap,同时内部自定义了LinkedHashMapEntry节点形成了双向链表结构,其内部有head和tail两个指向头和尾节点的指针,通过accessOrder变量来控制是否顺序。

	static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
        LinkedHashMapEntry<K,V> before, after;
        LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
	transient LinkedHashMapEntry<K,V> head;
    transient LinkedHashMapEntry<K,V> tail;
    //true表示按照访问顺序,false表示按照插入顺序
    final boolean accessOrder;

那么如何LinkedHashMap如何更新节点顺序呢?我们看下get方法的流程就知道了。

	public V get(Object key) {
        Node<K,V> e;
        //调用父类HashMap的getNode方法获取key对应的节点,该方法会根据hash去对应的桶内查找key相等的节点
        if ((e = getNode(hash(key), key)) == null)
            return null;
            //如果accessOrder=true,则表示要按照访问顺序对节点排序
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

get方法做了两件事:

  1. 调用父类HashMap的getNode方法获取key对应的节点
  2. 如果获取到节点值且accessOrder=true,则调用afterNodeAccess方法,该方法会对节点排序

接下来看下afterNodeAccess方法

	void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMapEntry<K,V> last;
        //如果tail节点不等于当前节点
        if (accessOrder && (last = tail) != e) {
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
                //将p节点从链表中删除
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            //更新tail节点指向p,即当前p变成了尾节点,即最新使用的节点,当LRUCache中大小超过maxSize时,会调用eldest方法找到最近最少使用的节点。实际上就是返回head节点
            tail = p;
            ++modCount;
        }
    }
    
	public Map.Entry<K, V> eldest() {
        return head;
    }
  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值