• 深究1.8版本HashMap源码


    本篇文章不讲HashMap是什么样的数据结构,

    只讲一下HashMap中用到的比较巧妙的算法,如何去优化扩容的。

    put方法

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }

    在putVal方法之前,对 key 进行了 hash 计算。

        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    通过 hashCode() 方法和无符号左移16后的 hashCode 进行异或计算。 进行了一次扰动计算。

    再看 putVal 方法中。如何计算 key 再数组中的下标。

     1     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     2                    boolean evict) {
     3         Node<K,V>[] tab; Node<K,V> p; int n, i;
     4         if ((tab = table) == null || (n = tab.length) == 0)
     5             n = (tab = resize()).length;    // 初始扩容
     6         if ((p = tab[i = (n - 1) & hash]) == null)
     7             tab[i] = newNode(hash, key, value, null);
     8         else {
     9             Node<K,V> e; K k;
    10             if (p.hash == hash &&
    11                 ((k = p.key) == key || (key != null && key.equals(k))))
    12                 e = p;
    13             else if (p instanceof TreeNode)      // 判断节点是不是红黑树节点
    14                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);    // 加入红黑树中
    15             else {
    16                 for (int binCount = 0; ; ++binCount) {
    17                     if ((e = p.next) == null) {      // 将数据加在链表的尾部
    18                         p.next = newNode(hash, key, value, null);
    19                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    20                             treeifyBin(tab, hash);      // 将链表转换成红黑树
    21                         break;
    22                     }
    23                     if (e.hash == hash &&
    24                         ((k = e.key) == key || (key != null && key.equals(k))))
    25                         break;
    26                     p = e;
    27                 }
    28             }
    29             if (e != null) { // existing mapping for key
    30                 V oldValue = e.value;
    31                 if (!onlyIfAbsent || oldValue == null)
    32                     e.value = value;
    33                 afterNodeAccess(e);
    34                 return oldValue;
    35             }
    36         }
    37         ++modCount;
    38         if (++size > threshold)
    39             resize();      // 扩容
    40         afterNodeInsertion(evict);
    41         return null;
    42     }

    在第 6 行中,   tab[i = (n - 1) & hash]  计算下标,使用了 hash 值跟 n-1 进行位与运算。

    第一次初始容量为 16, hash 值跟 15 进行位 与运算。而 15 的二进制是  1111。得到的是 hash 值的前 4位二进制。所以得到的结果就是,0-15之间的数字,正好是数组下标。

    假如容量已经扩展,那现在的容量为  2n,hash 值跟 2n-1 进行位 与运算, 得到的是 hash 值的前 n 位二进制。

    resize方法

    原本的扩容方式就是将数组变长,对每个节点数据重新计算下标,但是1.8版并不是这样做的。看看源码:

     1     final Node<K,V>[] resize() {
     2         Node<K,V>[] oldTab = table;
     3         int oldCap = (oldTab == null) ? 0 : oldTab.length;
     4         int oldThr = threshold;
     5         int newCap, newThr = 0;
     6         if (oldCap > 0) {
     7             if (oldCap >= MAXIMUM_CAPACITY) {    // 超过最大值,不再扩容,随它怎么碰撞
     8                 threshold = Integer.MAX_VALUE;
     9                 return oldTab;
    10             }
    11             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
    12                      oldCap >= DEFAULT_INITIAL_CAPACITY)
    13                 newThr = oldThr << 1; // double threshold    // 否则,将原数组大小扩充一倍
    14         }
    15         else if (oldThr > 0) // initial capacity was placed in threshold
    16             newCap = oldThr;
    17         else {               // zero initial threshold signifies using defaults
    18             newCap = DEFAULT_INITIAL_CAPACITY;
    19             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    20         }
    21         if (newThr == 0) {
    22             float ft = (float)newCap * loadFactor;
    23             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    24                       (int)ft : Integer.MAX_VALUE);
    25         }
    26         threshold = newThr;
    27         @SuppressWarnings({"rawtypes","unchecked"})
    28             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    29         table = newTab;
    30         if (oldTab != null) {
    31             for (int j = 0; j < oldCap; ++j) {
    32                 Node<K,V> e;
    33                 if ((e = oldTab[j]) != null) {
    34                     oldTab[j] = null;
    35                     if (e.next == null)
    36                         newTab[e.hash & (newCap - 1)] = e;
    37                     else if (e instanceof TreeNode)
    38                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
    39                     else { // preserve order
    40                         Node<K,V> loHead = null, loTail = null;
    41                         Node<K,V> hiHead = null, hiTail = null;
    42                         Node<K,V> next;
    43                         do {
    44                             next = e.next;
    45                             if ((e.hash & oldCap) == 0) {    // 将hash 与 16 这种 2n 进行 与操作,计算新增的一位bit 是0,还是 1。
    46                                 if (loTail == null)
    47                                     loHead = e;
    48                                 else
    49                                     loTail.next = e;
    50                                 loTail = e;
    51                             }
    52                             else {
    53                                 if (hiTail == null)
    54                                     hiHead = e;
    55                                 else
    56                                     hiTail.next = e;
    57                                 hiTail = e;
    58                             }
    59                         } while ((e = next) != null);
    60                         if (loTail != null) {      // 0 该节点还存原下标位置
    61                             loTail.next = null;
    62                             newTab[j] = loHead;
    63                         }
    64                         if (hiTail != null) {      // 1  该节点由原下标位置向后移动 2n 位置
    65                             hiTail.next = null;
    66                             newTab[j + oldCap] = hiHead;
    67                         }
    68                     }
    69                 }
    70             }
    71         }
    72         return newTab;
    73     }

    新版 hashMap 对扩容进行了优化,当容量扩充为原来的 2 倍时,只需判断新增的一位 bit 是0还是1。

    例如,当由 16 扩充至 32 时,16 的 二进制时 10000,与 hash 计算,得到的是第 5 位 bit 的值。

    原本计算下标,只是与前 4 位进行与运算,现在扩容一次后是对前 5 位进行与运算。

    扩容方法里面并没有重新计算所有位的与运算,只需判断新增的第 5 位。减少了计算量。

    原创文章,如有什么不对,欢迎指出。

  • 相关阅读:
    codeforces
    codeforces
    HDU
    poj
    poj
    HDU Problem
    HDU 3555 Bomb 【数位dp】
    POJ 1942 Paths on a Grid【组合数学】
    杭电 Problem 2089 不要62 【数位dp】
    codeforces 486c-Palindrome Transformation【贪心】
  • 原文地址:https://www.cnblogs.com/mmling/p/11815406.html
Copyright © 2020-2023  润新知