HashMap
一 定義和創建
HashMap實現了Map接口,繼承AbstractMap類。AbstractMap中包含了map的基本功能。
(1) 初始大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
從源碼可以看出大小是16(1左移動4位1000 = 16)
static final int MAXIMUM_CAPACITY = 1 << 30;
最大長度是2的30次方1073741824 基本能滿足絕大部分需求的使用。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默認的負載因子是0.75 ,默認的負載因子一般不需要改動。如果更加關注內存空間,而不怎么關注速度,可以調大負載因子,反之,如果更加關注HashMap讀寫速度,而不太關注內存空間,則可以調小負載因子。
二 put方法,HASHMAP寫入值,get方法讀取值
從1.7之前的hashMap是用數組table+鏈表實現的,而在1.8jdk中隊hashMap做了優化,通過數組,鏈表,紅黑樹(二叉樹的一種)來實現,鏈表數組長度超過8時,轉成紅黑樹從而提高了HashMap的效率。
(1)1.7中 hashMap的put方法詳解:
public V put(K key, V value) { // 如果 key 為 null,調用 putForNullKey 方法進行處理 if (key == null) return putForNullKey(value); // 根據 key 的 keyCode 計算 Hash 值,hash函數為一個數學方法用位運算繼續計算hash值 int hash = hash(key.hashCode()); // 查找hash值在table中的索引(此處的indexfor方式是一個數學方法, 返回值為 hash&table.length-1 ,通過位運算,保證此函數的返回值總是小於等於table.length,這樣能確保i值在table數組的索引之內) int i = indexFor(hash, table.length); // 如果 i 索引處的 Entry 不為 null,通過循環不斷遍歷 e 元素的下一個元素 // table的i處存放的Entry中next值記錄了鏈表情況,可能有多個Entry鏈,所以此時對鏈表循環處理 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 1.找到key值的hash值與插入值的hash值相同的key,並且key的值要與插入的key值相同, // 2.兩者同時滿足才能判斷插入的是同一個key( 不同key值的hash值可能相等(hash碰撞),所以此處要增加key值自身的判斷) if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //key值相同時直接替換value值,跳出函數 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果 i 索引處的 Entry 為 null 或者key的hash值相同而key不同 ,則需要新增Entry modCount++; // 將 key、value 添加到 i 索引處 addEntry(hash, key, value, i); return null; }
下面通過viso圖來簡要描述流程
addEntry這個函數這里在簡單的說一下,先上代碼
//table數組節點中新增 Entry void addEntry(int hash, K key, V value, int bucketIndex) { // 獲取指定 bucketIndex 索引處的 Entry Entry<K,V> e = table[bucketIndex]; // ① //將新創建的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry //此處分為兩種情況 //1.table中i出無元素,則 e的值為null,新插入的Entry的next為null,無鏈表 //2.table中i處存在元素,新插入的元素會覆蓋數組中的位置,同時新創建的Entry中next指向老元素的,形成鏈表。 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 如果 Map 中的 key-value 對的數量超過了極限 if (size++ >= threshold) // 把 table 對象的長度擴充到 2 倍。 resize(2 * table.length); // ② }
在table的指定位置添加Entry元素,如果已經有entry元素,用新增的Entry替代老元素,但在entry中的next記錄原元素的位置,實現鏈表查詢。
到這里基本上put方法的原理基本上就清晰明了,下面看一下get方法
public V get(Object key) { // 如果 key 是 null,調用 getForNullKey 取出對應的 value if (key == null) return getForNullKey(); // 根據該 key 的 hashCode 值計算它的 hash 碼 int hash = hash(key.hashCode()); // 直接取出 table 數組中指定索引處的值, for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; // 搜索該 Entry 鏈的下一個 Entr e = e.next) // ① { Object k; // 如果該 Entry 的 key 與被搜索 key 相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
先通過hash方法查到在數組中的位置,查找出entry並通過entry的next做遍歷,查到key值對應的value
三 jdk1.8中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; //如果table空,初始化table數組 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //查到i出元素為空 ,則在i位置新增Node元素 //注:此處i = (n - 1) & hash此方法即是jdk1.7中的indexFor方法,通過數學位運算,取的一個在table的長度范圍內的值) if ((p = tab[i = (n - 1) & hash]) == null) //沒有查到key ,新增節點 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //索引處的key值和key的hash值匹配,則直接把p節點指針傳給臨時變量e if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode)//如果p節點是紅黑樹,特殊處理 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//遍歷鏈表,處理key值的hash沖突情況 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {//在鏈表結尾處新增節點 p.next = newNode(hash, key, value, null);//鏈表原末尾元素next指向先創建的元素 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))))//檢查key值和hash值是否同時相同 break; p = e; } } if (e != null) { // existing mapping for key 將新value值存放到節點處 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
主要新增了紅黑樹方法, 在key值產生沖突事,如果鏈表的長度超過8則轉化成紅黑樹來實現,避免同一節點太長的鏈表影響效率。