LinkedHashMap是基于哈希算法,以键值对的形式存储和操作数据的非线程安全容器,继承于HashMap,在HashMap的基础上增加了双链表来支持插入顺序遍历,除此之外对操作顺序遍历也提供了支持,可用于实现LRU缓存
与HashMap一样在无哈希冲突情况下时间复杂度也能达到O(1),但由于双链表的维护,性能会稍低一些
LinkedHashMap在实现上很多方法直接继承HashMap,仅为维护双链表重写了一些方法,因此本文不介绍与HashMap相同的内容,有需要的可以看HashMap原理
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;
LinkedHashMap相比HashMap增加了双链表的实现,使它能够支持键值插入顺序排序
final boolean accessOrder;
accessOrder是操作顺序排列的标志位,当在构造函数中指定该标志位时,双链表就会将当前操作的节点移出后插入链表尾部,来实现操作顺序排列
void afterNodeRemoval(Node<K,V> e) { // unlink
...
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest
...
}
void afterNodeAccess(Node<K,V> e) { // move node to last
...
}
HashMap中定义了三个空方法,而在LinkedHashMap中重写了这三个方法,这三个方法是专门留给LinkedHashMap用于维护双链表
构造函数不管有参无参都会调用父类HashMap的构造,不指定accessOrder时默认为false,表示按照插入顺序排序,否则就是操作顺序排序
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkedHashMap没有重写remove方法,而是直接使用父类的实现,父类最后会回调子类重写的afterNodeRemoval方法
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
...
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
...
// 回调子类方法
afterNodeRemoval(node);
return node;
}
}
return null;
}
afterNodeRemoval方法用于实现双链表中的删除节点操作,如果待删除的节点是首节点或尾节点都会做相应处理
void afterNodeRemoval(Node<K,V> e) { // unlink
// 将当前节点转为LinkedHashMap的节点,并获得该节点的前节点和后节点
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;
}
LinkedHashMap中重写了get方法,但也调用了父类的getNode方法,最后如果按操作顺序排列则调用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;
}
afterNodeAccess方法用于将节点移动到双链表的尾部,节点若是首尾节点都会做相应处理
void afterNodeAccess(Node<K,V> e) { // move node to last
// 定义最近操作节点
LinkedHashMap.Entry<K,V> last;
// 如果不是操作顺序排列或节点已经是尾节点则无需再做处理
// 将尾节点设为最近操作节点
if (accessOrder && (last = tail) != e) {
// 将当前节点转为LinkedHashMap的节点,并获得该节点的前节点和后节点
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;
}
}
put方法LinkedHashMap同样没有重写,也是直接使用父类的put方法,但是调用newNode时会回调子类的newNode实现,当出现哈希冲突替换key相同节点时会回调子类的afterNodeAccess方法将该节点移动到双链表尾部,以及最后会回调子类的afterNodeInsertion方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
...
if ((p = tab[i = (n - 1) & hash]) == null)
// 回调子类newNode方法
tab[i] = newNode(hash, key, value, null);
else {
...
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 回调子类afterNodeAccess方法
afterNodeAccess(e);
return oldValue;
}
}
...
// 回调子类afterNodeInsertion方法
afterNodeInsertion(evict);
return null;
}
LinkedHashMap重写的newNode方法最终会调用linkNodeLast方法,将节点插入双链表尾部
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;
}
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;
}
}
afterNodeAccess方法用于移除最近最少被操作过的节点,自定义LRU缓存就可以通过将LinkHashMap设为按操作顺序排序并重写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方法
removeNode(hash(key), key, null, false, true);
}
}
https://segmentfault.com/a/1190000012964859
https://segmentfault.com/a/1190000021434024