java8中的HashMap源码分析(一):构造函数

Java架构师交流群:793825326

java版本:jdk1.8

IDE:idea 18

查看HashMap的源码,会发现它有四个构造函数:

1)

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)  //MAXIMUM_CAPACITY数组的最大长度,这个值是:
        initialCapacity = MAXIMUM_CAPACITY;  //static final int MAXIMUM_CAPACITY = 1 << 30;即2的30次方
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);  //将initialCapacity处理成大于或等于它的最小的2的整数次方一个值。
                                                     //传1时返回1,传2时返回2,传3时返回4,传7时返回8,以此类推
}
static final int tableSizeFor(int cap) {
    int n = cap - 1;  //确保大于等于,比如如果你传16,理论上应该得到16,但是如果不先减1,得到的将是32。
    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;
}

2)

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

3)

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

4)

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);   //将一个老的map的元素添加到这个新的map中
}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();  //获取m的元素个数public int size() {return size;}
    if (s > 0) {
        if (table == null) {
            float ft = ((float)s / loadFactor) + 1.0F;  //使用这个旧的map的size计算出新的
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?   //map的threshold 应该是多大
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold) //旧的map的size大于当前的threshold,容纳不下,需要重新计算
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)    //该条件判断用于向已经实例化了的map里面添加某个map的所有元素
            resize();              //因此,如果s > threshold,会触发扩容。
                                   //当尝试调用putAll时,该条件才有可能成立。
                                   //(从目前的源码看,这句似乎是多余的)
        //循环把m里面的所有元素存入新的map里面,这里调用的是putVal,这个方法在第二篇文章里面就会讲到
        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);//该方法每次插入新元素之后都会对扩容
        }                                               //的必要性做判断,因此上面的扩容判断没
                                                        //在我看来没必要
    }
}

这里DEFAULT_LOAD_FACTOR是默认加载因子:

static final float DEFAULT_LOAD_FACTOR = 0.75f;

另外还有两个值:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认初始容量2的4次方:16
static final int MAXIMUM_CAPACITY = 1 << 30;       //最大容量为2的30次方

前三种构造函数,其目的都是一样的,对loadFactor和threshold进行初始化。loadFactor叫做加载因子,我们知道hashmap最上层的数据类型其实是数组,既然是数组,就有一个长度的问题,假设数组的长度是16,它和加载因子loadFactor相乘,得到一个12的值,也就是阈值threshold。当hashmap里面的元素个数超过这个值时,就会进行扩容。所以threshold取决于initialCapacity,但是最终得到的值并不是我们传入的值*loadFactor,而是经过了处理,处理方法请看上面的源码,至于为何这么处理,主要还是为了计算索引的时候比较方便,可以直接使用与运算。后面我们进一步分析它的源码的时候,你就会发现这样做的好处。


版权声明:本文为dap769815768原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。