LinkedHashMap和LRU
刚好正在研究Android中LruCache缓存,它的实现其实也是使用了LinkedHashMap,所以今天就专门写博客记录一下相关知识。
存储结构
LinkedHashMap实际上是使用HashMap+双向链表,有关HashMap的详细知识就请看之前相关博客HashMap源码分析。我们知道HashMap是以散列表的形式存储数据的,LinkedHashMap继承HashMap,所以它也是使用散列表存储数据,但是,会有额外的“Linked”双向链表把所有的数据连接起来。为什么要这样做?HashMap是无序的,而加上双向链表,就将所有数据有序管理起来。具体如下图:
在HashMap的基础上多了befor和after字段,用来形成双向链表。
两个例子
LinkedHashMap的核心就是存在存储顺序和可以实现LRU算法,所以下面我用两个例子证明这两种情况:
存储顺序
1 | public class LinkedHashMapTest { |
观察以上代码,其实它是符合先进先出的规则的,不管你怎么查询插入已存在的数据,不会对排序造成影响,如果有新插入的数据将会放在最尾部。
LRU
启用LinkedHashMap的LRU规则是要使用它的三个参数的构造方法。
1 | /** |
1 | public class LinkedHashMapTest { |
从上面可以看出,每当我get或者put一个已存在的数据,就会把这个数据放到双向链表的尾部,put一个新的数据也会放到双向链表的尾部。
实现原理
构造函数
1 | public LinkedHashMap(int initialCapacity, float loadFactor) { |
5个构造函数,可以设置容量和加载因子,且默认情况下是不开启LRU规则。
双向链表
1 | /** |
LRU实现
1 | void afterNodeAccess(Node<K,V> e) { // 把当前节点e放到双向链表尾部 |
开启LRU后,put,get等方法都会调用这个函数来调整顺序。
1 | public V get(Object key) { |
移除Eldest
1 | protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { |
LinkedHashMap有一个自带的移除最老数据的方法,默认返回false,我们可以在继承的时候重写这个方法,给定一个条件就可以控制存储在LinkedHashMap中的最老数据何时删除。触发这个删除机制,一般是在PUT一个数据进入的时候,但是LinkedHashMap并没有重写Put方法如何实现呢?在LinekdHashMap中,这个方法被包含在afterNodeInsertion()方法之中,而这个方法是重写了HashMap的,但是HashMap中并没有去实现它,所以在put的时候就会触发删除这个机制。