HashMap
數組(Node類型的數組)加鏈表(或者紅黑樹),new HashMap時不指定大小,則默認爲空,在第一次put數據時候會對Node數組進行擴容,默認大小爲16,擴容是耗費性能的,所以阿里手冊中創建HashMap時候需要指定容量大小
transient Node<K,V>[] table;
通過計算存入對象的Hash值來計算在數組的索引位置
當發生Hash衝突時候,衝突的索引位置會以鏈表的形式解決衝突
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
如果鏈表的長度大於8,會嘗試把鏈表轉化爲紅黑樹。
問題:鏈表的長度有沒有可能大於8?
是存在這種情況的
因爲升級爲紅黑樹時會調用treeifyBin方法,該方法會判讀當前map的size是否小於64,如果小於64,會先對table進行擴容,如果大於64則轉換爲紅黑樹;因此當前map的size是否小於64是存在鏈表的長度大於8的情況的。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
....
if (binCount >= TREEIFY_THRESHOLD - 1) //8-1
treeifyBin(tab, hash);
.....
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果小於64則先resize擴容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 否則纔會轉爲紅黑樹
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
爲什麼HashMap的key是亂序的呢?
因爲對HashMap取key時,它是按照數組table的順序進行取值的,而數組table所存儲元素的位置又是根據Hash算出來的,所以導致亂序,取key時候如下圖:
即遍歷數組,如果元素爲鏈表,把該數組索引位置的鏈表遍歷完成後,在遍歷數組中下一個元素。
如果想要使用key按照添加順序的map的話,可以使用下面的LinkedHashMap。
LinkedHashMap
繼承於HashMap,構造方法如下:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
前兩個參數同HashMap;
accessOrder:是否按照訪問次數排序,默認爲false,false則按照插入次序排序。
接下來我們看它是如何保證順序不變的,首先看put方法,它的put方法還是使用的HashMap的put方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);// newNode是重點
else {
...
}
....
}
其中LinkedHashMap對這個方法進行了重寫
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
再看linkNodeLast方法,該方法每次添加新的節點都會添加到原鏈表的末端
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
發現了head和tail對象,LinkedHashMap維護了兩個成員變量,是雙向鏈表
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
這兩個雙向列表在每次進行put操作時候都會進行更新,按照插入順序不斷在鏈表末尾添加新的數據,從而保證了數據的次序性,從而達到獲取的key是按順序顯示的。