Java集合系列源码分析(四)--LinkedHashMap
简介
LinkedHashMap继承自HashMap,LinkedHashMap中的多种操作都是基于HashMap的,跟HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了Entry的插入顺序和访问顺序。
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);
}
}
构造函数
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
从LinkedHashMap的构造方法可以看出来最终还是调用的HashMap中创建对象的方法,不过LinkedHashMap多了一个accessOrder,如果accessOrder未指定则默认为false,当accessOrder为false的时候则按照插入的顺序排序,如果为true的时候则按照访问的顺序排序,当访问某个节点的时候都会将该节点放置到链表的尾部。
虽然LinkedHashMap没有重写HashMap的put方法,但是重写了put方法中调用的其它方法newNode、replacementNode、newTreeNode、replacementTreeNode、afterNodeAccess、afterNodeInsertion。
newNode方法
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;
}
}
先看上面的图片中未添加新的节点时,head头节点指向了节点张三,而tail尾节点指向了节点李四,节点张三的after指针指向了节点李四,节点李四的before指针指向了节点张三,当添加了一个新的节点王五之后,节点李四的after指针指向了王五,王五的before指针指向了李四,tail尾节点指针指向了王五。
replacementNode方法
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
//将树节点转换为链表节点
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
//创建一个新的链表
LinkedHashMap.Entry<K,V> t = new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
//转移指针
transferLinks(q, t);
return t;
}
/**
* 转移源节点的指针到目标节点中去
* @param src 源节点
* @param dst 目标节点
*/
private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) {
//获取源节点的before指针指向的节点并将目标节点指向该节点
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
//获取源节点的after指针指向的节点并将目标节点指向该节点
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
if (b == null)
//源节点的before指针指向的位置为null则将目标节点设置为头节点
head = dst;
else
//源节点的before指针指向的节点的after指针指向目标节点
b.after = dst;
if (a == null)
//源节点的after指针指向的位置为null则将目标节点设置为尾节点
tail = dst;
else
//源节点的after指针指向的节点的before指针指向目标节点
a.before = dst;
}
replacementNode方法则是将红黑树中的节点转换为双向链表,上面为什么创建一个新的LinkedHashMap,个人理解是红黑树中的指针太多创建一个新的LinkedHashMap来接收双向链表的指针,红黑树中的指针有next、prev、left、right、parent、after、before。
下面图片中则是将红黑树转换为双向链表的过程,原红黑树中的指针转移完成之后,红黑树中的节点没有被引用则会被GC回收。
newTreeNode方法
newTreeNode方法创建一个新的树节点并将该节点添加到双向链表的尾部。
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;
}
}
replacementTreeNode方法
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
//将普通节点转换为链表节点
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
//创建一个新的树节点
TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
//转移指针
transferLinks(q, t);
return t;
}
private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) {
//获取源节点的before指针指向的节点并将目标节点指向该节点
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
//获取源节点的after指针指向的节点并将目标节点指向该节点
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
if (b == null)
//源节点的before指针指向的位置为null则将目标节点设置为头节点
head = dst;
else
//源节点的before指针指向的节点的after指针指向目标节点
b.after = dst;
if (a == null)
//源节点的after指针指向的位置为null则将目标节点设置为尾节点
tail = dst;
else
//源节点的after指针指向的节点的before指针指向目标节点
a.before = dst;
}
afterNodeAccess方法
afterNodeAccess方法将访问到的节点放置到链表的尾部
void afterNodeAccess(Node<K,V> e) {
//尾节点
LinkedHashMap.Entry<K,V> last;
//校验是否开启了访问顺序排序并且当前替换值的节点不是尾节点
if (accessOrder && (last = tail) != e) {
//p 将当前访问到的节点转换为链表节点
//b 当前访问到的节点的前一个节点
//a 当前访问到的节点的后一个节点
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//将当前访问到的节点的after指针置为null
p.after = null;
if (b == null)
//当前访问到的节点的before指针为null则将当前访问到的节点的after指针指向的节点设置为头节点
head = a;
else
//将当前访问到的节点的before和after指针指向的节点进行关联
b.after = a;
if (a != null)
//将当前访问到的节点的before和after指针指向的节点进行关联
a.before = b;
else
//当前访问到的节点的after指针指向的节点为null则将before指针指向的节点设置为尾节点
last = b;
if (last == null)
//将当前访问到的节点设置为头节点
head = p;
else {
//将当前访问到的节点与原尾节点进行关联
p.before = last;
last.after = p;
}
//将当前访问到的节点设置为尾节点
tail = p;
++modCount;
}
}
afterNodeInsertion方法
void afterNodeInsertion(boolean evict) {
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;
}
afterNodeInsertion方法则是一个lru算法,在每次添加一个新的节点的时候是否删除最近最少使用的节点,前提是必需开启了访问顺序排序,开启了访问顺序排序则每次访问节点的时候都会将节点放置双向链表的尾部,这样头部的节点就是最少访问的节点,从LinkedHashMap中的方法来看是不会删除最近最少使用的节点,因为removeEldestEntry方法永远返回的是false。
afterNodeRemoval方法
afterNodeRemoval则是在删除节点的时候将该节点从双向链表中删除
void afterNodeRemoval(Node<K,V> e) {
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;
}
get方法
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;
}
linkedHashMap重写了get方法,在获取key对应的value的时候如果开启了访问顺序排序则会调用afterNodeAccess方法将key所在的节点放置到双向链表的尾部。
转载自:https://juejin.cn/post/7172904518030639135