LinkedHashMap继承了HashMap,在hashmap的哈希表的基础上维护了一个双向循环链表。是哈希表和链表两种数据结构的结合体。
LinkedHashMap添加了两个重要的属性:LinkedHashMap.Entry类型的header–双向链表的头;boolean类型的accessOrder,用于确定链表是以访问顺序排序还是以插入顺序排序。
LinkedHashMap中的内部类Entry类,继承自HashMap.Entry类,添加了指向链表中前后元素的两个引用。
构造器
LinkedHashMap使用父类构造器进行构造,然后添加了对accessOrder的初始化和对header的初始化工作。
LinkedHashMap在HashMap的操作的基础上添加了链表中元素的引用关系的维护操作,以及按需进行元素顺序的维护操作。
基础设施
LinkedHashMap中关于链表部分的操作都是围绕其内部类Entry来展开的。用remove()和addBefore()维护添加或删除链表元素时元素之间的引用关系;如果需要按访问顺序排列链表,则用recordAccess()和recordRemoval()来动态维护链表的元素顺序。
/**
* LinkedHashMap 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;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* 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;
}
/**
* 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;
//如果LinkedHashMap定义为访问顺序的链表,那么修改modCount,将该元素移至链表的尾端。如果是插入顺序,则什么也不做(不进行顺序的调整)
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
重写的方法
addEntry():
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
//添加了对用作缓存时移出链表最老元素的支持
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
//移除最老元素(访问最少的元素)
removeEntryForKey(eldest.key);
}
}
createEntry():
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
//维护链表的前后引用关系,将新增的Entry添加至链表尾部
e.addBefore(header);
size++;
}
对于父类的put方法,1、如果哈希表中有相同的key,那么会调用recordAccess方法;2、如果哈希表中没有,新增Entry的话,会调用重写过的createEntry()方法,在添加新元素的同时,维护链表元素引用关系。注意:createEntry()方法是同时满足访问顺序和插入顺序的。所以不需要额外的调用recordAccess方法。
get():
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
//添加了顺序调整
e.recordAccess(this);
return e.value;
}
containsValue和transfer
因为有所有元素的链表,所以元素的遍历相比父类而言需要访问的地址少。(hashmap需要访问整个capacity,而linkedhashmap只需要访问size。)
总结
LinkedHashMap通过继承HashMap和重写其部分方法,并增加了Entry的属性,给哈希表里的元素附上了链表数据结构,赋予了hashmap链表的功能。
通过accessOrder属性值true,和新的Entry的recordAccess方法,加上对父类createEntry方法的重写,使linkedhashmap拥有了维护访问顺序的能力。其关键就是在每次访问(put(包括新增元素)/get)某个元素时,调整该元素的位置至链表尾端。所以该链表具有访问顺序排序。
如果accessOrder为false(默认值),则每次访问元素(包括新增元素)只是添加元素至链表尾端,不做其他操作,即为插入顺序。
访问顺序的一个典型应用就是LRU的实现。
另外通过对removeEldestEntry方法提供实现,可以将linkedhashmap作为缓存来实现。
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
当调用addEntry()方法时,如果size超限,那么最老的entry将被移除。