餘生很長,學精學透。
常量
源碼中定義的一些常量和方法都表示爲靜態變量,如下:
// 默認容積
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容積
static final int MAXIMUM_CAPACITY = 1 << 30;
// 當存量達到容積的0.75倍時,擴容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 當桶中bin的數量超過該閾值時,不再用鏈表存儲,改用樹存儲(紅黑樹)
static final int TREEIFY_THRESHOLD = 8;
// 當桶中bin數量小於該值時,不再用樹存儲,改用鏈表存儲
static final int UNTREEIFY_THRESHOLD = 6;
// 被樹化時的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
計算哈希值的方法:
static final int hash(Object key){};
當然,還有一些信息是不希望被序列化的,用transient修飾了:
// 存儲node節點的表
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;
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
主要常量這些,還有一些默認修飾的,如threshold等;
構造函數
一共有4種。
/*
* 傳入容量和裝載因子
*/
public HashMap(int initialCapacity, float loadFactor) {}
/*
* 傳入容量,裝載因子用默認的
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/*
* 容量和裝載因子都使用默認的
*/
public HashMap(){}
/*
* 參數使用默認的,然後把注入的map一個個傳給本HashMap
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
Node類
默認情況下,存在HashMap中的鍵值對都是以鏈表的形式存放在node類中。代碼也並不複雜,如下:
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
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;
}
}
裏面就存放 了hash值、key、value和nextNode。
TreeNode類
實現比較複雜,繼承了LinkedHashMap.Entry<K,V>,而後者是繼承的HashMap.Node<K,V>,皮球繞了一圈又回到了Map.Entry<K,V>。紅黑樹比較複雜,先了解紅黑樹再看源碼,紅黑樹的五大性質:
1)每個結點要麼是紅的,要麼是黑的。
2)根結點是黑的。
3)每個葉結點,即空結點(NIL)是黑的。
4)如果一個結點是紅的,那麼它的倆個兒子都是黑的。
5)對每個結點,從該結點到其子孫結點的所有路徑上包含相同數目的黑結點。
紅黑樹比較複雜,還沒看懂……
在何種情況下,單向鏈表會轉換成treenode?
在HashMap最開始的註釋中,轉換的情況是這樣描述的:
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins. In
* usages with well-distributed user hashCodes, tree bins are
* rarely used. Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)).
我試着翻譯一下:
鑑於TreeNodes一般都是有規則節點(鏈表節點?)的兩倍長度,所以我們只有在節點足夠多的時候才使用TreeNode。當節點數減少到足夠小的時候,TreeNodes就會轉換爲普通節點(單向鏈表)。在使用的哈希值分佈比較好的時候,很少使用樹。理想情況下,在閾值爲0.75時節點在箱中的分佈遵循一個平均參數爲0.5的泊松分佈,雖然調整粒度但方差還是挺大的。忽略方差,期望分佈滿足這個公式。
好吧!這裏只是說節點多的情況下會轉換爲樹,還是沒有寫清楚具體啥時候轉。既然前面已經說了,最開始是regular node,那說明插入數據達到一定程度後,就將錶轉爲樹,看put代碼順藤摸瓜,在putVal方法中有提現:
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是判斷是否哈希值、key都相同,就是判斷是不是插入同樣的數據;第二else if 判斷是否已經是樹了;第三個是正常插入操作,看代碼可以看出來,是一個無限循環,直到找到節點p的next節點是空的,或者p的next節點就是這個插入的數據,如果p一直往next節點找,找到空並且箱數超過限定的轉樹閾值(默認是8),那就執行treeifyBin操作,即把table轉換爲紅黑樹。
所以,重點看看這個方法。
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
從這個方法我們就可以看出,桶數組中,如果對應的某個桶的單向鏈路中bincount大於7,且桶的capacity大於64(均默認情況下),那麼此鏈路就會轉換爲紅黑樹,而桶數組的其他單向鏈路則仍然是單向鏈路,不需要桶化。