ToB企服应用市场:ToB评测及商务社交产业平台

标题: hashMap get put resize方法源码解析 [打印本页]

作者: 美食家大橙子    时间: 2022-10-27 12:50
标题: hashMap get put resize方法源码解析
hashMap get put resize方法源码解析

hashMap源码学习

简单介绍一下hashMap,hashMap的顶级父类接口为Map为key-value存贮,在在根据key查找单个元素时时间复杂度为ON(1),但是不能保证元素顺序,即元素存进去和取出来的顺序不一致,在jdk1.7采用数组+链表实现线程不安全,但是在大量存贮元素时可能会出现某种极端情况,链表过长(或元素全部存贮到一条链表上),查找元素变慢;在jdk1.8时为了解决这个问题,hashMap底层使用了数组+链表+红黑树的方式实现,当链表元素过长时jdk将会把链表转化为红黑树来增加查找速率,但1.8的hashMap仍然不是线程安全的。
为什么jdk1.8采用红黑树而不采用其他的树:
而二叉树在有种极端情况,所有元素全部存储到树的一边上,这样的话树在查找某个元素时就和链表类似,没有体现出树的优势,而二叉树是一种平衡二叉树,树的左右子树高度相差不超过一,超过时通过左旋或右旋调整。
参考https://juejin.cn/post/6844903519632228365#comment
下面我们来看一下具体jdk1.8底层是怎么实现的:
静态变量
静态变量来定制hashMpa中一些规范如:初始容量、扩容阈值等
  1.     /**
  2.      * The default initial capacity - MUST be a power of two.
  3.      * 默认初始容量-必须是2的幂。
  4.      * 初始容量
  5.      */
  6.     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  7.     /**
  8.      * The maximum capacity, used if a higher value is implicitly specified
  9.      * by either of the constructors with arguments.
  10.      * MUST be a power of two <= 1<<30.
  11.      * 如果隐式指定了更高的值,则使用最大容量由带有参数的构造函数之一执行。
  12.      * 必须是2的幂<=1<<30。
  13.      * HashMap的最大容量
  14.      */
  15.     static final int MAXIMUM_CAPACITY = 1 << 30;
  16.     /**
  17.      * The load factor used when none specified in constructor.
  18.      * 构造函数中未指定时使用的负载系数
  19.      * 容量到达容器的0.75时HashMap将会进行扩容操作
  20.      */
  21.     static final float DEFAULT_LOAD_FACTOR = 0.75f;
  22.     /**
  23.      * The bin count threshold for using a tree rather than list for a
  24.      * bin.  Bins are converted to trees when adding an element to a
  25.      * bin with at least this many nodes. The value must be greater
  26.      * than 2 and should be at least 8 to mesh with assumptions in
  27.      * tree removal about conversion back to plain bins upon
  28.      * shrinkage.
  29.      * 对于bin使用树而不是列表的bin计数阈值。将元素添加到具有至少这么多节点的bin时,
  30.      * bin将转换为树。该值必须大于2,并且至少应为8,
  31.      * 以便与树木移除中关于收缩时转换回普通箱的假设相匹配
  32.      *
  33.      * 当链表长度超过8时HashMap会将链表转换为红黑树
  34.      * HashMap会优先将数组扩容到64时才会进行红黑树的转化
  35.      */
  36.     static final int TREEIFY_THRESHOLD = 8;
  37.     /**
  38.      * The bin count threshold for untreeifying a (split) bin during a
  39.      * resize operation. Should be less than TREEIFY_THRESHOLD, and at
  40.      * most 6 to mesh with shrinkage detection under removal.
  41.      * 在调整大小操作过程中取消筛选(拆分)垃圾箱的垃圾箱计数阈值。
  42.      * 应小于TREEIFY_THRESHOLD,最多为6,以便在移除时进行收缩检测
  43.      *
  44.      */
  45.     static final int UNTREEIFY_THRESHOLD = 6;
  46.     /**
  47.      * The smallest table capacity for which bins may be treeified.
  48.      * (Otherwise the table is resized if too many nodes in a bin.)
  49.      * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
  50.      * between resizing and treeification thresholds.
  51.          * 可将箱子树化的最小工作台容量。
  52.          *(否则,如果bin中的节点过多,将调整表的大小。)
  53.          * 应至少为4*TREEIFY_THRESHOLD以避免冲突
  54.          * 在调整大小和树化阈值之间。
  55.          *
  56.          * 当数组长度到达64时且链表长度大于等于8时链表转换为红黑树
  57.      */
  58.     static final int MIN_TREEIFY_CAPACITY = 64;
复制代码
get相关方法和属性
  1.         //初始无参构造
  2.         //Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
  3.         //使用默认初始容量(16)和默认负载因子(0.75)构建一个空的HashMap
  4.     public HashMap() {
  5.         this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  6.     }
  7.     /**
  8.      * Constructs an empty <tt>HashMap</tt> with the specified initial
  9.      * capacity and the default load factor (0.75).
  10.      *
  11.      * @param  initialCapacity the initial capacity.
  12.      * @throws IllegalArgumentException if the initial capacity is negative.
  13.      */
  14.         //使用指定的初始容量和默认负载因子(0.75)构造空HashMap。
  15.         //参数: initialCapacity–初始容量。
  16.         //抛出: IllegalArgumentException–如果初始容量为负数
  17.         //构造一个含有初始容量的HashMap
  18.     public HashMap(int initialCapacity) {
  19.         this(initialCapacity, DEFAULT_LOAD_FACTOR);
  20.     }
  21.     /**
  22.      * Constructs an empty <tt>HashMap</tt> with the specified initial
  23.      * capacity and load factor.
  24.      *
  25.      * @param  initialCapacity the initial capacity 初始化容量
  26.      * @param  loadFactor      the load factor      负载因子
  27.      * @throws IllegalArgumentException if the initial capacity is negative
  28.      *         or the load factor is nonpositive
  29.      */
  30.         //使用外部传入的HashMap的初始化容量和负载因子
  31.     public HashMap(int initialCapacity, float loadFactor) {
  32.         if (initialCapacity < 0) //判断外部传入的初始化容量是否小于0,小于0则抛出异常
  33.             throw new IllegalArgumentException("Illegal initial capacity: " +
  34.                                                initialCapacity);
  35.         if (initialCapacity > MAXIMUM_CAPACITY) //判断初始化容量是否超过最大容量
  36.             initialCapacity = MAXIMUM_CAPACITY; //如果大于最大容量则使用最大容量
  37.         if (loadFactor <= 0 || Float.isNaN(loadFactor)) //判断负载因子是否小于0或者不是数字
  38.             throw new IllegalArgumentException("Illegal load factor: " +
  39.                                                loadFactor); //抛出异常
  40.         this.loadFactor = loadFactor;                                 //负载因子赋值
  41.         this.threshold = tableSizeFor(initialCapacity); //将初始化容量先赋给容器界限
  42.     }
  43.        
  44.         //Returns a power of two size for the given target capacity.
  45.         //返回给定目标容量的两次幂。
  46.         //得到一个大于获等于他的2的幂并返回
  47.         static final int tableSizeFor(int cap) {
  48.         // >>>为无符号右移 将数字转换为二进制向右移动 高位补0
  49.         // |为或运算,两个数转化为二进制,有1则为1,全0则为0
  50.         // |= 与+=类似 a|=b 等价于 a=a|b
  51.         
  52.         //防止传进来的数本身就是2^n,如果不减一则传出的值为2^n+1
  53.         int n = cap - 1;
  54.         //右移一位并与自己进行或运算,则原来是1则变成11。
  55.         //例如传进来数为100001001,右移一位得到0110000100,与自己进行或运算得到110001101
  56.         //一位1变成两位1,所以下面各右移1、2、4、8、16位
  57.         n |= n >>> 1;
  58.         n |= n >>> 2;
  59.         n |= n >>> 4;
  60.         n |= n >>> 8;
  61.         n |= n >>> 16;
  62.         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  63.     }
  64.        
复制代码
put添加相关方法和属性
[code]    /**     * Associates the specified value with the specified key in this map.     * If the map previously contained a mapping for the key, the old     * value is replaced.     *     * @param key key with which the specified value is to be associated     * @param value value to be associated with the specified key     * @return the previous value associated with key, or     *         null if there was no mapping for key.     *         (A null return can also indicate that the map     *         previously associated null with key.)     */        //将指定值与此映射中的指定键相关联。如果映射之前包含键的映射,则替换旧值。         //参数: key–与指定值关联的键 value–与指定键关联的值         //返回值: 与键关联的前一个值,如果没有键的映射,则为null。(null返回还可以指示映射先前将null与键关联。)    public V put(K key, V value) {        return putVal(hash(key), key, value, false, true);    }    /**     * Implements Map.put and related methods.     *     * @param hash hash for key     * @param key the key     * @param value the value to put     * @param onlyIfAbsent if true, don't change existing value     * @param evict if false, the table is in creation mode.     * @return previous value, or null if none     */        //实施映射。put和相关方法。         //参数: hash–密钥的哈希 key–钥匙 value–要放置的值 onlyIfAbsent–如果为true,则不更改现有值 evict-如果为false,则表处于创建模式。         //返回值: 上一个值,如果没有则为null    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                   boolean evict) {        Node[] tab; Node p; int n, i;        //检查HashMap中是否为空,为空则使用resize初始化,并给n赋值        if ((tab = table) == null || (n = tab.length) == 0)            n = (tab = resize()).length;        //检查数组下标处是否为空,是则直接赋值((n - 1) & hash 为下标)        if ((p = tab[i = (n - 1) & hash]) == null)            tab = newNode(hash, key, value, null);        else {            Node e; K k;            //检查数组下标处key是否与新值重复,是则e的值为 key            if (p.hash == hash &&                ((k = p.key) == key || (key != null && key.equals(k))))                e = p;            //检查是否是红黑树,是则使用红黑树插入,否则链表插入            else if (p instanceof TreeNode)                //红黑树不了解                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);            else {                // 循环遍历链表,如果找到相同key则替换,没有找到则插入到链表最后并检查是否超过8,是则将链表转化为红黑树                for (int binCount = 0; ; ++binCount) {                    // 检查p是否为最后一个元素,此时结束循环e为 null                    if ((e = p.next) == null) {                        //新元素尾插到末尾                        p.next = newNode(hash, key, value, null);                        //检查链表长度是否超过八,是则转化为红黑树,                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                            //构造树的具体方法                            treeifyBin(tab, hash);                        break;                    }                    // 检查元素是否与新元素相同,是则退出循环,此时结束循环e值为 key                    if (e.hash == hash &&                        ((k = e.key) == key || (key != null && key.equals(k))))                        break;                    p = e;                }            }            // 这句话的意思其实是判断上面的循环走完了吗?如果走完链表则e应该是null,如果e不为空则表示新值还没有被替换,再这里替换            if (e != null) { // existing mapping for key                // 将e的值赋给oldValue做中转                V oldValue = e.value;                       // 判断条件是 是否改变现有值 新值是否为null                if (!onlyIfAbsent || oldValue == null)                    e.value = value;                afterNodeAccess(e);                // 返回替换前的值                return oldValue;            }        }        ++modCount;        // 判断是否到达扩容界限 threshold = 容量 * 负载因子(默认0.75)        if (++size > threshold)            // 扩容            resize();        afterNodeInsertion(evict);        return null;    }    /**     * Initializes or doubles table size.  If null, allocates in     * accord with initial capacity target held in field threshold.     * Otherwise, because we are using power-of-two expansion, the     * elements from each bin must either stay at same index, or move     * with a power of two offset in the new table.     *     * @return the table     */        //初始化或加倍表大小。如果为空,则根据字段阈值中保持的初始容量目标进行分配。        //因为我们使用的是二次幂展开,所以每个bin中的元素必须保持在同一索引中,或者在新表中以二次幂偏移量移动。        //返回值: table        //HashMpa的扩容方法        final Node[] resize() {                Node[] oldTab = table;        // 判断HahsMap是否为空给oldCap(旧容量)赋值        int oldCap = (oldTab == null) ? 0 : oldTab.length;        // 旧的容量界限        int oldThr = threshold;        // 新的容量和新的容量界限        int newCap, newThr = 0;        // 判断是否HashMap第一次初始化        if (oldCap > 0) {            // 判断旧容量是否大于等于最大,是则提升界限,并将容器直接返回            if (oldCap >= MAXIMUM_CAPACITY) {                // 将界限提升到2的31次方 - 1                 threshold = Integer.MAX_VALUE;                // 返回原容器                return oldTab;            }            //判断旧容量扩大2倍后是否超过最大容量,判断旧容量界限是否大于等于初始容量(保证不是第一次初始化容器)            //




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4