HashMap源码分析

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散列表。

HashMap源码分析

上一篇:LeetCode 20. 有效的括号(Valid Parentheses)


下一篇:快排的改进