奇怪,为什么ArrayList初始化容量大小为10?

开发
我们都知道ArrayList和HashMap底层都是基于数组的,但为什么ArrayList不像用HashMap那样用16作为初始容量大小,而是采用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,或许适合你呢。

责任编辑:赵宁宁 来源: 程序新视界
相关推荐

2019-04-15 08:49:59

阿里巴巴容量集合

2023-11-12 23:08:17

C++初始化

2018-05-29 14:57:59

HashMap容量初始化

2011-04-07 17:39:16

SQL Server数

2011-06-17 15:29:44

C#对象初始化器集合初始化器

2010-07-28 10:22:33

FlexApplica

2022-07-06 10:37:45

SpringServlet初始化

2021-03-12 10:30:11

SpringMVC流程初始化

2020-12-03 09:50:52

容器IoC流程

2009-11-11 15:29:15

ADO初始化

2019-11-04 13:50:36

Java数组编程语言

2009-09-08 09:48:34

LINQ初始化数组

2024-01-15 06:34:09

Gin镜像容器

2019-09-17 07:31:53

MySQL表容量数据库

2009-07-31 17:51:27

C#对象初始化

2009-06-10 16:17:00

Netbeans JT初始化

2021-07-07 05:00:17

初始化源码

2012-03-13 13:38:42

Java

2009-06-11 13:26:16

Java数组声明创建

2021-04-28 20:44:01

HashMap容量性能
点赞
收藏

51CTO技术栈公众号