zoukankan      html  css  js  c++  java
  • HashMap的put()与扩容

    1. put()

     1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     2                    boolean evict) {
     3         HashMap.Node<K,V>[] tab;//指向存储数组的引用
     4         HashMap.Node<K,V> p;
     5         int n, i;//n是数组长度,i是插入值的存储下标
     6         //判断存储数组是否为空,即数组是否初始化
     7         if ((tab = table) == null || (n = tab.length) == 0)
     8             n = (tab = resize()).length;
     9         //计算出存储下标,并将数组中下标与插入值位置相同的赋给p
    10         if ((p = tab[i = (n - 1) & hash]) == null)
    11             //如果p为null则表示插入值的插入位置没有值,则直接插入
    12             tab[i] = newNode(hash, key, value, null);
    13         //p!=null
    14         else {
    15             HashMap.Node<K,V> e;
    16             K k;
    17             //判断p的key是否与插入值的key相等,如果相等,则不需要插入
    18             if (p.hash == hash &&
    19                     ((k = p.key) == key || (key != null && key.equals(k))))
    20                 e = p;
    21             //如果p是红黑树节点
    22             else if (p instanceof HashMap.TreeNode)
    23                 //直接将值插入到红黑树中,在红黑树的插入中同样会进行key是否存在的判断,如果有则返回那个节点,否则返回null
    24                 e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    25             //p是链表节点
    26             else {
    27                 //遍历链表
    28                 for (int binCount = 0; ; ++binCount) {
    29                     //如果链表到头了,则将当前值插入到链表中
    30                     if ((e = p.next) == null) {
    31                         //插入
    32                         p.next = newNode(hash, key, value, null);
    33                         //判断链表的长度是否达到变成红黑树的长度
    34                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    35                             //变成红黑树
    36                             treeifyBin(tab, hash);
    37                         //如果从这里break出去,e的值就为null
    38                         break;
    39                     }
    40                     //如果链表中存在key与插入值的key相同的节点,break
    41                     if (e.hash == hash &&
    42                             ((k = e.key) == key || (key != null && key.equals(k))))
    43                         break;
    44                     p = e;
    45                 }
    46             }
    47             //e!=null表示map中存在key与插入值的key相同,则更新值就行
    48             if (e != null) { // existing mapping for key
    49                 V oldValue = e.value;
    50                 if (!onlyIfAbsent || oldValue == null)
    51                     e.value = value;
    52                 //对于HashMap来说是空函数,没有意义,LinkedHashMap中才有用
    53                 afterNodeAccess(e);
    54                 return oldValue;
    55             }
    56         }
    57         //modCount是数组实际插入次数(根据代码来看,如果没有插入,只是更新的话是不会增加的)
    58         ++modCount;
    59         //size是map中的元素个数(包括链表,红黑树以及数组上的元素),thresold是扩容阈值,如果达到这个值则扩容
    60         if (++size > threshold)
    61             resize();
    62         //对于HashMap来说是空函数,没有意义,LinkedHashMap中才有用
    63         afterNodeInsertion(evict);
    64         return null;
    65     }

    2. 扩容

     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) { // table已初始化
     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; // 容量翻倍
    14         }
    15         else if (oldThr > 0) // table未初始化且已设置阈值,初始容量设为阈值
    16             newCap = oldThr;
    17         else {               // table未初始化且未设置阈值
    18             newCap = DEFAULT_INITIAL_CAPACITY;
    19             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    20         }
    21         if (newThr == 0) { // 1.table未初始化且已设置阈值 2.table已初始化但扩容后容量超过限制 3.table已初始化但原table容量小于默认值
    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; // 更新threshold
    27         @SuppressWarnings({"rawtypes","unchecked"})
    28         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    29         table = newTab; // 更新table
    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; // 从原table中删除正在移动的元素
    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 { // 链表节点
    40                         // 如果对于这里涉及到的低位、高位概念不懂,在我的另一篇博文ConcurrentHashMap源码解析(JDK8)中有介绍
    41                         Node<K,V> loHead = null, loTail = null; // 用于记录低位头结点与尾节点的变量
    42                         Node<K,V> hiHead = null, hiTail = null;// 用于记录高位头结点与尾节点的变量
    43                         Node<K,V> next;
    44                         do { // 遍历链表,遍历出来的两条链表保持原有的顺序
    45                             next = e.next;
    46                             if ((e.hash & oldCap) == 0) { // 表示该节点是低位节点
    47                                 if (loTail == null)
    48                                     loHead = e;
    49                                 else
    50                                     loTail.next = e;
    51                                 loTail = e;
    52                             }
    53                             else { // 表示该节点是高位节点
    54                                 if (hiTail == null)
    55                                     hiHead = e;
    56                                 else
    57                                     hiTail.next = e;
    58                                 hiTail = e;
    59                             }
    60                         } while ((e = next) != null);
    61                         // 将两条链表插入到新table中
    62                         if (loTail != null) {
    63                             loTail.next = null;
    64                             newTab[j] = loHead;
    65                         }
    66                         if (hiTail != null) {
    67                             hiTail.next = null;
    68                             newTab[j + oldCap] = hiHead;
    69                         }
    70                     }
    71                 }
    72             }
    73         }
    74         return newTab;
    75     }

      虽然JDK8没有了扩容时死循环的问题,但是依然是线程不安全的,在多线程环境中请使用ConcurrentHashMap。

  • 相关阅读:
    用存储过程实现二进制字段的更新
    在Google 上搜书的方法 (转)
    Oracle开发中,关于查询的一个问题
    北京公司招聘 .net开发人员
    UIAlertView的使用方法
    FMEFB开始上传
    应用中的安全管理方案
    使用NHibernate时需要考虑的另一个问题
    .net使用Com组件的问题
    使用NHibernate时,如何提高访问Oracle数据库(ODP.net)的性能
  • 原文地址:https://www.cnblogs.com/ouhaitao/p/9805340.html
Copyright © 2011-2022 走看看