《精要主義》書中的一些感悟:
幾乎沒有事物的次要性,再怎麼高估都不過分。試着利用精要主義打破用忙碌衡量成功的淺見吧。
無論是在工作or面試中,HashMap都是我們經常打交道的。所以今天我們源碼的角度來窺探其奧妙。
1.HashMap的結構
1.1 JDK1.7版本
在JDK1.7以前的設計,HashMap採用數組+單鏈表的實現方式,鏈表就是用來處理Hash衝突的。
學過數據結構的人都知道,鏈表有個缺點:查找速度慢因爲必須從頭查詢,時間複雜度是O(n)
1.2 JDK1.8版本
所以在JDK 1.8 對這裏做了優化,因爲我們知道樹型結構可以將查找時間複雜度降爲O(lg n)
在鏈表節點數量超過一定量(8)時,則將鏈表轉化爲紅黑樹, 以後找時間專門來了解一下紅黑樹。
1.3 關於HashMap結構的總結
HashMap的結構
JDK 1.7 : 數組 + 單鏈表 (注意這裏強調一下單)
JDK 1.8 :數組 + 單鏈表 + 紅黑樹
1.(單)鏈表的目的:解決了Key的hash衝突問題;
2. 紅黑樹的目的:加快查詢速度;
2. 設計HashMap
2.1 HashMap中的常量值
- 默認容量16;
- 最大容量2^30;
- 默認負載因子 0.75
- 可樹化的閥值 8 (鏈表—> 紅黑樹);
- 去樹化的閥值 6 (紅黑樹–>鏈表 );
- 哈希表考慮樹形化的最小數量 64;
/**
* 默認的初始化變量(16)-必須是2的冪(次方)
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量(必須是2的冪且 <= 2的30次方)
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默認的裝載因子(填充比例):0.75,即實際元素所佔所分配的容量的75%時就要擴容啦。
* 如果該值過大,再增加元素將導致鏈表過長,查找效率降低;反之,浪費空間
* 關注內存:適當增大填充比;
* 關注查找性能: 適當減小填充比;
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 每個桶的可樹化的閥值,即當桶中元素個數超過該值時,自動將鏈表轉化爲紅黑樹結構
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 每個桶的去樹化閥值,即在擴容(resize())時,發現桶中的元素少於該值,則將樹形結構還原爲鏈表結構
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 哈希表中可被樹形化的最小數量。
* 即若哈希表中的元素個數少於該值,則桶(bin)不會被樹形化,而是會進行擴容(就算桶中的元素個數超過TREEIFY_THRESHOLD)。
* 主要是爲了避免擴容&可樹化的選擇衝突
*/
static final int MIN_TREEIFY_CAPACITY = 64;
HashMap的跟容量相關的都必須是2的N次方。
2.2 HashMap中的變量
2.2.1 關鍵字-transient
用transient關鍵字標記的成員變量不參與序列化過程,即不會被序列化
2.2.2 HashMap中的變量
如果我們在設計HashMap時,這些基本我們都是要進行考慮的。(PS: 調整次數modCount 確實不知道有什麼用, ~233333)
/***
* 我們都知道HashMap底層數據結構是:數組+鏈表
* 這個參數就是數組,即我們常說的:桶數組。
* table 在「第一次使用時進行初始化」並在需要的時候重新調整自身大小。對於 table 的大小必須是2的冪次方。
*/
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* Map中的鍵值對的數量
*/
transient int size;
/**
* HashMap 進行結構性調整的次數。結構性調整指的是增加或者刪除鍵值對等操作,注意對於更新某個鍵的值不是結構特性調整。
*/
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
* Map的容量的閥值:表的大小 capacity * load factor,達到這個容量時進行擴容操作。
*/
int threshold;
/**
* The load factor for the hash table.
* 負載因子,默認值爲 0.75
*/
final float loadFactor;
目前爲止,我們只看到了HashMap結構中的桶數組: transient Node<K,V>[] table;
看鏈表其實就是看節點Node的。
2.3 HashMap鏈表節點
單鏈表
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
* Node 實現了Map.Entry接口
*/
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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
// 注意這裏,設置新值的同時返回舊值
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
2.4 HashMap紅黑樹節點
注意這裏繼承的是LinkedHashMap.LinkedHashMapEntry<K,V>
LinkedHashMap.LinkedHashMapEntry<K,V> 是雙向鏈表,本質上還是繼承自Node的。
static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
...
}
這篇文章就到這裏,乾貨貌似不多…
因爲這篇文章的目的主要是讓大家對於HashMap有個概括上的認識。
歡迎繼續閱讀下一篇 深入理解Java之HashMap —— 02