您的位置:首页 > 编程语言 > Java开发

jdk源码分析之LinkedHashMap

2016-06-02 11:11 721 查看

基本原理

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,对双向循环链表进行遍历查找
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jdk