• Java | JDK8 | HashMap


     

    从添加一个key~value操作分析起,putVal是一个用final修饰的方法,可知是一个不予许被重写的方法,

    传入key的hash值,相对应key~value,

    将onlyIfAbsent设置false,意味着当传入的key已经存在时,当前的value会替换掉oldValue,否则只有当value为null时当前value才会替换oldValue

    将evict设置为true,目前class中没有具体实现,可以认为没有意义。文档表达:if false, the table is in creation mode.

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

     

    分析hash(key)

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

    通过key.hashCode()计算出key的哈希值,然后将哈希值h右移16位,再与原来的h做异或^运算。

     

    补充:

    “>>>”:按二进制形式把所有的数字向右移动对应位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。 其他结构和>>相似。

    “^” : 位异或第一个操作数的的第n位于第二个操作数的第n位相反,那么结果的第n为也为1,否则为0 0^0=0, 1^0=1, 0^1=1, 1^1=0

     

    主干流程分析:

     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      //step1:如果初次使用hashMap,进行初始化
     5      if ((tab = table) == null || (n = tab.length) == 0)
     6          n = (tab = resize()).length;
     7      //step2:通过hash和(长度-1)做与操作寻址key索引位置-i,当tab[i]没有存值时直接加入,否则做hash冲突处理
     8      if ((p = tab[i = (n - 1) & hash]) == null)
     9          tab[i] = newNode(hash, key, value, null);
    10      else {
    11          Node<K,V> e; K k;
    12          //step3:判断待添加的key与首元素的key是否相同
    13          if (p.hash == hash &&
    14              ((k = p.key) == key || (key != null && key.equals(k))))
    15              e = p;
    16          //step4:判断是否为红黑树结构,若是则进入到另一个方法处理
    17          else if (p instanceof TreeNode)
    18              //putTreeVal内部进行了遍历,存在相同hash时返回被覆盖的TreeNode,否则返回null。
    19              e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    20          //step5:否则是链表结构,执行相关操作,检查是否存在相同的key,否则添加到尾部,并进行检查是否转成红黑树【当链表长度大于等于8进行转换】
    21          else {
    22              for (int binCount = 0; ; ++binCount) {
    23                  if ((e = p.next) == null) {
    24                      p.next = newNode(hash, key, value, null);
    25                      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    26                          treeifyBin(tab, hash);
    27                      break;
    28                  }
    29                  if (e.hash == hash &&
    30                      ((k = e.key) == key || (key != null && key.equals(k))))
    31                      break;
    32                  p = e;
    33              }
    34          }
    35          //step6:对已经有存在相同的key进行操作,返回oldValue
    36          //根据onlyIfAbsent,onlyIfAbsent为false时,意味着当传入的key已经存在时,当前的value会替换掉oldValue,否则只有当value为null时当前value才会替换oldValue
    37          if (e != null) { // existing mapping for key
    38              V oldValue = e.value;
    39              if (!onlyIfAbsent || oldValue == null)
    40                  e.value = value;
    41              afterNodeAccess(e);
    42              return oldValue;
    43          }
    44      }
    45      //step7:对新增元素操作,判断是否达需要扩容
    46      ++modCount;
    47      if (++size > threshold)
    48          resize();
    49      afterNodeInsertion(evict);
    50      return null;
    51  }

     

    Q:寻址操作 (n - 1) & hash,为什么是n?

    在table数组中寻址,利用了按位与操作的一个特点,1&1 才能为 1,那么也就是说,如果数组最大下标为15,那么不管hash是多少都不会大于15,也就不会数组越界

     

    Q:如何判断是否扩容?

    首先看下threshold的初始化

      /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
     //相当于2的30次方
     static final int MAXIMUM_CAPACITY = 1 << 30;
     //threshold = 初始容量 * 加载因子。是扩容的门槛。相当于实际使用的容量。
     int threshold;
     
     //int最大值为2^31,当赋予的初始值比MAXIMUM_CAPACITY大时,这时候再扩容threshold就会造成整型溢出。
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
     this.threshold = tableSizeFor(initialCapacity);
     
     //返回一个大于等于且最接近 cap 的2的幂次方整数,如给定9,返回2的4次方16
     static final int tableSizeFor(int cap) {
         //让cap-1再赋值给n的目的是另找到的目标值大于或等于原值
         int n = cap - 1;
         n |= n >>> 1;
         n |= n >>> 2;
         n |= n >>> 4;
         n |= n >>> 8;
         n |= n >>> 16;
         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
     }
     

    再看扩容操作

     1  //当HashMap中存在的node节点大于threshold时,hashmap进行扩容。
     2  if (++size > threshold)
     3          resize();
     4  5    final Node<K,V>[] resize() {
     6          Node<K,V>[] oldTab = table;
     7          int oldCap = (oldTab == null) ? 0 : oldTab.length;
     8          int oldThr = threshold;
     9          int newCap, newThr = 0;
    10          if (oldCap > 0) {
    11              //当容量预扩容比2^30大时,停止扩容
    12              if (oldCap >= MAXIMUM_CAPACITY) {
    13                  threshold = Integer.MAX_VALUE;
    14                  return oldTab;
    15              }
    16              //以自身两倍容量进行扩容
    17              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
    18                       oldCap >= DEFAULT_INITIAL_CAPACITY)
    19                  newThr = oldThr << 1; // double threshold
    20          }
    21          else if (oldThr > 0) // initial capacity was placed in threshold
    22              newCap = oldThr;
    23          else {               // zero initial threshold signifies using defaults
    24              newCap = DEFAULT_INITIAL_CAPACITY;
    25              newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    26          }
    27          if (newThr == 0) {
    28              float ft = (float)newCap * loadFactor;
    29              newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    30                        (int)ft : Integer.MAX_VALUE);
    31          }
    32          threshold = newThr;
    33          @SuppressWarnings({"rawtypes","unchecked"})
    34              Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    35          table = newTab;
    36          if (oldTab != null) {
    37              for (int j = 0; j < oldCap; ++j) {
    38                  Node<K,V> e;
    39                  if ((e = oldTab[j]) != null) {
    40                      oldTab[j] = null;
    41                      if (e.next == null)
    42                          newTab[e.hash & (newCap - 1)] = e;
    43                      else if (e instanceof TreeNode)
    44                          ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
    45                      else { // preserve order
    46                          Node<K,V> loHead = null, loTail = null;
    47                          Node<K,V> hiHead = null, hiTail = null;
    48                          Node<K,V> next;
    49                          do {
    50                              next = e.next;
    51                              if ((e.hash & oldCap) == 0) {
    52                                  if (loTail == null)
    53                                      loHead = e;
    54                                  else
    55                                      loTail.next = e;
    56                                  loTail = e;
    57                              }
    58                              else {
    59                                  if (hiTail == null)
    60                                      hiHead = e;
    61                                  else
    62                                      hiTail.next = e;
    63                                  hiTail = e;
    64                              }
    65                          } while ((e = next) != null);
    66                          if (loTail != null) {
    67                              loTail.next = null;
    68                              newTab[j] = loHead;
    69                          }
    70                          if (hiTail != null) {
    71                              hiTail.next = null;
    72                              newTab[j + oldCap] = hiHead;
    73                          }
    74                      }
    75                  }
    76              }
    77          }
    78          return newTab;
    79      }
  • 相关阅读:
    创建 demo项目表
    安装 vue-devtools
    安装 Visual Studio Code
    切面拦截过滤日志
    Mysql 创建自增序列表
    [编译] 8、在Linux下搭建 stm8 单片机的开发烧写环境(makefile版)
    [蓝牙嗅探-Ubertooth One] 千元开源蓝牙抓包 Ubertooth One 安装和使用
    微信客服接口文档
    七牛云违规删除问题
    好用的团队协助软件推荐
  • 原文地址:https://www.cnblogs.com/jj81/p/11545470.html
Copyright © 2020-2023  润新知