目录
ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问。但因为ArrayList中插入或删除一个元素需要移动其他元素,所以不适合在插入和删除操作频繁的场景下使用。
在说明扩容方式之前,我们需要提前知道ArrayList内部封装的成员变量以及构造方法
1、成员变量
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
(1)DEFAULT_CAPACITY:这是一个常量,表示默认的容量大小,也是在没有初始化容量大小的时候 调用add方法后初始化的容量大小;
(2)EMPTY_ELEMENTDATA:定义空数组,大小固定为0;
(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA:定义默认空容量的数组,大小固定为0,将它于EMPTY_ELEMENTDATA数组区分开;
(4)elementData:实际存储元素的数组,并且不可以被序列化;
(5)size:表示数组元素的个数;
2、构造方法
(1)带有一个int类型参数的构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
传入的int类型的参数表示数组容量的大小,根据传入的参数大小创建对应容量的数组
当initialCapacity > 0时,会在堆上new一个大小为initialCapacity的数组,然后将其引用赋给elementData,此时ArrayList的容量为initialCapacity,元素个数size为默认值0。
当initialCapacity = 0时,elementData被赋予了默认空数组,因为其被final修饰了,所以此时ArrayList的容量为0,元素个数size为默认值0。
当initialCapacity < 0时,会抛出异常。
(2)无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当我们直接创建ArrayList的时候,将elementData赋予了一个默认空容量的数组,并且这个空容量的数组是被final关键词修饰的,所以说此时elementData是空的,元素个数和容量都为0;
在调用add()方法添加元素的时候,会将默认的数组容量DEFAULT_CAPACITY分配给elementData;
(3)带有一个Collection对象参数的构造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
传入Collection元素列表后,构造方法首先会将其转化为数组,将其索引赋给elementData。
如果此数组的长度为0,会重新赋予elementData为空数组,此时ArrayList的容量是0,元素个数size为0。
如果此数组的长度大于0,会更新size的大小为其长度,也就是元素的个数,然后执行里面的程序。大家对里面的代码可能不理解,让我们等会看下面解析。执行完后此时ArrayList的容量为传入序列的长度,也就是size的大小,同时元素个数也为size,也就是说,此时ArrayList是满的。
当我们了解了相关的成员变量和构造方法后,就可以深入的了解ArrayList的扩容机制
3、ArrayList的扩容机制
(1)首先我们需要从add()方法观察
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
当我们第一次调用add()方法的时候,发现它里面调用了另一个ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
1、ensureCapacityInternal方法分别调用calculateCapacity和ensureExplicitCapacity函数 进行扩容;
2、calculateCapacity方法用来判断elementData是否为空数组,也就是看数组是否添加了元素,如果是则返回DEFAULT_CAPACITY和minCapacity的最大值 否则直接返回minCapacity;
3、ensureExplicitCapacity方法时用来判断minCapacity的elementData数组的长度大小,当minCapacity大于数组的长度的时候则调用扩容函数进行扩容;
下面扩容开始,grow()表示扩容函数。
其中定义的MAX_ARRAY_SIZE表示一个常量,防止数组的容量溢出 要分配的数组的最大大小
hugeCapacity方法的作用是比较minCapacity和MAX_ARRAY_SIZE的大小 如果minCapacity小于0 则抛出异常;如果大于MAX_ARRAY_SIZE则返回Integer.MAX_VALUE ,小于MAX_ARRAY_SIZE则直接返回MAX_ARRAY_SIZE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
1、oldCapacity用来记录旧数组的容量,newCapacity就是扩容后的容量,自动扩容为原容量的1.5倍;
2、判断新容量的大小是否小于最小需要容量;
3、判断当前新容量大于MAX_ARRAY_SIZE,使用hugeCapacity函数比较;
4、最后将原来数组的元素拷贝到扩容后的数组;
4、总结
ArrayList的扩容机制可以分为两种情况:
1、当数组的容量为0的时候扩容,并且三种构造方法的扩容各不相同
(1)无参构造:默认的数组容量为0,当第一次调用add()方法添加元素的时候,会给数组分配一个默认大小的容量10,当添加的元素元素个数大于该容量的时候,正常扩容;
(2)int类型参数的构造方法:传入的参数既为数组容量的大小,当传入的容量大小为0的时候,在添加第一个元素以后,容量变为1,在需要添加的时候需要进行扩容;
(3)Collection类型参数的构造方法:传入的Collection如果为空,那么数组容量为0,当添加第一个元素的时候,容量变为1,继续添加的时候就会发生扩容。
2、当数组的容量大于0的时候并且此时数组是满的,就进行正常扩容。