原文:http://zy19982004.iteye.com/blog/1663303
一.LinkedHashMap的存儲結構
- LinkedHashMap是繼承HashMap,也就繼承了HashMap的結構,也就是圖中的結構2,在下文中我用"Entry數組+next鏈表"來描述。而LinkedHashMap有其自己的變量header,也就是圖中的結構1,下文中我用"header鏈表"來描述。
- 結構1中的Entry和結構2中的Entry本是同一個,結構1中應該就只有一個header,它指向的是結構2中的e1 e2,但這樣會使結構圖難畫。爲了說明問題的方便,我把結構2裏的e1 e2在結構1中多畫一個。
二.LinkedHashMap成員變量
- // LinkedHashMap維護了一個鏈表,header是鏈表頭。此鏈表不同於HashMap裏面的那個next鏈表
- private transient Entry<K, V> header;
- // LRU:Least Recently Used最近最少使用算法
- // accessOrder決定是否使用此算法,accessOrder=true使用
- private final boolean accessOrder;
// LinkedHashMap維護了一個鏈表,header是鏈表頭。此鏈表不同於HashMap裏面的那個next鏈表
private transient Entry<K, V> header;
// LRU:Least Recently Used最近最少使用算法
// accessOrder決定是否使用此算法,accessOrder=true使用
private final boolean accessOrder;
三.LinkedHashMap裏的Entry對象
- // 繼承了HashMap.Entry,其他幾個方法邊用邊分析
- rivate static class Entry<K, V> extends HashMap.Entry<K, V> {
- // 增加了兩個屬性,每個Entry有before Entry和after Entry,就構成了一個鏈表
- Entry<K, V> before, after;
- Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {
- super(hash, key, value, next);
- }
- private void addBefore(Entry<K, V> existingEntry) {
- .....
- }
- void recordAccess(HashMap<K, V> m) {
- .....
- }
- void recordRemoval(HashMap<K, V> m) {
- .....
- }
- private void remove() {
- .....
- }
// 繼承了HashMap.Entry,其他幾個方法邊用邊分析
private static class Entry<K, V> extends HashMap.Entry<K, V> {
// 增加了兩個屬性,每個Entry有before Entry和after Entry,就構成了一個鏈表
Entry<K, V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {
super(hash, key, value, next);
}
private void addBefore(Entry<K, V> existingEntry) {
.....
}
void recordAccess(HashMap<K, V> m) {
.....
}
void recordRemoval(HashMap<K, V> m) {
.....
}
private void remove() {
.....
}
}
四.構造函數
- //默認accessOrder爲false
- //調用HashMap構造函數
- public LinkedHashMap() {
- super();
- accessOrder = false;
- }
- //如果想實現LRU算法,參考這個構造函數
- public LinkedHashMap(int initialCapacity, float loadFactor,
- boolean accessOrder) {
- super(initialCapacity, loadFactor);
- this.accessOrder = accessOrder;
- }
- //模板方法模式,HashMap構造函數裏面的會調用init()方法
- //初始化的時候map裏沒有任何Entry,讓header.before = header.after = header
- void init() {
- header = new Entry<K, V>(-1, null, null, null);
- header.before = header.after = header;
- }
//默認accessOrder爲false
//調用HashMap構造函數
public LinkedHashMap() {
super();
accessOrder = false;
}
//如果想實現LRU算法,參考這個構造函數
public LinkedHashMap(int initialCapacity, float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
//模板方法模式,HashMap構造函數裏面的會調用init()方法
//初始化的時候map裏沒有任何Entry,讓header.before = header.after = header
void init() {
header = new Entry<K, V>(-1, null, null, null);
header.before = header.after = header;
}
五.存數據
- //LinkedHashMap沒有put(K key, V value)方法,只重寫了被put調用的addEntry方法
- //1是HashMap裏原有的邏輯,23是LinkedHashMap特有的
- void addEntry(int hash, K key, V value, int bucketIndex) {
- createEntry(hash, key, value, bucketIndex);
- Entry<K, V> eldest = header.after;
- //3.如果有必要,移除LRU裏面最老的Entry,否則判斷是否該resize
- if (removeEldestEntry(eldest)) {
- removeEntryForKey(eldest.key);
- } else {
- if (size >= threshold)
- resize(2 * table.length);
- }
- }
- void createEntry(int hash, K key, V value, int bucketIndex) {
- //1.同HashMap一樣:在Entry數組+next鏈表結構裏面加入Entry
- HashMap.Entry<K, V> old = table[bucketIndex];
- Entry<K, V> e = new Entry<K, V>(hash, key, value, old);
- table[bucketIndex] = e;
- //2.把新Entry也加到header鏈表結構裏面去
- e.addBefore(header);
- size++;
- }
- //默認是false,我們可以重寫此方法
- protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
- return false;
- }
- private static class Entry<K, V> extends HashMap.Entry<K, V> {
- //鏈表插入元素四個步驟,對着圖看
- private void addBefore(Entry<K, V> existingEntry) {
- after = existingEntry; //1
- before = existingEntry.before; //2
- before.after = this; //3
- after.before = this; //4
- }
- }
- //如果走到resize,會調用這裏重寫的transfer
- //HashMap裏面的transfer是n * m次運算,LinkedHashtable重寫後是n + m次運算
- void transfer(HashMap.Entry[] newTable) {
- int newCapacity = newTable.length;
- //直接遍歷header鏈表,HashMap裏面是遍歷Entry數組
- for (Entry<K, V> e = header.after; e != header; e = e.after) {
- int index = indexFor(e.hash, newCapacity);
- e.next = newTable[index];
- newTable[index] = e;
- }
- }
//LinkedHashMap沒有put(K key, V value)方法,只重寫了被put調用的addEntry方法
//1是HashMap裏原有的邏輯,23是LinkedHashMap特有的
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
Entry<K, V> eldest = header.after;
//3.如果有必要,移除LRU裏面最老的Entry,否則判斷是否該resize
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
//1.同HashMap一樣:在Entry數組+next鏈表結構裏面加入Entry
HashMap.Entry<K, V> old = table[bucketIndex];
Entry<K, V> e = new Entry<K, V>(hash, key, value, old);
table[bucketIndex] = e;
//2.把新Entry也加到header鏈表結構裏面去
e.addBefore(header);
size++;
}
//默認是false,我們可以重寫此方法
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return false;
}
private static class Entry<K, V> extends HashMap.Entry<K, V> {
//鏈表插入元素四個步驟,對着圖看
private void addBefore(Entry<K, V> existingEntry) {
after = existingEntry; //1
before = existingEntry.before; //2
before.after = this; //3
after.before = this; //4
}
}
//如果走到resize,會調用這裏重寫的transfer
//HashMap裏面的transfer是n * m次運算,LinkedHashtable重寫後是n + m次運算
void transfer(HashMap.Entry[] newTable) {
int newCapacity = newTable.length;
//直接遍歷header鏈表,HashMap裏面是遍歷Entry數組
for (Entry<K, V> e = header.after; e != header; e = e.after) {
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}
下面三個圖是初始化LinkedHashMap------->添加Entry e1------>添加Entry e2時,LinkedHashMap結構的變化。
六.取數據
- //重寫了get(Object key)方法
- public V get(Object key) {
- //1.調用HashMap的getEntry方法得到e
- Entry<K, V> e = (Entry<K, V>) getEntry(key);
- if (e == null)
- return null;
- //2.LinkedHashMap牛B的地方
- e.recordAccess(this);
- return e.value;
- }
- // 繼承了HashMap.Entry
- private static class Entry<K, V> extends HashMap.Entry<K, V> {
- //1.此方法提供了LRU的實現
- //2.通過12兩步,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry
- //3.最近使用是什麼意思:accessOrder爲true時,get(Object key)方法會導致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用
- //4.總結LinkedHashMap迭代方式:accessOrder=false時,迭代出的數據按插入順序;accessOrder=true時,迭代出的數據按LRU順序+插入順序
- // HashMap迭代方式:橫向數組 * 豎向next鏈表
- void recordAccess(HashMap<K, V> m) {
- LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
- //如果使用LRU算法
- if (lm.accessOrder) {
- lm.modCount++;
- //1.從header鏈表裏面移除當前Entry
- remove();
- //2.把當前Entry移到header的before位置
- addBefore(lm.header);
- }
- }
- //讓當前Entry從header鏈表消失
- private void remove() {
- before.after = after;
- after.before = before;
- }
- }
//重寫了get(Object key)方法
public V get(Object key) {
//1.調用HashMap的getEntry方法得到e
Entry<K, V> e = (Entry<K, V>) getEntry(key);
if (e == null)
return null;
//2.LinkedHashMap牛B的地方
e.recordAccess(this);
return e.value;
}
// 繼承了HashMap.Entry
private static class Entry<K, V> extends HashMap.Entry<K, V> {
//1.此方法提供了LRU的實現
//2.通過12兩步,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry
//3.最近使用是什麼意思:accessOrder爲true時,get(Object key)方法會導致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用
//4.總結LinkedHashMap迭代方式:accessOrder=false時,迭代出的數據按插入順序;accessOrder=true時,迭代出的數據按LRU順序+插入順序
// HashMap迭代方式:橫向數組 * 豎向next鏈表
void recordAccess(HashMap<K, V> m) {
LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
//如果使用LRU算法
if (lm.accessOrder) {
lm.modCount++;
//1.從header鏈表裏面移除當前Entry
remove();
//2.把當前Entry移到header的before位置
addBefore(lm.header);
}
}
//讓當前Entry從header鏈表消失
private void remove() {
before.after = after;
after.before = before;
}
}
七.刪數據
- // 繼承了HashMap.Entry
- private static class Entry<K, V> extends HashMap.Entry<K, V> {
- //LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove調用的recordRemoval方法
- //這個方法的設計也和精髓,也是模板方法模式
- //HahsMap remove(Object key)把數據從橫向數組 * 豎向next鏈表裏面移除之後(就已經完成工作了,所以HashMap裏面recordRemoval是空的實現調用了此方法
- //但在LinkedHashMap裏面,還需要移除header鏈表裏面Entry的after和before關係
- void recordRemoval(HashMap<K, V> m) {
- remove();
- }
- //讓當前Entry從header鏈表消失
- private void remove() {
- before.after = after;
- after.before = before;
- }
- }
// 繼承了HashMap.Entry
private static class Entry<K, V> extends HashMap.Entry<K, V> {
//LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove調用的recordRemoval方法
//這個方法的設計也和精髓,也是模板方法模式
//HahsMap remove(Object key)把數據從橫向數組 * 豎向next鏈表裏面移除之後(就已經完成工作了,所以HashMap裏面recordRemoval是空的實現調用了此方法
//但在LinkedHashMap裏面,還需要移除header鏈表裏面Entry的after和before關係
void recordRemoval(HashMap<K, V> m) {
remove();
}
//讓當前Entry從header鏈表消失
private void remove() {
before.after = after;
after.before = before;
}
}
八.LinkedHashMap EntrySet遍歷
- private abstract class LinkedHashIterator<T> implements Iterator<T> {
- //從header.after開始遍歷
- Entry<K, V> nextEntry = header.after;
- Entry<K, V> nextEntry() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- if (nextEntry == header)
- throw new NoSuchElementException();
- Entry<K, V> e = lastReturned = nextEntry;
- nextEntry = e.after;
- return e;
- }
- }
private abstract class LinkedHashIterator<T> implements Iterator<T> {
//從header.after開始遍歷
Entry<K, V> nextEntry = header.after;
Entry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry<K, V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
- 上圖中,遍歷的結果是先e1然後e2。
- accessOrder爲true時,get(e1.key)或者put(e1.key, value)一下,則結構1變成e2------e1------header,遍歷的結果就是先e2然後e1。
九.總結
- LinkedHashMap繼承HashMap,結構2裏數據結構的變化交給HashMap就行了。
- 結構1裏數據結構的變化就由LinkedHashMap裏重寫的方法去實現。
- 簡言之:LinkedHashMap比HashMap多維護了一個鏈表。