ArrayList的扩容问题
这里就要从ArrayList的源码说起。
ArrayList类的定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从ArrayList类的定义可以看出,ArrayList
继承了AbstractList
并且实现了List,是一个数组队列,具有增、删、改、遍历等基本功能。
AbstractList还实现了RandomAccess
接口,及RandomAccess
可以进行随机访问。
什么是随机访问?
我理解的随机访问就是可以根据ArrayList
元素下标获取元素。
ArrayList实现了Cloneable
接口,可以被克隆。
ArrayList
实现了java.io.Serializable
接口,即ArrayList
支持序列化,可通过序列化进行传输。
关于线程安全问题!!
ArrayList
是线程不安全的集合,在多线程环境中,可使用线程安全的Vector
或者CopyOnWriteArrayList
(这里建议使用CopyOnWriteArrayList
)。
ArrayList源码中的属性
// ArrayList的额序列化版本号
private static final long serialVersionUID = 8683452581122892189L;
// ArrayList的默认容量为10
private static final int DEFAULT_CAPACITY = 10;
// 空的对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空的对象数组,当调用ArrayList的无参构造器时使用。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList存放数据的数组
transient Object[] elementData;
// ArrayList中的数据个数(与容量不同)
private int size;
从ArrayList
的属性可以看出,ArrayList
底层其实是一个动态数组,他与数组最大的区别就是ArrayList
可以动态的扩容。ArrayList
的扩容问题会在本文分析到。
ArrayList的构造函数
ArrayList有三个构造函数
指定ArrayList
初始容量的构造器。
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);
}
}
从构造函数中不难看出,若传入的参数initialCapacity<0
会抛出异常,当initialCapacity==0
时,将ArrayList
属性中的EMPTY_ELEMENTDATA
赋值给elementData
,这时,ArrayList
底层其实就是一个空的容量为0数组队列。当initialCapacity>0
会创建一个容量为initialCapacity
大小的对象数组。
无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList
的无参构造函数内容很简单,就是讲ArrayList
类属性中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA
赋值给elementData
,通过ArrayList
的无参构造函数可以得到一个空的长度为0的数组队列。
参数为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
对象c的toArray
方法,将对象c转换为Object
数组并赋值为elementData
。ArrayList
便初始化。
那么,ArrayList的初始容量为什么为10呢?ArrayList类中的DEFAULT_CAPACITY属性又在哪里被使用?接下来说到的ArrayList的扩容问题会解释道ArrayList的初始容量问题和DEFAULT_CAPACITY的使用。
ArrayList
的扩容问题可以从ArrayList
的add
方法说起。
ArrayList
的add
方法有两个:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
这里通过讲解第一个add方法分析ArrayList的扩容问题。
当通过使用add(E e
)方法向集合中添加数据时,会先调用ensureCapacityInternal(size + 1);
方法,
我们假设当前集合size==0
,即空集合。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
ensureCapacityInternal(int minCapacity)
中minCapacity参数为size+1即参数为1,ensureCapacityInternal
方法再调用ensureExplicitCapacity(int minCapacity)
方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
而ensureExplicitCapacity(int minCapacity)
方法的参数minCapacity
为
calculateCapacity(elementData, minCapacity)
方法的返回值。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
所以我们要先分析calculateCapacity
方法,calculateCapacity
方法传入elementData, minCapacity
,这里minCapacity的值认为size+1为1,if判断条件成立,执行return Math.max(DEFAULT_CAPACITY, minCapacity);
返回的便是DEFAULT_CAPACITY
,即返回10。然后将返回的10作为ensureExplicitCapacity
方法的参数,那么ensureExplicitCapacity
方法的参数为10。
ensureExplicitCapacity
方法中的if判断成立,调用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);
}
方法内部int oldCapacity = elementData.length
,则oldCapacity ==0
这里涉及到位运算,>>1这里可以大致理解为扩大为原本的1.5倍,
那么 int newCapacity = oldCapacity + (oldCapacity >> 1)
,则newCapacity ==0
,
则第一个if判断条件成立,newCapacity = minCapacity
,newCapacity ==10
。
最后执行elementData = Arrays.copyOf(elementData, newCapacity);
,则elementData
被扩容为容量为10的对象数组。
至此,ArrayList的扩容到此结束,继续回到add(E e)
方法,执行elementData[size++] = e;
语句,将e放入
elementData
对象数组中。
扩容问题总结
这里笔者以刚被初始化的ArrayList
集合,即size==0
的的情况分析了ArrayList
集合的扩容问题,只是为了展示ArrayList
的扩容全过程,大家可以通过不同的size进行不同的分析,充分理解ArrayList
的扩容过程。
这里值得一提的是,ArrayList
默认初始容量(调用无参构造器创建的ArrayList
对象)并不是10,而是0,当第一次放入数据时,ArrayList
的容量会被扩容到10。
ArrayList的其它方法包括被重载的add方法就不再过多分析,大家理解了上述问题后,其它方法便会无师自通。