LinkedHashMap源码 JDK1.8
第一步就是看看类继承实现图。它继承了HashMap,这就意为着拥有了和HashMap一样的特性。
底层是HashMap的散列表+双向链表。多出来的双向链表带来了新的特性:
- 可以保证插入的顺序,插入是a-》b-》c 遍历输出也是a-》b-》c
- 提供了LRU策略,最近最少使用。在这里是将访问元素之后将该元素插入到链表的末尾,这样操作下来头结点就是最近最少访问的元素
结构
//属性
//双向链表头尾指针
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
//一个限定值,限制是否开启LRU策略,默认false 不开启
final boolean accessOrder;
//构造方法
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);//可以看到直接调用了父类的构造方法
accessOrder = false;//默认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);
}
//这个构造方法可开启LRU策略,也只有在这里可以开启
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
//结点Entry
//继承了HashMap的Node结点,Node结点只有next是个单链表,但是Entry增加了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);//用了父类的构造参数
}
}
常用方法
一个类或者说容器的使用,对于使用者来说关注的还是怎么crud
增加元素
在LinkedHashMap中无此方法,直接调用的是HashMap的put方法。此方法我在HashMap源码分析中看过
之前看的时候有方法在HashMap中是空实现,说是在LinkedHashMap的LRU策略的实现
还有newNode方法被重写了,新加入的结点总是在双向链表末尾
//hashMap中是
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
//LinkedHashMap是
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;
}
//此方法保证在当前桶是null的时候,也会置于链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)//第一个结点则直接让head=p=tail
head = p;
else {
p.before = last;
last.after = p;
}
}
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 ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //在这里
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //在这里
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict); //这里
return null;
}
//这个方法在当前桶有hash冲突时且是存在相同key时,保证结点会在双向链表的尾部
void afterNodeAccess(Node<K,V> e) { // move node to last 移动结点到尾部
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {//限定值在这里判断 为true且当前结点不在尾部
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;
}
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest 淘汰最近最少使用的结点
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) { //还有一个限定值 evict 默认是true。
K key = first.key; //用户要是想开启此策略 还需重写removeEldestEntry(first)此方法,比如让结点个数大于3时才删除最近最少使用结点。
//此方法默认返回false 也就是不开启
removeNode(hash(key), key, null, false, true);
}
}
总的描述:
不开启LRU策略和HashMap put值一样,但是会保证顺序
开启就会将新加入结点移到链表尾部,或者描述为操作过的结点。因为还有修改值。
一个疑问?当桶产生hash冲突时,怎么保证顺序?
人傻了 还有这个呢 p.next = newNode(hash, key, value, null);
查找元素
//前一个if和hashmap差不多,后面的是LRU策略,操作过的结点放到链表尾部
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;
}
删除元素
直接调用的是父类的remove
在LinkedHashMap中实现了afterNodeRemoval(node);方法
//取消双链表中的链接
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;
}
不想看了 之后看着补充。。。
学习参考:https://yzhyaa.blog.csdn.net/article/details/108512608
https://github.com/hpmmp/Java_collections/blob/master