美文网首页
HashMap源码解析 (HashMap类-构造方法)

HashMap源码解析 (HashMap类-构造方法)

作者: 七喜丶 | 来源:发表于2021-10-26 08:34 被阅读0次

    1. HashMap()

    public HashMap() {
    // 将默认的负载因子0.75赋值给loadFactor,并没有创建数组
       this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }
    

    2. HashMap(int initialCapacity)

    // 指定“容量大小”的构造函数
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    

    3. HashMap(int initialCapacity, float loadFactor)

    /*
         指定“容量大小”和“负载因子”的构造函数
         initialCapacity:指定的容量
         loadFactor:指定的负载因子
    */
    public HashMap(int initialCapacity, float loadFactor) {
            // 判断初始化容量initialCapacity是否小于0
            if (initialCapacity < 0)
                // 如果小于0,则抛出非法的参数异常IllegalArgumentException
                throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
            // 判断初始化容量initialCapacity是否大于集合的最大容量MAXIMUM_CAPACITY
            if (initialCapacity > MAXIMUM_CAPACITY)
                // 如果超过MAXIMUM_CAPACITY,会将MAXIMUM_CAPACITY赋值给initialCapacity
                initialCapacity = MAXIMUM_CAPACITY;
            // 判断负载因子loadFactor是否小于等于0或者是否是一个非数值
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                // 如果满足上述其中之一,则抛出非法的参数异常IllegalArgumentException
                throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
             // 将指定的负载因子赋值给HashMap成员变量的负载因子loadFactor
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor(initialCapacity);
        }
    // 最后调用了tableSizeFor,来看一下方法实现:
         /*
             返回比指定初始化容量大的最小的2的n次幂
         */
        static final int tableSizeFor(int cap) {
            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;
        }
    

    tableSizeFor(initialCapacity)判断指定的初始化容量是否是2的n次幂,如果不是那么会变为大于等于 initialCapacity 的最小的 2 的幂。
    但是注意,在tableSizeFor方法体内部将计算后的数据返回给调用这里了,并且直接赋值给threshold边界值了。有些人会觉得这里是一个bug,应该这样书写:
    this.threshold = tableSizeFor(initialCapacity) * this.loadFactor;
    这样才符合threshold的意思(当HashMap的size到达threshold这个阈值时会扩容)。
    但是请注意,在jdk8以后的构造方法中,并没有对table这个成员变量进行初始化,table的初始化被推迟到了put方法中,在put方法中的会对threshold重新计算,准确的的说是内部的resize方法

    4. HashMap(Map<? extends K, ? extends V> m)

    // 构造一个映射关系与指定 Map 相同的新 HashMap。
    public HashMap(Map<? extends K, ? extends V> m) {
            // 负载因子loadFactor变为默认的负载因子0.75
             this.loadFactor = DEFAULT_LOAD_FACTOR;
             putMapEntries(m, false);
     }
    
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        //获取参数集合的长度
        int s = m.size();
        if (s > 0) {
            //判断参数集合的长度是否大于0,说明大于0
            if (table == null) { // 判断table是否已经初始化
                    // 未初始化,s为m的实际元素个数
                    float ft = ((float)s / loadFactor) + 1.0F;
                    int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);
                    // 计算得到的t大于阈值,则初始化阈值
                    if (t > threshold)
                        threshold = tableSizeFor(t);
            }
            // 已初始化,并且m元素个数大于阈值,进行扩容处理
            else if (s > threshold)
                resize();
            // 将m中的所有元素添加至HashMap中
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
    

    注意:
    float ft = ((float)s / loadFactor) + 1.0F; 这一行代码中为什么要加 1.0F ?

    s/loadFactor 的结果是小数,加 1.0F 与 (int)ft 相当于是对小数做一个向上取整以尽可能的保证更大容量,更大的容量能够减少 resize 的调用次数。所以 + 1.0F 是为了获取更大的容量

    例如:原来集合的元素个数是 6 个,那么 6/0.75 是8,是 2 的n次幂,那么新的数组大小就是 8 了。然后原来数组的数据就会存储到长度是 8 的新的数组中了,这样会导致在存储元素的时候,容量不够,还得继续扩容,那么性能降低了,而如果 +1 呢,数组长度直接变为16了,这样可以减少数组的扩容

    相关文章

      网友评论

          本文标题:HashMap源码解析 (HashMap类-构造方法)

          本文链接:https://www.haomeiwen.com/subject/lvwraltx.html