关于java:奇怪为什么ArrayList初始化容量大小为10

6次阅读

共计 3804 个字符,预计需要花费 10 分钟才能阅读完成。

背景

看 ArrayList 源码时,无心中看到 ArrayList 的初始化容量大小为 10,这就奇怪了!咱们都晓得 ArrayList 和 HashMap 底层都是基于数组的,但为什么 ArrayList 不像用 HashMap 那样用 16 作为初始容量大小,而是采纳 10 呢?

于是各方查找材料,求证了这个问题,这篇文章就给大家讲讲。

为什么 HashMap 的初始化容量为 16?

在聊 ArrayList 的初始化容量时,要先来回顾一下 HashMap 的初始化容量。这里以 Java 8 源码为例,HashMap 中的相干因素有两个:初始化容量及装载因子:

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

在 HashMap 当中,数组的默认初始化容量为 16,当数据填充到默认容量的 0.75 时,就会进行 2 倍扩容。当然,使用者也能够在初始化时传入指定大小。但须要留神的是,最好是 2 的 n 次方的数值,如果未设置为 2 的 n 次方,HashMap 也会将其转化,反而多了一步操作。

对于 HashMap 的实现原理的内容,这里就不再赘述,网络上曾经有太多文章讲这个了。有一点咱们须要晓得的是 HashMap 计算 Key 值坐标的算法,也就是通过对 Key 值进行 Hash,进而映射到数组中的坐标。

此时,保障 HashMap 的容量是 2 的 n 次方,那么在 hash 运算时就能够采纳位运行间接对内存进行操作,无需转换成十进制,效率会更高。

通常,能够认为,HashMap 之所以采纳 2 的 n 次方,同时默认值为 16,有以下方面的考量:

  • 缩小 hash 碰撞;
  • 进步 Map 查问效率;
  • 调配过小避免频繁扩容;
  • 调配过大浪费资源;

总之,HashMap 之所以采纳 16 作为默认值,是为了缩小 hash 碰撞,同时晋升效率。

ArrayList 的初始化容量是 10 吗?

上面,先来确认一下 ArrayList 的初始化容量是不是 10,而后在探讨为什么是这个值。

先来看看 Java 8 中,ArrayList 初始化容量的源码:

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

很显著,默认的容器初始化值为 10。而且从 JDK1.2 到 JDK1.6,这个值也始终都为 10。

从 JDK1.7 开始,在初始化 ArrayList 的时候,默认值初始化为空数组:

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

此处必定有敌人说,Java 8 中 ArrayList 默认初始化大小为 0,不是 10。而且还会发现构造方法上的正文有一些奇怪:结构一个初始容量 10 的空列表。什么鬼?明明是空的啊!

保留疑难,先来看一下 ArrayList 的 add 办法:

    public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

在 add 办法中调用了 ensureCapacityInternal 办法,进入该办法一开始是一个空容器所以 size=0 传入的minCapacity=1

    private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

上述办法中先通过 calculateCapacity 来计算容量:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

会发现 minCapacity 被从新赋值为 10(DEFAULT_CAPACITY=10),传入 ensureExplicitCapacity(minCapacity);minCapacity=10,上面是办法体:

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

上述代码中 grow 办法是用来解决扩容的,将容量扩容为原来的 1.5 倍。

理解下面的解决流程,咱们会发现,实质上 ArrayList 的初始化容量还是 10,只不过应用懒加载而已,这是 Java 8 为了节俭内存而进行的优化而已。所以,从头至尾,ArrayList 的初始化容量都是 10。

这里再多提一下懒加载的益处,当有成千上万的 ArrayList 存在程序当中,10 个对象的默认大小意味着在创立时为底层数组调配 10 个指针(40 或 80 字节)并用空值填充它们,一个空数组(用空值填充)占用大量内存。如果可能提早初始化数组,那么就可能节俭大量的内存空间。Java 8 的改变就是出于上述目标。

为什么 ArrayList 的初始化容量为 10?

最初,咱们来探讨一下为什么 ArrayList 的初始化容量为 10。其实,能够说没有为什么,就是“感觉”10 挺好的,不大不小,刚刚好,眼缘!

首先,在探讨 HashMap 的时候,咱们说到 HashMap 之所以抉择 2 的 n 次方,更多的是思考到 hash 算法的性能与碰撞等问题。这个问题对于 ArrayList 的来说并不存在。ArrayList 只是一个简略的增长阵列,不必思考算法层面的优化。只有超过肯定的值,进行增长即可。所以,实践上来讲 ArrayList 的容量是任何正值即可。

ArrayList 的文档中并没有阐明为什么抉择 10,但很大的可能是出于性能损失与空间损失之间的最佳匹配考量。10,不是很大,也不是很小,不会节约太多的内存空间,也不会折损太多性能。

如果非要问当初到底为什么抉择 10,可能只有问问这段代码的作者“Josh Bloch”了吧。

如果你仔细观察,还会发现一些其余有意思的初始化容量数字:

ArrayList-10
Vector-10
HashSet-16
HashMap-16
HashTable-11

ArrayList 与 Vector 初始化容量一样,为 10;HashSet、HashMap 初始化容量一样,为 16;而 HashTable 独独应用 11,又是一个很有意思的问题。

小结

有很多问题是没有明确起因、明确的答案的。就如同一个女孩儿对你没感觉,可能是因为你不够好,也可能是她曾经爱上他人了,但也有很大可能你是不会晓得答案。但在寻找起因和答案的过程中,还是可能学到很多,成长很多的。没有比照就没有挫伤,比方 HashMap 与 ArrayList 的比照,没有比照就不晓得是否适宜,还比方 HashMap 与 ArrayList。当然,你还能够试试特立独行的 HashTable,或者适宜你呢。

博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢送关注~

技术交换:请分割博主微信号:zhuan2quan

正文完
 0