HashMap

hash

-- JDK 1.8 中树化之后默认按照hashCode排序,如果对象实现了compareTo方法,则会按照对应的方法排序

  



// 将hashCode的高位与低位异或,从而使得高位可以影响哈希值,以减少哈希碰撞
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


 

 

getNode



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;
}


 

 

put

---- JDK1.7中采用头插法,JDK1.8中采用尾插法;

头插法在多线程环境下可能会导致循环链表,从而程序死循环。

 

---- 第一次插入节点需要初始化列表

---- 如果插入位置为空,直接插入新节点;

|-- 否则判断节点key是否相同,相同则替换新值;

|-- 不相同,则根据节点类型插入数据;

|-- 如果是树节点,则将节点插入树,如果是链表,则需要先遍历链表查找是否存在相同的key,不存在则在尾部插入新节点;

---- 最后判断长度是否超过8,哈希桶数组是否达到64,超过则进行树化。



// 插入值
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;

// 如果hash位置为空,则直接存入新的节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 新值的hash和key与数组中的相等,则在后面的代码中替换值即可
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果p是树,则插入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;
}
// 找到key相等的,则替换值
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;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}


 

 

resize



1 // 容量扩大
2 final Node<K,V>[] resize() {
3 Node<K,V>[] oldTab = table;
4
5 // 计算新的容量和阈值
6 int oldCap = (oldTab == null) ? 0 : oldTab.length;
7 int oldThr = threshold;
8 int newCap, newThr = 0;
9 if (oldCap > 0) {
10 // 容量已经达到最大
11 if (oldCap >= MAXIMUM_CAPACITY) {
12 threshold = Integer.MAX_VALUE;
13 return oldTab;
14 }
15 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
16 oldCap >= DEFAULT_INITIAL_CAPACITY)
17 newThr = oldThr << 1; // double threshold
18 }
19
20 else if (oldThr > 0) // initial capacity was placed in threshold
21 newCap = oldThr;
22
23 // new HashMap<>() 未指定长度
24 else { // zero initial threshold signifies using defaults
25 newCap = DEFAULT_INITIAL_CAPACITY;
26 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
27 }
28 if (newThr == 0) {
29 float ft = (float)newCap * loadFactor;
30 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
31 (int)ft : Integer.MAX_VALUE);
32 }
33 threshold = newThr;
34
35 // 进行扩容和rehash
36 @SuppressWarnings({"rawtypes","unchecked"})
37 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
38 table = newTab;
39 if (oldTab != null) {
40 for (int j = 0; j < oldCap; ++j) {
41 Node<K,V> e;
42 if ((e = oldTab[j]) != null) {
43 oldTab[j] = null;
44 // e是链表节点且只有e一个节点
45 if (e.next == null)
46 newTab[e.hash & (newCap - 1)] = e;
47 // e是树节点
48 else if (e instanceof TreeNode)
49 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50 // e是链表且有多个节点
51 else { // preserve order
52 // lo为rehash后与原位置相同的链表,hi为位置不同的链表
53 Node<K,V> loHead = null, loTail = null;
54 Node<K,V> hiHead = null, hiTail = null;
55 Node<K,V> next;
56 do {
57 next = e.next;
58 // 直接hash计算位置时为 e.hash & (oldCap-1), 即oldCap=16时,oldCap-1=1111b
59 // 假设原容量为16,即oldCap=16 = 10000b
60 // e.hash低5位为01010,原始位置为 e.hash & 10000b = 00000b
61 // e.hash第5位可能为0或1,则新的位置为 e.hash & 10000b = (0/1)0000b
62 if ((e.hash & oldCap) == 0) {
63 if (loTail == null)
64 loHead = e;
65 else
66 loTail.next = e;
67 loTail = e;
68 }
69 else {
70 if (hiTail == null)
71 hiHead = e;
72 else
73 hiTail.next = e;
74 hiTail = e;
75 }
76 } while ((e = next) != null);
77
78 // 低链表位置不变
79 if (loTail != null) {
80 loTail.next = null;
81 newTab[j] = loHead;
82 }
83 // 高链表放到新的位置 即 j + oldCap
84 if (hiTail != null) {
85 hiTail.next = null;
86 newTab[j + oldCap] = hiHead;
87 }
88 }
89 }
90 }
91 }
92 return newTab;
93 }
94


 

remove



1 final Node<K,V> removeNode(int hash, Object key, Object value,
2 boolean matchValue, boolean movable) {
3 Node<K,V>[] tab; Node<K,V> p; int n, index;
4
5 // 找到需要删除的节点
6 if ((tab = table) != null && (n = tab.length) > 0 &&
7 (p = tab[index = (n - 1) & hash]) != null) {
8 Node<K,V> node = null, e; K k; V v;
9
10 // 情况1,hash列表中当前位置仅一个节点
11 if (p.hash == hash &&
12 ((k = p.key) == key || (key != null && key.equals(k))))
13 node = p;
14 else if ((e = p.next) != null) {
15
16 // 情况2,hash列表中当前位置为一棵树
17 if (p instanceof TreeNode)
18 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
19
20 // 情况3,当前位置为一个链表
21 else {
22 do {
23 if (e.hash == hash &&
24 ((k = e.key) == key ||
25 (key != null && key.equals(k)))) {
26 node = e;
27 break;
28 }
29 p = e;
30 } while ((e = e.next) != null);
31 }
32 }
33
34 // 进行节点的删除
35 if (node != null && (!matchValue || (v = node.value) == value ||
36 (value != null && value.equals(v)))) {
37 if (node instanceof TreeNode)
38 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
39 else if (node == p)
40 tab[index] = node.next;
41 else
42 p.next = node.next;
43 ++modCount;
44 --size;
45 afterNodeRemoval(node);
46 return node;
47 }
48 }
49 return null;
50 }
51


 

树的退化:如果 、、 或者 为空,则将树退化为链表:



1 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
2 boolean movable){
3 // ......
4 if (root == null
5 || (movable
6 && (root.right == null
7 || (rl = root.left) == null
8 || rl.left == null))) {
9 tab[index] = first.untreeify(map); // too small
10 return;
11 }
12 //......
13 }


 

 

split

将红黑树进行rehash和拆分



1 /**
2 * Splits nodes in a tree bin into lower and upper tree bins,
3 * or untreeifies if now too small. Called only from resize;
4 * see above discussion about split bits and indices.
5 */
6 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
7 TreeNode<K,V> b = this;
8 // Relink into lo and hi lists, preserving order
9 TreeNode<K,V> loHead = null, loTail = null;
10 TreeNode<K,V> hiHead = null, hiTail = null;
11 int lc = 0, hc = 0;
12
13 // TreeNode 继承了 LinkedHashMap.Entry, 后者又继承了 HashMap.Node
14 // 所以TreeNode 也存在next域,向红黑树插入TreeNode时会将上一个插入结点的next域指向当前插入的TreeNode
15
16 // 将树拆分为两个链表
17 for (TreeNode<K,V> e = b, next; e != null; e = next) {
18 next = (TreeNode<K,V>)e.next;
19 e.next = null;
20 if ((e.hash & bit) == 0) { // bit = oldCap ,在resize()中传入
21 if ((e.prev = loTail) == null)
22 loHead = e;
23 else
24 loTail.next = e;
25 loTail = e;
26 ++lc;
27 }
28 else {
29 if ((e.prev = hiTail) == null)
30 hiHead = e;
31 else
32 hiTail.next = e;
33 hiTail = e;
34 ++hc;
35 }
36 }
37
38 if (loHead != null) {
39 // 如果小于退化阈值,则将树退化为链表
40 if (lc <= UNTREEIFY_THRESHOLD)
41 tab[index] = loHead.untreeify(map);
42 else {
43 // 否则保持红黑树状态
44 tab[index] = loHead;
45 // 如果hiHead不为空,则说明有部分节点在hiHead处,需要对loHead重新树化;
46 // 如果hiHead为空,则说明所有节点在hiHead这边,树的形态没有得到破环,不需要重新树化。
47 if (hiHead != null) // (else is already treeified)
48 loHead.treeify(tab);
49 }
50 }
51 if (hiHead != null) {
52 if (hc <= UNTREEIFY_THRESHOLD)
53 tab[index + bit] = hiHead.untreeify(map);
54 else {
55 tab[index + bit] = hiHead;
56 if (loHead != null)
57 hiHead.treeify(tab);
58 }
59 }
60 }


 

 

QA

为什么负载因子是0.75?

选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。

当负载因子是1.0的时候,也就意味着,只有当数组的8个值(这个图表示了8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。

当负载因子太小时,虽然时间效率提升了,但是空间利用率降低了。

在理想情况下,使用随机哈希码,在扩容阈值(加载因子)为0.75的情况下,节点出现在频率在Hash桶(表)中遵循参数平均为0.5的泊松分布。计算结果如上述的列表所示,当一个bin中的链表长度达到8个元素的时候,概率为0.00000006,几乎是一个不可能事件。

在维基百科来描述加载因子:

对于开放定址法,加载因子是特别重要因素,应严格限制在0.7-0.8以下。超过0.8,查表时的CPU缓存不命中(cache missing)按照指数曲线上升。因此,一些采用开放定址法的hash库,如Java的系统库限制了加载因子为0.75,超过此值将resize散列表。