基礎知識(一) HashMap 原始碼詳解
阿新 • • 發佈:2019-02-14
因為最近想面試,所以複習下。分析學習基於JDK1.8
HashMap 繼承於 AbstrackHashMap 實現於 Map<K,V>, Cloneable, Serializable,內部使用雜湊連結串列 紅黑樹實現。注意此Map不是執行緒安全的,如果需要同步使用請使用ConcurrentHashMap 或者 Collections.synchronizedMap
常量引數
1、下面的都是直接static final 的值,也就是在JVM準備的時候就已經初始化了
DEFAULT_INITIAL_CAPACITY =16 預設容量為
MAXIMUM_CAPACITY =1 << 30 最大容量為
DEFAULT_LOAD_FACTOR = 0.75f 預設負載因子
TREEIFY_THRESHOLD=8 連結串列轉換紅黑樹的閥值
UNTREEIFY_THRESHOLD=6 紅黑樹轉換連結串列的閥值
MIN_TREEIFY_CAPACITY=64 桶中bin最小hash容量,如果大於這個值會進行resize擴容操作,此值至少是TREEIFY_THRESHOLD的4倍
2、下面說下成員變數 都是 transient,也就是說不會被序列化的欄位
Node<K,V>[] table HashMap內部類實現了Map的內部類Entry,用於儲存K,V,第一次使用的時候被建立,根據需要可以進行resize。分配長度為2的冥次方
Set<Map.Entry<K,V>> entrySet 當被呼叫entrySet時被賦值。通過keySet()方法可以得到map key的集合,通過values方法可以得到map value的集合
int size 存放在map中K,V的總數
int modCount HashMap被結構性修改的次數。(結構性修改是指改變了KV對映數量的操作或者修改了HashMap的內部結構(如 rehash)。這個用於fail-fast
int threshold 進行resize的閥值,當Map中K,V數量(size) 超過了這個值,那將進行resize操作。
threshold =DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR
final float loadFactor 負載因子
構造方法
目前有4個構造方法
1、public HashMap(int initialCapacity, float loadFactor)
引數為初始容量,負載因子
步驟:
1、如果容量引數小於0就丟擲異常
2、如果容量引數大於最大值MAXIMUM_CAPACITY 就初始化為MAXIMUM_CAPACITY
3、如果負載因子小於等於0或者是一個非數字就丟擲異常
4、賦值負載因子
5、使用tableSizeFor 計算初始容量
方法原始碼解析
先看put方法,使用的是
publicV
put(Kkey, Vvalue)
{
returnputVal(hash(key),key,value,false ,true);
}
內建final方法,不可被子類重寫,在編譯期已經靜態繫結
finalVputVal(inthash,
Kkey, Vvalue,booleanonlyIfAbsent,booleanevict)
引數:
hash 計算出的hash值
key 傳入鍵
value 傳入值
onlyIfAbsent 預設false,為true的時候不覆蓋已存在的值
evict 預設true
下面說下resize
下面說下簡單的get
下一篇來說下linkedHashMap
transient Node<K,V>[] table; static final int TREEIFY_THRESHOLD = 8; transient int modCount; transient int size; int threshold; //新手看到這麼多變數肯定暈了,記住一點,這是引用傳遞 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //首先定義區域性變數 Node<K,V>[] tab; Node<K,V> p; int n, i; //將目前儲存table 賦值給區域性變數tab 並判斷其是否為空或size為0,如果為空則進行table初始化,並將初始化的size賦值給n(初始化其實就是給threshold計算出一個閥值,並new了一個node給table) if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //根據hash值計算出tab陣列的位置並判斷其是否為空,如果為空就直接把值存入一個新的Node中 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //如果計算出的p 索引有元素存在 else { Node<K,V> e; K k; //根據hash key判斷是不是相同的元素,如果是相同的元素就把p(老元素) 賦值給新的成員變數e if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //如果不是相同的key,只是index一樣,判斷元素是不是紅黑樹,是就將他放入樹中 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //不是一樣的key hash 並且不是紅黑樹 else { //無限迴圈 for (int binCount = 0; ; ++binCount) { //判斷是否有下一個元素 if ((e = p.next) == null) { //沒有下一個元素,就將當前元素存入為下一個元素 p.next = newNode(hash, key, value, null); //判斷當前連結串列長度是否超過了7,則將連結串列元素轉換為紅黑樹,跳出迴圈 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //判斷當前index的元素是否一樣,一樣就賦值替換,跳出 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // if (e != null) { // existing mapping for key //將老元素的value提取出 V oldValue = e.value; //判斷是否覆蓋老元素的值 if (!onlyIfAbsent || oldValue == null) e.value = value; //這個方法是給LinkedHashMap留得,因為他用的也是這個put方法 afterNodeAccess(e); return oldValue; } } //更新結構更改次數 ++modCount; //判斷+1後的size是否大於閥值預設計算出的是12) if (++size > threshold) resize(); //這個方法是給LinkedHashMap留得,因為他用的也是這個put方法 afterNodeInsertion(evict); return null; }
下面說下resize
int threshold;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;
// 擴容方法,不能被重寫
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = table;
// 獲取當前容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 獲取擴容閥值,預設0
int oldThr = threshold;
// newCap 新容量 newThr 新閥值
int newCap, newThr = 0;
if (oldCap > 0) {
// 判斷其容量如果大於最大值就將擴容閥值設定為Integer最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
// 判斷當前容量的兩倍是否小於最大容量限定,並且當前容量是否大於等於預設的16 ,滿足條件就將當前容量擴大一倍
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
} else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 走到這代表是個新map首次建立
else { // zero initial threshold signifies using defaults
// 初始化容量16
newCap = DEFAULT_INITIAL_CAPACITY;
// 初始化計算擴容閥值12
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({ "rawtypes", "unchecked" })
Node<K, V>[] newTab = new Node[newCap];
// 賦值擴容完的node[]給teble
table = newTab;
// 判斷當前table是否為空,如果為null說明是新建,否則為擴容
if (oldTab != null) {
// 根據當前容量迴圈Node
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// 先將老物件賦值給成員變數,然後判斷其是否為null
if ((e = oldTab[j]) != null) {
// 將老物件設定為null,方便垃圾回收
oldTab[j] = null;
// 如果當前元素沒有下一個元素,計算出其index並賦值給新node[]
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 判斷其是否為紅黑樹
else if (e instanceof TreeNode)
((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
else { // preserve order 保持順序
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
next = e.next;
// 根據hash oldCap計算出結果,將符合結果的元素組建成為新的連結串列lo
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
} else {
// 將不為0的元素組建成為連結串列hi
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 將連結串列放到原位
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
下面說下簡單的get
public V get(Object key) {
Node<K, V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K, V> getNode(int hash, Object key) {
Node<K, V>[] tab;
Node<K, V> first, e;
int n;
K k;
//判斷當前table是否為空,並根據lenght hash算出index位置
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
//直接在第一個桶中查詢看是否命中
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果有下一個元素
if ((e = first.next) != null) {
//如果為紅黑樹,直接樹中查詢
if (first instanceof TreeNode)
return ((TreeNode<K, V>) first).getTreeNode(hash, key);
do {
//這就是連結串列了,直接next迴圈查詢
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
下一篇來說下linkedHashMap