慢慢攻略HashMap(2)——构造方法篇

********本篇主要介绍了HashMap源码中构造方法部分********


下面展示关于构造方法的 HashMap底层源码

1.四种构造方法的介绍

/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
     1号🌰
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
     2号🌰
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
     3号🌰
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
     4号🌰
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

    /**

2.四种构造方法的源码分析

我们在上述源码中看到了4种构造方法,我用X号🌰已经标注上了,那么我们来看一看这几个构造方法中比较重要的吧~

构造方法方法头
1号🌰public HashMap(int initialCapacity, float loadFactor)
2号🌰public HashMap(int initialCapacity)
3号🌰public HashMap()
4号🌰public HashMap(Map<? extends K, ? extends V> m)

1号🌰

public HashMap(int initialCapacity, float loadFactor) {
    //其实就是做了一些校验
    //capacity必须大于0 , 最大值也就是MAX_CAPACITY
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    
    //loadFactor必须大于0
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    //扩容阈值只能是2的次方 🌰
    this.threshold = tableSizeFor(initialCapacity);
}

initialCapacity:table数组的大小
loadFactor:负载因子
threshold:扩容阈值
看标记🌰处的代码:

this.threshold = tableSizeFor(initialCapacity);

问题1:为什么initialCapacity需要通过tableSizeFor方法后才能赋值?
问题2:为什么不能赋值给table长度,而是赋值给了threshold呢?

问题2的答案是:table长度的赋值是采用了延迟初始化机制,只有调用put方法时才会初始化table长度,之后会有一期来将put方法。

接下来我们探究问题一
首先我们应该看一下tableSizeFor方法的源码:

	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;
	}

这里用到了无符号右移运算
在这里就不讲无符号右移是怎么算的了

传进来的参数是initialCapacity,也就是table数组大小,他先减1,在进行无符号右移,最后n如果满足条件且比MAXIMUM_CAPACITY小,那么就return n+1;

这个方法的作用就是:返回一个大于等于当前值cap的一个数字,并且这个数字一定是2的次方数。

举个例子:
eg:
cap = 10
n = 10 - 1 => 9
| 按位或运算:如果相对应位都是 0,则结果为 0,否则为 1
0b1001 | 0b0100 => 0b1101
0b1101 | 0b0011 => 0b1111
0b1111 | 0b0000 => 0b1111

0b1111 => 15
15 < 0? false
15 >= MAXIMUM_CAPACITY ? false
所以return 15 + 1 =>16

最后一定会把传进来的cap的二进制的有效低位全变成1,
比如把例子中的cap=10,n-1后变成9,9的二进制为:0b1001,变成了 0b1111,也就是变成了15,最后return n+1变成16,正好是2的次方数了。
所以总结一下就是,把你传进来的数变成离这个数最近的2的次方数。

到这里可能有疑问,为什么一开始要进行cap - 1的操作?
那咱就可以试一下没有这句代码之后的情况。
我就不写算式了,直接说结果,结果就是比原来的结果大一倍

2号🌰

	public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
	}

这段代码没什么好说的,用了个this直接套娃了1号构造方法。

3号🌰

	public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
	}

默认构造方法,只初始化了负载因子。

4号🌰

	public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

就是写进去个集合,用到了putMapEntries方法,那我们看看这个方法的源码吧,我把每行都加了注释。

	final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    // <1>
    if (s > 0) {
        // 如果 table 为空,说明还没初始化,适合在构造方法的情况
        if (table == null) { // pre-size
            // 根据 s 的大小 + loadFactor 负载因子,计算需要最小的 tables 大小
            float ft = ((float)s / loadFactor) + 1.0F; // + 1.0F 的目的,是因为下面 (int) 直接取整,避免不够。
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            // 如果计算出来的 t 大于阀值,则计算新的阀值
            if (t > threshold)
                threshold = tableSizeFor(t);
        // 如果 table 非空,说明已经初始化,需要不断扩容到阀值超过 s 的数量,避免扩容
        } else {
            // Because of linked-list bucket constraints, we cannot
            // expand all at once, but can reduce total resize
            // effort by repeated doubling now vs later
            while (s > threshold && table.length < MAXIMUM_CAPACITY)
                resize(); // 扩容
        }

        // <2> 遍历 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);
        }
    }
}

本博客仅供学习参考,也是个人笔记总结,如果错误请见谅~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值