1.介绍
LinkedHashMap
是继承于HashMap
,是基于HashMap
和双向链表
来实现的HashMap
无序,LinkedHashMap
的元素是有序的,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)LinkedHashMap
同样也是非线程安全的LinkedHashMap
可以用来实现LRU
算法
2.LinkedHashMap的属性
// 静态内部类Entry继承自HashMap的Node,在其基础上加上了before和after两个指针实现双向链表特性
// 这两个属性保存前后节点的引用,将哈希表中所有的Entry贯穿起来
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
// 链表头结点
transient LinkedHashMap.Entry<K,V> head;
// 链表尾结点
transient LinkedHashMap.Entry<K,V> tail;
// 这里是指是否基于访问排序,默认为false
// 为true时,被访问的元素会被放到链表尾结点
// 为false时,按插入的顺序排序
final boolean accessOrder;
3.重要函数
afterNodeAccess、afterNodeInsertion、afterNodeRemoval这三个钩子函数,在父类HashMap中只是空实现,LinkedHashMap进行了重写,实现双向链表的维护
3.1 afterNodeAccess函数
把当前节点放到双向链表尾部,此函数在很多函数(如get,put
)中都可能会被回调,LinkedHashMap
重写了HashMap
中的此函数。若访问顺序为true,且访问的对象不是尾结点
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
/* accessOrder就是我们前面说的LRU控制,当它为true,同时e对象不是尾节点
(如果访问尾节点就不需要设置,该方法就是把节点放置到尾节点)
*/
if (accessOrder && (last = tail) != e) {
// 用a和b分别记录该节点前面和后面的节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 释放当前节点与后节点的关系
p.after = null;
// 如果当前节点的前节点是空
if (b == null)
// 那么头节点就设置为a
head = a;
else
// 如果b不为null,那么b的后节点指向a
b.after = a;
// 如果a节点不为空
if (a != null)
// a的后节点指向b
a.before = b;
else
// 如果a为空,那么b就是尾节点
last = b;
// 如果尾节点为空
if (last == null)
// 那么p为头节点
head = p;
else {
// 否则就把p放到双向链表最尾处
p.before = last;
last.after = p;
}
// 设置尾节点为P
tail = p;
// LinkedHashMap对象操作次数+1
++modCount;
}
}
从上面的效果图中可以看到,结点3链接到了尾结点后面
3.2 afterNodeInsertion函数
在哈希表中插入了一个新节点时会调用afterNodeInsertion
函数,而afterNodeInsertion
函数调用了removeEldestEntry
,如果返回true
则删除链表的头节点。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
removeEldestEntry
也只是简单的返回false
,换言之LinkedHashMap
新增元素后默认是不会删除表头元素的。
可以通过重写removeEldestEntry
方法,实现一个最简单的LRU缓存
,代码如下:
Map<String, String> map = new LinkedHashMap<String, String>(7, 0.75F, true){
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
//当LinkHashMap的容量大于等于5的时候,再插入就移除旧的元素
return this.size() >= 5;
}
};
for (int i = 0; i < 10; i++) {
map.put(i, i);
}
System.out.println("map size is:"+map.size());
map.entrySet().iterator().forEachRemaining(System.out::println);
输出结果
map size is:5
5=5
6=6
7=7
8=8
9=9
3.2 afterNodeRemoval函数
当HashMap删除结点时调用的,afterNodeRemoval
会把这个节点一并从链表中删除,保证了哈希表和链表的一致性
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}