基本原理
LinkedHashMap继承自HashMap,因此具有HashMap的所有特性。在HashMap的基础上,保持了key的插入顺序或者访问顺序。实现方法就是用双向循环链表将所有的entry链接起来,读取数据的时候直接读取此双向循环链表的数据即可。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map:
* true for access-order,false for insertion-order.
*/
private final boolean accessOrder;
header是双向循环链表的头结点,不存储数据,用于定位头尾节点
header.after指向链表的第一个节点
header.before指向链表的最后一个节点
accessOrder是一个标志位,true的话表示按照访问顺序访问链表,可据此构建LRU缓存,fasle的话表示按照插入顺序访问链表
双向循环链表的Entry节点
LinkedHashMap的Entry继承自HashMap的Entry
private static class Entry<K,V> extends HashMap.Entry<K,V>
并添加了指向前序节点的前序指针和指向后继节点的后继指针
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
因此LinkedHashMap的Entry节点具有三个指针域,next指针维护Hash桶中冲突key的链表,before和after维护双向循环链表
为了维护双向循环链表,Entry新增加了4个方法
删除节点remove
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
充分体现了链表这种数据结构删除中间节点的方便之处,仅仅修改指针的指向即可。
如果要删除当前节点,就把当前节点的前序节点的后继指针指向当前节点的后继节点,当前节点的后继节点的前序指针指向当前节点的前序节点即可,这样当前节点就从链表中脱离开了,断开的节点也得到重新链接。
添加节点
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
添加节点同样是指针操作,非常高效方便
记录访问recordAccess
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
当LinkedHashMap的标志位accessOrder为true时,标志着要采用访问顺序访问链表。
remove(); addBefore(lm.header);
最新访问的节点先从原来的位置删除,然后重现添加到链表的末尾,这样最近最少访问的节点就被挪到了链表的前端。
LinkedHashMap添加数据
LinkedHashMap本质上也是一个HashMap,在HashMap的基础上添加所有entry构成双向循环链表的功能。因此LinkedHashMap并没有覆写HashMap的put方法,只是覆写了HashMap中put方法调用的addEntry方法。
先来回顾下HashMap的put方法够干了什么
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
HashMap的put方法主要功能是计算对应key的桶的位置,遍历桶中链表,找到对应key的entry修改原数据。遍历链表结束没有找到对应key的entry则调用addEntry方法将新添加的键值对entry添加到桶中entry链表的头部。
而对于LinkedHashMap中的entry节点来说,要维持两个链表,一个是桶中的next指针域链接的hask冲突的key构成的entry单链表,第二个就是维护所有entry构成的双向循环链表
LinkedHashMap的addEntry方法如下所示
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
先调用createEntry把新节点链接到两条链表上,然后判断是否要删除最近最久未访问的节点(也就是双向循环链表的第一个节点),要删除的话就不需要检查是否需要扩容了,都则要检查是否需要扩容。
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
可见新节点被两条链表都链接上了
第一条是单项非循环链表,桶中hash值冲突得key构成的entry链表
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
第二条是所有entry节点构成的双向循环链表
e.addBefore(header);
containsValue方法直接在双向循环链表中查找值
public boolean containsValue(Object value) {
// Overridden to take advantage of faster iterator
if (value==null) {
for (Entry e = header.after; e != header; e = e.after)
if (e.value==null)
return true;
} else {
for (Entry e = header.after; e != header; e = e.after)
if (value.equals(e.value))
return true;
}
return false;
}
根据传入参数value是否为null,对双向循环链表进行遍历查找