📝个人主页:哈__
期待您的关注
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) {
//如果初始化的大小大于0
if (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();
//如果元素个数不为0
if ((size = a.length) != 0) {
//如果元素列表是一个ArrayList类型
if (c.getClass() == ArrayList.class) {
//把a赋给elementData
elementData = 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) {
//确保内部容量 传入当前集合中的元素个数在加上1
ensureCapacityInternal(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 code
if (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) // overflow
throw new OutOfMemoryError();
// 如果最小容量比数组最大容量大,返回整形的最大值,否则的话就是等于数组最大值
// 不再扩充1.5倍了
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
到了这一步真的就是走完了最最最上方的ensureCapacityInternal方法,然后就可以添加元素了。
以上内容就是ArrayList集合的扩容机制。