jdk版本:1.8
資料結構:
HashMap
的底層主要基於陣列+連結串列/紅黑樹實現,陣列優點就是查詢塊,HashMap
通過計算hash碼
獲取到陣列的下標來查詢資料。同樣也可以通過hash碼
得到陣列下標,存放資料。
雜湊表為了解決衝突,HashMap
採用了連結串列法,新增的資料存放在連結串列中,如果傳送衝突,將資料放入連結串列尾部。
上圖左側部分是一個雜湊表,也稱為雜湊陣列(hash table):
// table陣列
transient Node<K,V>[] table;
table
陣列的引用型別是Node節點
,陣列中的每個元素都是單連結串列的頭結點,連結串列主要為了解決上面說的hash衝突,Node節點包含:
hash
hash值key
鍵value
值next
next指標
Node節點
結構如下:
static class Node<K,V> implements Map.Entry<K,V> {
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;
}
// 省略 get/set等方法
}
主要屬性
// 儲存元素陣列
Node<K,V>[] table;
// 元素個數
int size;
// 陣列擴容臨界值,計算為:元素容量*裝載因子
int threshold
// 裝載因子,預設0.75
float loadFactor;
// 連結串列長度為 8 的時候會轉為紅黑樹
int TREEIFY_THRESHOLD = 8;
// 長度為 6 的時候會從紅黑樹轉為連結串列
int UNTREEIFY_THRESHOLD = 6;
- size記錄元素個數
- threshold 擴容的臨界值,等於元素容量*裝載因子
- TREEIFY_THRESHOLD 8 連結串列個數增加到
8
會轉成紅黑樹 - UNTREEIFY_THRESHOLD 6 連結串列個數減少到
6
會退化成連結串列 - loadFactor 裝載因子,預設為
0.75
loadFactor 裝載因子等於擴容閾值/陣列長度,表示元素被填滿的程式,越高表示空間利用率越高,但是
hash衝突
的概率增加,連結串列越長,查詢的效率降低。越低hash衝突
減少了,資料查詢效率更高。但是示空間利用率越低,很多空間沒用又繼續擴容。為了均衡查詢時間和使用空間,系統預設裝載因子為0.75
。
獲取雜湊陣列下標
新增、刪除和查詢方法,都需要先獲取雜湊陣列的下標位置,首先通過hash演算法
算出hash值,然後再進行長度取模,就可以獲取到元素的陣列下標了。
首先是呼叫hash
方法,計算出hash值
:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
先獲取hashCode值,然後進行高位運算,高位運算後的資料,再進行取模運算的速度更快。
算出hash
值之後,再進行取模運算:
(n - 1) & hash
上面的n
是長度,計算的結果就是陣列的下標了。
構造方法
HashMap()
/**
* default initial capacity (16)
* the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
設定預設裝載因子0.75
,預設容量16
。
HashMap(int initialCapacity)
// 指定初始值大小
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定初始值和預設裝載因子 0.75
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);
}
HashMap(int initialCapacity)
指定初始容量,呼叫HashMap(int initialCapacity, float loadFactor)
其中loadFactor
為預設的0.75
。
首先做容量的校驗,小於零報錯,大於最大容量賦值最大值容量。然後做裝載因子的校驗,小於零或者是非數字就報錯。
tableSizeFor
使用右移和或運算,保證容量是2的冪次方,傳入2的冪次方,返回傳入的資料。傳入不是2的冪次方資料,返回大於傳入資料並接近2的冪次方數。比如:
- 傳入
10
返回16
。 - 傳入
21
返回32
。
HashMap(Map<? extends K, ? extends V> m)
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
將集合m
的資料新增到HashMap
集合中,先設定預設裝載因子,然後呼叫putMapEntries
新增集合元素到HashMap
中,putMapEntries
是遍歷陣列,新增資料。
總結
本文基於jdk1.8
解析HashMap原始碼,主要介紹了:
HashMap
是基於陣列+連結串列/紅黑樹結構實現。採用連結串列法
解決hash衝突。- Node 節點記錄了資料的
key
、hash
、value
以及next
指標。 HashMap
主要屬性:- size 元素個數
- table[] 雜湊陣列
- threshold 擴容的閾值
- loadFactor 裝載因子
- TREEIFY_THRESHOLD 8,連結串列個數為8轉成紅黑樹。
- UNTREEIFY_THRESHOLD 6 ,連結串列個數為6紅黑樹轉為連結串列。
- 新增、刪除以及查詢元素,首先要先獲取陣列下標,
HashMap
先呼叫hasCode方法,hashCode()的高16位異或低16位,大大的增加了運算速度。然後再對陣列長度進行取模運算。本質就是取key的hashCode值、高位運算、取模運算。 HashMap
幾個構造方法:HashMap()
設定預設裝載因子0.75
和預設容量16
。HashMap(int initialCapacity)
設定初始容量,預設裝載因子0.75
,容量是一定要是2的冪次方,如果不是2的冪次方,增加到接近2的冪次方數。HashMap(Map<? extends K, ? extends V> m)
主要是遍歷新增的集合,新增資料。
參考
感覺不錯的話,就點個贊吧!