基本概念
HashMap又叫哈希表、散列表省店,是一種以key/value方式存儲數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)嚣崭,它利用不重復(fù)、無序的鍵實(shí)現(xiàn)了快速查找懦傍。每個key對應(yīng)唯一的value雹舀,查詢和修改的效率都很快,能達(dá)到O(1)的平均時間復(fù)雜度谎脯。它是非線程安全的葱跋,且不保證元素存儲的順序持寄。
繼承關(guān)系
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
- 繼承自
AbstractMap
,并實(shí)現(xiàn)Map
,具備Map
的所有功能 - 實(shí)現(xiàn)了
Cloneable
源梭,可以被復(fù)制,原型設(shè)計(jì)模式 - 實(shí)現(xiàn)了
Serializable
,可以被序列化
儲存結(jié)構(gòu)
- 藍(lán)色部分是數(shù)組稍味,數(shù)組的每一個元素也叫做桶废麻,但插入元素和已有元素發(fā)生Hash碰撞時,這兩個Hash碰撞的元素將會以單鏈表方式存放在同一個桶里模庐。
- 在
JDK1.8
版本之前,采用數(shù)組+鏈表的方式,當(dāng)發(fā)生Hash碰撞的次數(shù)足夠多時际跪,桶內(nèi)的單鏈表將會變的非常長春感,查找元素的時間復(fù)雜度將會退化成O(n)。為了解決這個問題疼燥,在JDK1.8
之后沧卢,引入了紅黑樹,在桶里單鏈表長度>=8
時醉者,單鏈表將會轉(zhuǎn)換成紅黑樹,查找時間復(fù)雜度從O(n)降低到O(log n),極大的提升了查找效率但狭。在桶內(nèi)單鏈表長度<=6
,紅黑樹轉(zhuǎn)換回單鏈表的方式存儲。
構(gòu)造函數(shù)
- 無參構(gòu)造
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
無參構(gòu)造擴(kuò)容因子默認(rèn)值0.75f
- int參數(shù)構(gòu)造
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_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);
}
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;
}
- 傳入
initialCapacity
初始容量撬即,將initialCapacity
和默認(rèn)擴(kuò)容因子傳入調(diào)用自身HashMap(int initialCapacity, float loadFactor)
構(gòu)造方法 -
HashMap(int initialCapacity, float loadFactor)
判斷傳入的初始容量initialCapacity
是否合法立磁,必須大于0。 - tableSizeFor(initialCapacity)方法計(jì)算是否需要擴(kuò)容的閥值剥槐,并賦值給
threshold
,
tableSizeFor(int cap)
保證擴(kuò)容閥值為2的冪次唱歧,接下來重點(diǎn)看下這個方法是怎么保證為2的冪次的。
舉個例子
假如tableSizeFor(int cap)方法傳入的參數(shù)是10粒竖,
int n = cap - 1;
//n=9 轉(zhuǎn)二進(jìn)制 1001 補(bǔ)足8位= 0000 1001
n |= n >>> 1;
// 9|9無符號右移1位 = 0000 1001|0000 0100 = 0000 1101
n |= n >>> 2;
//0000 1101|0000 0011 = 0000 1111 //后面的值不再有變化颅崩,都是一樣
n |= n >>> 4;
// 0000 1111|0000 0000 = 0000 1111
n |= n >>> 8;
//0000 1111
n |= n >>> 16;
//0000 1111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
//后面return的值等于 0000 1111 + 1 = 0001 0000 再轉(zhuǎn)10進(jìn)制 = 2的4次方 =16
再解釋一下為什么需要int n = cap - 1
,為了避免參數(shù)cap本來就是2的冪次方温圆,經(jīng)過后續(xù)的未操作的挨摸,cap將會變成2 * cap,不符合預(yù)期。
為什么擴(kuò)容閥值threshold要保證為2的冪次岁歉?
threshold要保證為2的冪次方主要原因是HashMap中數(shù)據(jù)存儲有關(guān),HashMap中key的Hash值由hash(Object key)方法計(jì)算得來得运。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- HashMap中存儲數(shù)據(jù)table的index是由key的Hash值決定的膝蜈。
- 在HashMap存儲數(shù)據(jù)的時候,我們期望數(shù)據(jù)能夠均勻分布熔掺,以避免哈希沖突饱搏。我們首先可能會想到采用%取余的操作來實(shí)現(xiàn)。
- 取余(%)操作中如果除數(shù)是2的冪次則等價于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的冪次方置逻。
- 采用二進(jìn)制位操作 &推沸,相對于%能夠提高運(yùn)算效率,這就是HashMap的長度為什么是2的冪次方的原因券坞。
- Map參數(shù)構(gòu)造
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) {
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
- 擴(kuò)容因子默認(rèn)值
0.75f
-
putMapEntries
方法將傳入的Map或Map子類傳入放入自身進(jìn)行存儲
put(K key, V value)方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put調(diào)用了內(nèi)部的putVal(hash(key), key, value, false, true)
@param hash -> key.hashCode())^ (h >>> 16) 這里可以認(rèn)為是key的hash值
@param key
@param value
@param onlyIfAbsent 是否不改變現(xiàn)有的值
@param evict 如果為false鬓催,則表處于創(chuàng)建模式。
@return previous value, or null if none
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;//如果是第一次put恨锚,初始化tab
if ((p = tab[i = (n - 1) & hash]) == null)
// 通過hash計(jì)算當(dāng)前的下標(biāo)索引對應(yīng)的value為null宇驾,創(chuàng)建新的鏈表節(jié)點(diǎn)賦值
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))))
// 如果key相同,p賦值給e
e = p;
else if (p instanceof TreeNode)
// 如果p是紅黑樹類型,調(diào)用putTreeVal方式賦值
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//單鏈表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 如果p的next為空猴伶,將新的value值添加至鏈表后面
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // 循環(huán)初始值0 课舍,所以-1
//static final int TREEIFY_THRESHOLD = 8;
// 如果鏈表長度大于等于8,鏈表轉(zhuǎn)化為紅黑樹他挎,執(zhí)行插入
treeifyBin(tab, hash);
break;
}
// key相同則跳出循環(huán)
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;
//已存在相同的key筝尾,覆蓋并返回舊的value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);//給子類的回調(diào)方法
return oldValue;
}
}
++modCount;
if (++size > threshold)
// size大于加載因子,擴(kuò)容
resize();
afterNodeInsertion(evict);//給子類的回調(diào)方法
return null;
}
擴(kuò)容resize()方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {//舊表為空,不是第一次put
// 超過容量最大值就不再擴(kuò)容了办桨,就只好返回舊表 基本不可能發(fā)生
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 沒超過最大值筹淫,擴(kuò)容oldThr << 1,即原來的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;// 新的容量大小 = 舊擴(kuò)容閥值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 計(jì)算新的擴(kuò)容閥值 容量*擴(kuò)容因子 默認(rèn)擴(kuò)容因子0.75
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把每個數(shù)組元素(桶)都移動到新的桶中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
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 { // 保證順序
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
// 原索引+oldCap放到bucket里
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
get(Object key)方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
- 調(diào)用
tab[(n - 1) & hash]
方法獲取到key的hash對應(yīng)的數(shù)組元素(桶) - 調(diào)用
getNode()
方法通過key和hash獲取對應(yīng)的value崔挖。不存在則返回null
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
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 {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- 1.若數(shù)組table不為null且長度大于0且其索引為(n - 1) & hash(等價于hash%n)的節(jié)點(diǎn)不為null贸街。其中n為數(shù)組長度,hash為插入的鍵值對的key的哈希值狸相。則進(jìn)入下一步薛匪,否則直接返回null
- 2.判斷首節(jié)點(diǎn)的key和hash是否與入?yún)⒁恢拢粝嗤瑒t返回首節(jié)點(diǎn)脓鹃,否則進(jìn)入下一步逸尖。
- 3.判斷節(jié)點(diǎn)個數(shù)只有1個,若是則返回null瘸右,否則進(jìn)入下一步
- 4.判斷首節(jié)點(diǎn)是否為樹節(jié)點(diǎn)娇跟,若是則遍歷紅黑樹,否則為鏈表太颤,進(jìn)入下一步
- 5.遍歷鏈表苞俘,檢索key和hash與入?yún)⑾嗤墓?jié)點(diǎn),若找到則返回該節(jié)點(diǎn)龄章,否則返回null
remove(Object key)/remove(Object key, Object value)方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
remove調(diào)用removeNode方法
@param hash -> key.hashCode())^ (h >>> 16) 這里可以認(rèn)為是key的hash值
@param key
@param value 要刪除的鍵值對的value吃谣,該值是否作為刪除的條件取決于matchValue是否為true
@param matchValue 如果為true乞封,則當(dāng)key對應(yīng)的鍵值對的值equals(value)為true時才刪除;否則不關(guān)心value的值
@param movable 刪除后是否移動節(jié)點(diǎn)岗憋,如果為false肃晚,則不移動
@return 返回節(jié)點(diǎn),如果沒有仔戈,則返回null
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index; // 聲明節(jié)點(diǎn)數(shù)組关串、當(dāng)前節(jié)點(diǎn)、數(shù)組長度监徘、索引值
/*
* 如果 節(jié)點(diǎn)數(shù)組tab不為空晋修、數(shù)組長度n大于0、根據(jù)hash定位到的節(jié)點(diǎn)對象p(該節(jié)點(diǎn)為 樹的根節(jié)點(diǎn) 或 鏈表的首節(jié)點(diǎn))不為空
* 需要從該節(jié)點(diǎn)p向下遍歷耐量,找到那個和key匹配的節(jié)點(diǎn)對象
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v; // 定義要返回的節(jié)點(diǎn)對象飞蚓,聲明一個臨時節(jié)點(diǎn)變量滤港、鍵變量廊蜒、值變量
// 如果當(dāng)前節(jié)點(diǎn)的鍵和key相等,那么當(dāng)前節(jié)點(diǎn)就是要刪除的節(jié)點(diǎn)溅漾,賦值給node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
/*
* 到這一步說明首節(jié)點(diǎn)沒有匹配上山叮,那么檢查下是否有next節(jié)點(diǎn)
* 如果沒有next節(jié)點(diǎn),就說明該節(jié)點(diǎn)所在位置上沒有發(fā)生hash碰撞, 就一個節(jié)點(diǎn)并且還沒匹配上添履,也就沒得刪了屁倔,最終也就返回null了
* 如果存在next節(jié)點(diǎn),就說明該數(shù)組位置上發(fā)生了hash碰撞暮胧,此時可能存在一個鏈表锐借,也可能是一顆紅黑樹
*/
else if ((e = p.next) != null) {
// 如果當(dāng)前節(jié)點(diǎn)是TreeNode類型,說明已經(jīng)是一個紅黑樹往衷,那么調(diào)用getTreeNode方法從樹結(jié)構(gòu)中查找滿足條件的節(jié)點(diǎn)
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
// 如果不是樹節(jié)點(diǎn)钞翔,那么就是一個鏈表,只需要從頭到尾逐個節(jié)點(diǎn)比對即可
else {
do {
// 如果e節(jié)點(diǎn)的鍵是否和key相等席舍,e節(jié)點(diǎn)就是要刪除的節(jié)點(diǎn)布轿,賦值給node變量,調(diào)出循環(huán)
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
// 走到這里来颤,說明e也沒有匹配上
p = e; // 把當(dāng)前節(jié)點(diǎn)p指向e汰扭,這一步是讓p存儲的永遠(yuǎn)下一次循環(huán)里e的父節(jié)點(diǎn),如果下一次e匹配上了福铅,那么p就是node的父節(jié)點(diǎn)
} while ((e = e.next) != null); // 如果e存在下一個節(jié)點(diǎn)萝毛,那么繼續(xù)去匹配下一個節(jié)點(diǎn)。直到匹配到某個節(jié)點(diǎn)跳出 或者 遍歷完鏈表所有節(jié)點(diǎn)
}
}
/*
* 如果node不為空滑黔,說明根據(jù)key匹配到了要刪除的節(jié)點(diǎn)
* 如果不需要對比value值 或者 需要對比value值但是value值也相等
* 那么就可以刪除該node節(jié)點(diǎn)了
*/
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode) // 如果該節(jié)點(diǎn)是個TreeNode對象笆包,說明此節(jié)點(diǎn)存在于紅黑樹結(jié)構(gòu)中鲁冯,調(diào)用removeTreeNode方法(該方法單獨(dú)解析)移除該節(jié)點(diǎn)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) // 如果該節(jié)點(diǎn)不是TreeNode對象,node == p 的意思是該node節(jié)點(diǎn)就是首節(jié)點(diǎn)
tab[index] = node.next; // 由于刪除的是首節(jié)點(diǎn)色查,那么直接將節(jié)點(diǎn)數(shù)組對應(yīng)位置指向到第二個節(jié)點(diǎn)即可
else // 如果node節(jié)點(diǎn)不是首節(jié)點(diǎn)薯演,此時p是node的父節(jié)點(diǎn),由于要刪除node秧了,所有只需要把p的下一個節(jié)點(diǎn)指向到node的下一個節(jié)點(diǎn)即可把node從鏈表中刪除了
p.next = node.next;
++modCount; // HashMap的修改次數(shù)遞增
--size; // HashMap的元素個數(shù)遞減
afterNodeRemoval(node); // 調(diào)用afterNodeRemoval方法跨扮,該方法HashMap沒有任何實(shí)現(xiàn)邏輯,目的是為了讓子類根據(jù)需要自行覆寫
return node;
}
}
return null;
}