HashMap是java開發中常用的一個,也是面試中幾乎必問的一個知識點,英文HashMap中涉及的知識點很多(考察面試者的java礎、數據結構、及源碼閱讀),下面就HashMap的一些知識進行詳談。
首先我們看源碼中定義的一些變量:
以上變量也是HashMap中非常重知識點。下面我們就一一進行詳解:
DEFAULT_INITIAL_CAPACITY:HashMap的初始化容量16
MAXIMUM_CAPACITY:HashMap最大容量2^30=1073741824
DEFAULT_LOAD_FACTOR:負載因子初始值0.75f,假如容量爲16,那麼當容量使用至12的時候就會開始擴容。
TREEIFY_THRESHOLD :鏈表長度大於8的時候,轉換成紅黑樹
UNTREEIFY_THRESHOLD:紅黑樹節點小於6轉換爲鏈表
MIN_TREEIFY_CAPACITY:數組大小大於64時轉換爲紅黑樹
HashMap插入過程
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1、判斷數組是否爲空,爲空初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//2、不爲空,計算key的hash值,通過(n-1)&hash計算應該存放在數組中的下標index,
//判斷table[index]是否存在數據,不存在構造node節點並將其存放在table[index]中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//存在數據時
else {
Node<K,V> e; K k;
//判斷key和hash值是否相等,相等的則將p值賦值給e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//不相等判斷是否屬於樹節點,屬於樹節點,則插入到紅黑樹中;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果不是樹節點,創建Node加入鏈表末尾,判斷鏈表的長度是否大於8,大於的話鏈表轉換爲紅黑樹。
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不爲空,說明存在重複的key值,用新值替換舊值
if (e != null) { // existing mapping for key
V oldValue = e.value;
//onlyIfAbsent初始值爲false
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//判斷當前節點數,是否達到擴容的閥值,大於閥值則擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- 判斷數組是否爲空,爲空則進行初始化。
- 不爲空,計算key的hash值,通過(n-1)&hash計算應該存放在數組中的下標index
- 查看table[index]是否存在數據,沒有數據就構造一個node節點存放在table[index]中;
- 存在數據,說明發生了hash衝突(存在兩個節點key的hash值一樣),計算判斷key是否相等,相等的話,用新的value替換原來的數據。
- 如果不相等,判斷當前節點類型是不是樹節點,如果是樹節點,創造樹節點插入紅黑樹中;
- 如果不是樹型節點,創建普通的Node加入鏈表當中;判斷鏈表的長度是否大於8,大於的話鏈表轉換爲紅黑樹。
- 插入完成後判斷當前節點數是否大於閥值,如果大於閥值則開始擴容。
HashMap初始化?
一般如果new HashMap不傳值,默認大小16,負載因子是0.75。提供了四種初始化的方法
HashMap() 默認大小16,負載因子是0.75
HashMap(int initialCapacity, float loadFactor)
HashMap(int initialCapacity)
HashMap(Map<? extends K, ? extends V> m)
初始化大小的函數
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
System.out.println(n);
n |= n >>> 2;
System.out.println(n);
n |= n >>> 4;
System.out.println(n);
n |= n >>> 8;
System.out.println(n);
n |= n >>> 16;
System.out.println(n);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
構造函數傳入初始化值initialCapacity,並不是直接設置HashMap的容量值爲initialCapacity.
而是通過上面的函數計算出一個大於等於initialCapacity的2的整數次方的最小整數。
下面給大家舉個例子以10爲例子,大於10的最小2的整數次方數是16
原始數 |
二進制 |
initialCapacity 10 |
0000 1010 |
-1 |
0000 1001 |
n >>> 1 |
0000 0100 |
n |= n >>> 1; |
0000 1101 |
n >>> 2 |
0000 1100 |
n |= n >>> 2; |
0000 1101 |
n >>> 4 |
0000 0000 |
n |= n >>> 4; |
0000 1101 |
n >>> 8 |
0000 0000 |
n |= n >>> 8; |
0000 1101 |
n >>> 16 |
0000 0000 |
n |= n >>> 16; |
0000 1101 |
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; |
16 |
HashMap初始化容量大小的參數爲10,其實HashMap的實際初始化大小爲16
Hash函數:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
爲什麼這樣設計?
- 儘可能的降低hash碰撞,越分散越好;
- 算法的高效性,採用位運算,保證高頻操作效率。
本文有借鑑右側 https://blog.csdn.net/zhengwangzw/article/details/104889549 網址博客