知乎:zhuanlan.zhihu.com/p/77152958
LinkedHashMap 简介
- 定义: LinkedHashMap 是根据插入或访问顺序实现有序输出的HashMap
- 数据结构 : HashMap + 双向链表
- 使用示例:
//普通HashMap
Map<Integer, String> hashMap = new HashMap<Integer, String>();
hashMap.put(3, "order3");
hashMap.put(1, "order1");
hashMap.put(2, "order2");
hashMap.forEach((key, value) -> System.out.println(key + "-->" + value));
输出结果:
1-->order1
2-->order2
3-->order3
//迭代顺序和插入顺序保持一致
Map<Integer, String> linkedHashMap = new LinkedHashMap<Integer, String>();
linkedHashMap.put(3, "order3");
linkedHashMap.put(1, "order1");
linkedHashMap.put(2, "order2");
linkedHashMap.forEach((key, value) -> System.out.println(key + "-->" + value));
输出结果:
3-->order3
1-->order1
2-->order2
//迭代顺序和查询顺序保持一致
Map<Integer, String> linkedHashMap = new LinkedHashMap<Integer, String>(16, 0.75f, true);
linkedHashMap.put(3, "order3");
linkedHashMap.put(1, "order1");
linkedHashMap.put(2, "order2");
linkedHashMap.get(3);
linkedHashMap.forEach((key, value) -> System.out.println(key + "-->" + value));
输出结果:
1-->order1
2-->order2
3-->order3
// 插入和删除元素后不影响迭代顺序
Map<Integer, String> linkedHashMap = new LinkedHashMap<Integer, String>(16, 0.75f, true);
linkedHashMap.put(3, "order3");
linkedHashMap.put(1, "order1");
linkedHashMap.put(2, "order2");
linkedHashMap.get(3);
linkedHashMap.remove(1);
linkedHashMap.put(4, "order4");
linkedHashMap.forEach((key, value) -> System.out.println(key + "-->" + value));
输出结果:
2-->order2
3-->order3
4-->order4
- 使用场景: LRU 缓存过期策略 (通过 LinkedHashMap 的访问顺序实现)
LinkedHashMap 实现
//扩展一个双向链表的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;
- 过构造函数掺入accessOrder值控制插入顺序或访问顺序存储
//LinkedHashMap的构造函数大多数默认存储顺序为插入顺序accessOrder=false,通过下面的构造函数可以自由设置accessOrder的值,为true时存储顺序为访问顺序
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
- 初始化时会调用HashMap的初始化方法,并初始化一个空链表
//重写HashMap 的reinitialize方法,初始化一个空链表
void reinitialize() {
super.reinitialize();
head = tail = null;
}
//直接复用HashMap的put方法,但是重写了newNode 和 newTreeNode方法
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;
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
//将插入的节点放入链表尾部
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;
}
}
//插入之后如果需要移除最老的节点,LinkedHashMap重写了afterNodeInsertion方法,但是可以看到removeEldestEntry这个方法直接返回的false,因此如果要生效泽应该重写removeEldestEntry该方法
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;
}
- 访问(当accessOrder=true时,通过afterNodeAccess重排数据,把访问节点移动到链表尾部,实现通过访问顺序输出)
//重写HashMap get方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// 如果 accessOrder 为 true,则调用 afterNodeAccess,将被访问节点移动到链表最后
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
//重写 HashMap afterNodeAccess,将被访问的节点移动到链表最后
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
//如果 accessOrder 为 true, 则将被访问节点移动到链表最后
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;
}
}
- LinkedHashMap 重写了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 的前置后置节点都置空
p.before = p.after = null;
//如果b为空,p就是头节点,删除之后a就是头节点,否则将前置节点b的后置节点指向a
if (b == null)
head = a;
else
b.after = a;
//如果a为空,那么p就是尾节点,删除之后,b就是新的尾节点,否则a的前置节点就改为b
if (a == null)
tail = b;
else
a.before = b;
}
- LinkedHashMap的迭代其实就是双向链表的迭代
//自定义LinkedHashIterator迭代器
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
// 保证从链表的头开始扫描
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
问答环节
1、@mackong 问: afterNodeAccess 方法中的 if(accessOrder&&(last=tail)!=e) 判断的作用是?
答: 此处 accessOrder 的判断是私有方法的二次确认判断,放置外部未判断从而导致破坏顺序.
2、@羊大王 问: afterNodeAccess 方法执行之后是否会改变 HashMap 的结构?
答: LinkedHashMap 在 HashMap 的基础上维护了一个双向链表,因此 afterNodeAccess 方法只会调整链表的结构.