📝个人主页:哈__
期待您的关注
ArrayList和普通数组不同,ArrayList支持动态扩容,那么ArrayList到底是如何扩容的呢?你又是否知道ArrayList数组的初始长度是多少呢?
在开始介绍之前,我们要先介绍一下ArrayList类中的一些属性。
/*** *默认初始容量。*/private static final int DEFAULT_CAPACITY = 10;/*** 用于空实例的共享空数组实例。*/private static final Object[] EMPTY_ELEMENTDATA = {};/*** 共享的空数组实例用于默认大小的空实例。我们* 将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少* 添加第一个元素。*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** 存放数组列表元素的数组缓冲区。* 数组列表的容量是这个数组缓冲区的长度。任何空的数组且满足elementData == * DEFAULTCAPACITY_EMPTY_ELEMENTDATA* 将在添加第一个元素时扩展为DEFAULT_CAPACITY。*/transient Object[] elementData;
elementData就是我们的数据要存储的进入的数组,看上边的注释说,如果数组是空的并且满足elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的时候,数组就会被扩容为10;
那么接下来我们看一下ArrayList的三个构造方法。
1.有参构造方法
// 传入参数初始化数组的大小
public ArrayList(int initialCapacity) {//如果初始化的大小大于0if (initialCapacity > 0) {//为elementData初始化this.elementData = new Object[initialCapacity];//如果初始化的空间大小为0} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}
2.无参构造方法
public ArrayList() {// elementData数组就等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
3.传入Collention元素列表
/**
*按照指定集合的迭代器返回的顺序,构造一个包含指定集合元素的列表。
**/
public ArrayList(Collection<? extends E> c) {//将元素列表转为数组Object[] a = c.toArray();//如果元素个数不为0if ((size = a.length) != 0) {//如果元素列表是一个ArrayList类型if (c.getClass() == ArrayList.class) {//把a赋给elementDataelementData = a;} else {// 如果不是ArrayList类型,进行元素拷贝elementData = Arrays.copyOf(a, size, Object[].class);}} else {// 如果元素个数为0,将elementData赋值为空数组elementData = EMPTY_ELEMENTDATA;}}
ArrayList的扩容机制
我们向ArrayList中添加数据时,调用的是add()方法。我们跟进查看。首先执行ensureCapacityInternal(size + 1); 这个方法,翻译为确保内部容量,传入的参数是当前集合的元素个数再加上1,一定是加1,这样才能确保正确的添加。我们跟进到方法中查看。
public boolean add(E e) {//确保内部容量 传入当前集合中的元素个数在加上1ensureCapacityInternal(size + 1); elementData[size++] = e;return true;}
这就是这样的一个确保内部容量的方法,传入了一个参数,名为最小的容量,之后调用 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity))这个方法,但是这个方法中还有一个calculateCapacity(elementData, minCapacity)方法,我们先进入这个方法查看
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}
calculateCapacity方法如下。他需要两个参数,一个是elementData,一个是最小的容量。如果我们的elementData是DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个数组的话,那么我们就返回这个最小的容量和我们内部默认容量中大的一个。如果不是这个默认数组的话直接返回最小容量。这里的判断是因为我们有两种不同的构造函数,一个是无参,另一个是有参,无参构造函数在添加数据的时候会自动将数组扩容为10。
private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}
这样调用完这个函数之后我们就知道数组的容量是多少了。
我们返回ensureExplicitCapacity这个函数接着看。
他需要一个参数就是最小的容量。modCount记录的是数组的修改次数。
接着判断最小的容量减去我们当前数组的容量,如果数组的空间不够,我们就要的调用grow函数进行扩容。否则的话我们就直接回到了最上方的add函数当中进行元素添加。
private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}
grow函数如下。别慌我写了注释。
private void grow(int minCapacity) {// overflow-conscious code// 这是我们之前数组的容量int oldCapacity = elementData.length;// 新数组的容量应该是旧数组容量+旧数组容量/2,也就是扩容了1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果新的容量还是不够,那我们就直接把容量定为传入的最小容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果扩容扩出的新数组太大了,比数组最大长度还要大 要重新扩容if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 将我们的elementData进行扩容,然后赋值给我们的elementData数组,也就是把数组搬到了一个// 大的空间中elementData = Arrays.copyOf(elementData, newCapacity);}
如果真的走到了hugeCapacity函数中,如下所示。
private static int hugeCapacity(int minCapacity) {// 如果最小的容量小于0 直接报错if (minCapacity < 0) // overflowthrow new OutOfMemoryError();// 如果最小容量比数组最大容量大,返回整形的最大值,否则的话就是等于数组最大值// 不再扩充1.5倍了return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}
到了这一步真的就是走完了最最最上方的ensureCapacityInternal方法,然后就可以添加元素了。
以上内容就是ArrayList集合的扩容机制。