一文看懂ArrayList的自动扩容

1.基本原理

在我们日常开发中,ArrayList 是最常用的一种集合。我们都知道ArrayList的底层是基于数组来实现的,那么,

对于数组呢,我们都不陌生:数组是同一数据类型的集合,一旦创建后,大小就不可变。也就是说长度是固定的。

在ArrayList 中,若我们把数组大小设置为20,往ArrayList里面插入数据,当元素超过20之后,就会有一个数组的扩容。

会新建一个更大的数组。然后把以前的数组拷贝到新的数组里面去。这儿的数组扩容和元素拷贝,就会相对慢一些。

因此,ArrayList有以下缺点:

缺点一:频繁的往ArrayList里面插入数据,会导致它频繁的数组扩容,这样会影响性能。

缺点二:对于数组,我们都知道,当你往数组的中间插入元素的时候,会让新插入元素位置的后面的元素全部往后挪动一位。

所以,往ArrayList中间插入数据的时候,性能较差,因为会导致新增元素后面元素的移动。

再简单说一下数组,计算机会给数组分配连续的内存空间,这块内存有一个首地址,我们访问数组的某个元素的时候,会通过一个寻址公式

来计算存储的地址,公式:arr[i]_address = base_address + i * data_type_size(arr[i] 首地址 = 数组内存块首地址 + 数据类型大小 * i ,其中i为偏移量。)

baseaddress:内存块的首地址。datatype_size:数组中每个元素的大小,比如每个元素大小是4个字节。所以,数组根据下标随机访问的时间复杂度是O(1)。

了解了数组随机访问元素的机制后,ArrayList的优点就出来了:

ArrayList基于数组实现的,随机读性能很高,如:list.get(1). 数组可以直接通过内存地址访问元素,所以ArrayList也可以基于底层数组,直接根据内存地址访问元素。

2.什么时候用ArrayList呢?

当我们不会频繁的插入元素,不会导致频繁的元素的位置移动,数组扩容。比如说,有一批数据,查询出来灌入ArrayList中,后续不会频繁插入元素,主要遍历这个集合。或者通过索引随机读取某个元素,

这种情况下使用ArrayList是很合适的。

ArrayList还有一个主要的功能,就是它里面的元素是有顺序的,比如大量业务中的列表,都有排序。我们拿到整理好的排序的数据之后,可以直接按照顺序灌入ArrayList中。

3.ArrayList核心方法

我们看一下ArrayList的源码。
在这里插入图片描述
在这里插入图片描述

默认的构造函数,直接初始化一个ArrayList实例的话,内部是一个默认的空数组。并且默认的初始化数组的大小的值,为10。

在这里插入图片描述

在平常的开发中,一般来说,在你可以推测出数据数量的话,尽可能不要去使用这个默认的构造函数。因为你使用ArrayList的话,基本上来说就是默认它不会太平凡的往

里面插入、移除元素的操作。

所有,构造ArrayList的时候,给一个比较靠谱的初始化数组的大小的值。比如说给100,这样可以避免数组太小,导致数组不断的扩容。
在这里插入图片描述

4.add()源码

在这里插入图片描述
看以上源码,我们每次往ArrayList中塞入数据的时候,都会判断一下,当前数据的元素是否塞满了,如果塞满的话,此时就会扩容这个数据,让后将老数组的元素拷贝到新的数组中去。

确保数组一定是可以插入更多的元素。

在这里插入图片描述

5.ArrayList数组扩容以及元素拷贝

从上面我们知道,若我们使用的是默认数组的大小,也就是10,如果我们已经往数组里面插入了10个元素了,那么现在数组的size=10 ,capacity=10,

此时调通add(),再插入一个元素,也就是插入第11个元素,那么肯定是插入不进去的。 要插入第11个元素,就会对数组进行扩容。

int newCapacity = oldCapacity + (oldCapacity >> 1);
其中,oldCapacity=10

oldCapacity + (oldCapacity >> 1 =5

newCapacity=15

数组扩容的时候,old数组的大小+old数组的大小>>1 就得到新的数组大小。
elementData = Arrays.copyOf(elementData,newCapacity(minCapacity)),
实现了数组元素的拷贝。

ArrayList自动扩容机制是指,当数组不足以容纳元素时,自动扩容。其内部实现是在添加元素时判断数组容量是否足够,如果不够就创建一个原数组长度两倍的新数组,并将原数组中的元素复制到新数组中,然后把新元素添加到新数组中。 下面是ArrayList自动扩容机制的代码实现: ``` private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) {...} ``` 这段代码中,首先调用ensureCapacityInternal方法进行容量确认,然后如果需要扩容,会调用ensureExplicitCapacity方法进行扩容,这个方法又调用grow方法实现扩容。在grow方法内部,首先计算新容量,然后调用Arrays.copyOf方法将原数组复制到新数组中,完成扩容操作。 其中,MAX_ARRAY_SIZE是一个限制数组大小的常量,因为Java虚拟机规定数组的长度不能超过Integer.MAX_VALUE - 8,因此在扩容时需要考虑这个限制,避免出现数组长度溢出的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书语时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值