準備工作
由于LinkedHashMap也是繼承HashMap,在HashMap類的基礎上進行的功能擴展,所以先了解下HashMap :http://www.lxweimin.com/p/374546518bb6
LinkedHashMap中鏈地址的雙向循環鏈表結構
// 雙向循環鏈表維護沖突值
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// 雙向循環鏈表的前驅節點和后繼結點
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
// 通過前驅后繼關系將節點刪除
private void remove() {
// 當前節點的前驅節點的后繼為當前節點的后繼結點
before.after = after;
// 當前節點的后繼結點的前驅為當前節點的前驅節點
after.before = before;
}
說明:使用前驅節點和后繼節點刪除當前節點
/**
* 將existingEntry指定節點前面插入當前節點。
* existingEntry :現有項
*/
private void addBefore(Entry<K,V> existingEntry) {
//this.after =existingEntry;
after = existingEntry;
//this.before =existingEntry.before;
before = existingEntry.before;
before.after = this;
after.before = this;
}
說明(重要設計):existingEntry指定節點作為當前節點的后繼節點,existingEntry指定節點的前驅節點作為當前節點的前驅節點,對于before和after本身就是當前節點的前驅節點和后繼節點,再修改下,讓前驅節點的后繼指針指向當前節點,讓后繼節點的前驅指針指向當前節點即可。完成了將existingEntry指定節點前面插入當前節點。
/**
* 如果LinkedHashMap的排序順序為訪問順序,那么就將當前節點插入到頭結點前面
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//如果排序順序為訪問順序
if (lm.accessOrder) {
// LinkedHashMap修改次數加1
lm.modCount++;
// 刪除當前節點
remove();
// 在頭節點前面插入當前節點
addBefore(lm.header);
}
}
說明:如果排序順序為訪問順序,那么將在頭結點前面插入當前節點。
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
屬性
// 雙向鏈表的頭結點
private transient Entry<K,V> header;
說明:雙向鏈表的頭節點。
// 排序模式:對于訪問順序,為 true;對于插入順序,則為 false。
private final boolean accessOrder;
說明:節點的排序方式:對于訪問順序,為 true;對于插入順序,則為 false。
構造方法
構造方法1:傳入初始化容量,加載因子,默認采用插入順序排序的HashMap構造
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
構造方法2:傳入初始化容量,默認加載因子為0.75,默認采用插入順序排序的HashMap構造
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
構造方法3:采用默認初始化容量16,默認加載因子為0.75,默認插入順序排序的HashMap構造
public LinkedHashMap() {
super();
accessOrder = false;
}
構造方法4:構造一個映射關系與指定 Map 相同的 HashMap; 所創建的 HashMap 具有默認的加載因子 (0.75) 和足以容納指定 Map 中映射關系的初始容量; 采用默認插入順序排序
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
構造方法5:構造一個擁有初始化容量、加載因子、排序順序的LinkedHashMap
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
方法
init方法,初始化雙向循環鏈表
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
createEntry方法:創建節點,將頭結點插入到當前節點的前面。
void createEntry(int hash, K key, V value, int bucketIndex) {
// 通過下標獲取鍵表中對應的節點Entry
HashMap.Entry<K,V> old = table[bucketIndex];
// 創建一個節點,后繼節點指向old
Entry<K,V> e = new Entry<>(hash, key, value, old);
// 插入節點e
table[bucketIndex] = e;
// e節點設置為頭結點的后繼節點
e.addBefore(header);
size++;
}
說明:
- 雖然HashMap查找元素的方式是,通過鍵找到hash值,通過hash值找到下標,通過下標找到節點鏈。
- LinkedHashMap在原來的HashMap基礎上,將單向節點鏈中的每個節點又額外構造了一條雙向鏈表,插入值的順序,就是在頭結點的后面插入新節點(類似于不停的插隊的節奏)。
transfer方法:將所有元素都放到新的數組中,重寫父類的HashMap
的transfer方法。
/**
* 將所有的元素放置到新的數組中
* 重寫父類HashMap的transfer方法
*/
@Override
void transfer(HashMap.Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
// 使用雙向鏈表的頭結點進行循環遍歷
for (Entry<K,V> e = header.after; e != header; e = e.after) {
if (rehash)
e.hash = (e.key == null) ? 0 : hash(e.key);
// 通過hash值獲取對應的下標索引
int index = indexFor(e.hash, newCapacity);
// 插入到鏈表表頭
e.next = newTable[index];
// 新數組的索引對應的值為header
newTable[index] = e;
}
}
說明:遍歷雙向鏈表,從頭結點的后繼節點開始遍歷,然后將遍歷到的節點設置到新的鍵表中。
get方法:返回此映射中映射到指定鍵的值。
public V get(Object key) {
// 調用父類的getEntry方法,通過鍵key來獲取對應的Entry
Entry<K,V> e = (Entry<K,V>)getEntry(key);
// 如果e為null,那么根本不存在對應的Entry就更沒有對應的值了,所以返回null
if (e == null)
return null;
// 將當前節點插入到頭結點前面
e.recordAccess(this);
return e.value;
}
說明:通過鍵獲取節點,通過節點獲取值。
迭代:
private class KeyIterator extends LinkedHashIterator<K> {
public K next() { return nextEntry().getKey(); }
}
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;
}
總結:
- LinkedHashMap存儲沖突值使用雙向循環鏈表。
- LinkedHashMap中鍵和值都允許存入null值。
- LinkedHashMap中值允許重復,如果發現鍵相同,就更新原來的值。
- LinkedHashMap是線程不安全的。
- LinkedHashMap中accessOrder屬性,true意味著排序模式為訪問順序遍歷出來,false意味著排序模式為插入順序遍歷出來。
- LinkedHashMap單獨維護了一條雙向鏈表,保證了按照插入的順序設計的,所以取得時候就會保證有序。