一、声明
摘自jdk1.8
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
二、概述
LinkedHashMap继承了HashMap,在HashMap基础上,通过维护一个双向链表来保证迭代顺序与插入顺序一致,也可以通过指定参数accessOrder=true,来保证迭代顺序为访问顺序(put、get都算访问)。
Map<String,String> map = new LinkedHashMap<String, String>();
map.put("k1","1");
map.put(null,"null");
map.put("k2","2");
map.put("k3","3");
map.put("k4","4");
for(Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
输出结果:
k1: 1
null: null
k2: 2
k3: 3
k4: 4
LinkedHashMap有两个属性head,tail,是双向链表的头节点和尾节点,其节点是LinkedHashMap.Entry,继承HashMap.Node,before和after分别代表链表中前一个节点引用和后一个节点引用。
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);
}
}
LinkedHashMap覆盖了HashMap的newNode,newTreeNode方法,调用put方法时会将HashMap.Node换为LinkedHashMap.Entry(put方法详情可查看上一篇 java8-HashMap源码分析),从而构建出一条双向链表。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
内存中的存储结构如下:
三、重要方法
HashMap中有三个空实现的方法:
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
LinkedHashMap对这三个方法做了具体实现,从注释和方法名字可以看出它们是 节点访问后、节点插入后、节点移除后的回调方法。
1.afterNodeAccess()
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//如果是按访问顺序排序,且当前节点不是尾节点,则将当前节点移动到末尾
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
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;
++modCount;
}
}
2.afterNodeInsertion()
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);
}
}
removeEldestEntry方法默认是返回false,如果自定义溢出规则,则可以将最近最少访问的节点移除,这就是一个经典的LRU置换算法的实现。
//代码摘自mysql jdbc包
package com.mysql.jdbc.util;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
public class LRUCache extends LinkedHashMap {
private static final long serialVersionUID = 1L;
protected int maxElements;
public LRUCache(int maxSize) {
super(maxSize);
this.maxElements = maxSize;
}
protected boolean removeEldestEntry(Entry eldest) {
return this.size() > this.maxElements;
}
}
3.afterNodeRemoval()
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//执行remove操作后,将链表中对应节点也删除
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
4.put、get方法
LinkedHashMap并未覆盖put方法。get方法则重新实现并加入了afterNodeAccess来保证访问顺序。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
这里要特别注意,当设置按访问顺序排序时,调用get方法也会更新链表,因此以下代码迭代会抛出ConcurrentModificationException,应当使用entrySet迭代。
for(Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();){
String name = iterator.next();
System.out.println(name+":"+map.get(name));
}
结果:
k1:1
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)
at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:742)
at hub.UserVo.main(UserVo.java:32)
四、总结
- LinkedHashMap继承HashMap,可以按插入顺序排序,也可以通过设置参数accessOrder=true,按访问顺序排序,其内部通过维护一个双向链表来保证迭代顺序;
- 覆盖了afterNodeAccess、afterNodeInsertion、afterNodeRemoval三个方法;
- 通过指定accessOrder=true,并覆盖removeEldestEntry方法自定义溢出规则可以实现LRUCache。