JDK8 原始碼解析 ---HashMap
##
Stack過時的類,使用Deque重新實現。
HashMap原始碼解析:
HashMap的定義:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
繼承抽象AbstractMap,實現了Map。
HashMap中重要常量:
//預設容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//預設載入因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//連結串列轉成紅黑樹的閾值
static final int TREEIFY_THRESHOLD = 8;
//紅黑樹轉為連結串列的閾值
static final int UNTREEIFY_THRESHOLD = 6;
//儲存方式由連結串列轉成紅黑樹的容量的最小閾值
static final int MIN_TREEIFY_CAPACITY = 64;
//HashMap中儲存的鍵值對的數量
transient int size;
//擴容閾值,當size>=threshold時,就會擴容
int threshold;
//HashMap的載入因子
final float loadFactor;
需要指出的是這裡loadFactor載入因子在初始化後就不能變更。載入因子也可以叫做擴充因子----畢竟只是拿來判斷是否擴容的嘛(#^.^#)。
初始化HashMap
Map<K,V> map = new HashMap<K,V>();
或者 Map<K,V> map = new HashMap<K,V>(31)
實際呼叫的程式碼
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
this(initialCapacity,DEAULT_LOAD_FACTOR);
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
在初始化中會判斷,初始化引數是否小於0,丟擲IllegalArgumentException();如果HashMap的最大容量MAXIMUM_CAPACITY(也就是2的32次方 ,為什麼是2的32次方呢? ),在確定threhold擴容閾值。
細心的同學可能注意到HashMap中所有常量的定義都是int型,Java中int型是32位的。
接下來就是對tableSizeFor的解釋:
/** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
原始碼中註釋的寫的是獲取與cap最相近的2的冪。
例如 32 會產生32 而33就會產生64,這個式子很神奇,為什麼正確,我也不太明瞭o(╥﹏╥)o。
Hash()方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
^是異或操作 1^1 =0 ;1^0 = 1; 0^1 =1 ;0^0=0; 多位的異或,比如3^2 = 1 (11^10 = 01);
這裡需要解釋的是,如果(h = key.hashCode())^(h>>>16) 是取h的低16位與高16為進行異或作為低16位與h的高16位進行拼接,得到最後的hash值。 據說能夠提高hash的分散程度。嚶嚶嚶。
還有提到一點就是HashSet內部是使用HashMap實現的;這個在解析HashSet的時候會詳細提到;
/** HashSet的Add的方法 */ public boolean add(E e) { return map.put(e, PRESENT)==null; }
HashSet核心方法
putVal()方法
原始碼:
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); else { Node<K,V> e; K k; 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); 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; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
我們慢慢來分析。首先看入參:
hash:表示key的hash值
key:待儲存的key值
value:待儲存的value值,從這個方法可以知道,HashMap底層儲存的是key-value的鍵值對,不只是儲存了value
onlyIfAbsent:這個引數表示,是否需要替換相同的value值,如果為true,表示不替換已經存在的value
evict:如果為false,表示陣列是新增模式
我們看到put時所傳入的引數put(hash(key), key, value, false, true),可以得到相應的含義。
作者:端木軒
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。
HashMap中的資料結構
在繼續下一步分析之前,我們首先需要看一下HashMap底層的資料結構。
HashMap的資料結構
我們可以看到,HashMap底層是陣列加單向連結串列或紅黑樹實現的(這是JDK 1.8裡面的內容,之前的版本純粹是陣列加單向連結串列實現)。
回到最騷氣的putVal()
Node<K,V>[] tab; Node<K,V> p; int n, i;
Node<K,V>[] tab 用於引用table也就是hash表;
Node<K,V> p 用於指向需要指向紅黑樹或者連結串列;
int n 用於儲存當前table的長度;
int i 用於儲存當前訪問的table索引;
####
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);
如果當前訪問的桶為空,初始化一個新的節點
Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;
當前的hash值等於。。。
else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
當前訪問的桶是紅黑樹,將該值放入紅黑樹中;
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; }
如果桶是空的,將節點新增到連結串列後面,如果當前連結串列長度大於TREEIFY_THRESHOLD將當前連結串列轉化為紅黑樹。
if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; }
如果當前key不為空,並且開啟了替換模式,將值直接替掉;
++modCount; if (++size > threshold) resize();
當前值大小大於閾值,將當前大小重新定製;到此,最核心的putVal()就淺顯的將完成了,雖然還有很多的疑惑為解決;
// Callbacks to allow LinkedHashMap post-actions void afterNodeAccess(Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node<K,V> p) { }在程式碼中這三個方法體時為空的,用於LinkedHashMap的操作;
#####
參考內容: