ArrayList的成员变量分析
//序列化版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认容量为10
private static final int DEFAULT_CAPACITY = 10;
//Object对象类型数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//Object对象类型数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*transient修饰的成员属性变量不会被序列化
*ArrayList的核心,elementData数组用于存放当前ArrayList集合的所有的元素数据
*意味着ArrayList底层其实就是个数组,所有add或者addAll的元素均放在elementData数组中
*对ArrayList的操作,其实就是对elementData数组的操作
*/
transient Object[] elementData;
//标记当前ArrayList的元素个数,既elementData数组中的元素个数
private int size;
ArrayList的构造方法分析
ArrayList构造方法有三个,分别是:
- 无参构造:ArrayList();
- 带参构造:ArrayList(int initialCapacity),指定initialCapacity容量大小,创建ArrayList集合;
- 带参构造:ArrayList(Collection<? extends E> c),通过一个集合,创建ArrayList集合;
下面我们将通过代码来分析,每个构造方法均做了什么:
/**
*elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
*此时size==0,ArrayList集合的容量大小为0
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
*此时size==0,ArrayList集合的容量大小为initialCapacity
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//大于0,按照指定的容量initialCapacity初始化elementData 数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//等于0,初始化为EMPTY_ELEMENTDATA的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
//把collection集合转化成数组,初始化给elementData
elementData = c.toArray();
//elementData初始化后元素个数不为0
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
通过上面三个ArrayList源码中的构造方法分析,我们可以知道
1.ArrayList集合初始化时,集合的容量capacity,也就是elementData数组的长度大小已经确定好了
2.接下来就是调用方法add等方法,来给集合增加元素的个数,存放在elementData数组中
但是此时ArrayList的情况如下:
- 数组是长度是不可变的,也就是初始化后elementData数组的长度是固定的
- 当存放的元素个数大于初始化时的容量时,运行时就会发生数组越界异常:java.lang.ArrayIndexOutOfBoundsException
ArrayList实际使用中并未发生数组越界的异常,那么ArrayList实际是怎么设计的,我们接着分析ArrayList在add元素时,如果初始化容量不够用时扩容处理
ArrayList的扩容源码分析
- 我们先创建一个ArrayList集合,初始化容量为0,此时add第一个元素,使用类似Debug模式
//创建一个ArrayList集合,未指定初始化capacity大小,此时集合的capacity==0,元素个数size==0,elementData.length==capacity==0
List<String> strList = new ArrayList<String>()
- 调用add(E e)方法给集合添加元素
//添加第一个元素
strList.add("我是第1个元素");
- 分析add方法源码的具体实现
public boolean add(E e) {//e="我是第1个元素"
//每个元素在存放到elementData数组中前,均调用该方法
//方法参数值为当前elementData数组的size+1
//而这个方法是干什么的呢?我们接着分析该方法
ensureCapacityInternal(size + 1); //此时size==0
//最终把"我是元素"的元素存在放elementData数组下标为size+1的位置
elementData[size++] = e;//elementData[0]="我是第1个元素",add成功!
return true;
}
- 分析ensureCapacityInternal(int minCapacity)方法的实现
private void ensureCapacityInternal(int minCapacity) {//minCapacity==1
//判断当前elementData 数组是否为初始化空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//true
//如果是,minCapacity重新赋值为DEFAULT_CAPACITY(常量10)和minCapacity中的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//minCapacity ==10
}
//把minCapacity容量作为参数传递给ensureExplicitCapacity方法
ensureExplicitCapacity(minCapacity);//minCapacity==10
}
- 分析ensureExplicitCapacity(int minCapacity)方法的实现
private void ensureExplicitCapacity(int minCapacity) {//minCapacity==10
modCount++;
//判断当前容量minCapacity是否大于elementData数组的容量长度
if (minCapacity - elementData.length > 0)//true
grow(minCapacity);//ArrayList集合扩容
}
此时整个集合add调用执行到了最后一步:
如果当前minCapacity大于elementData数组的容量长度,
则调用grow(int minCapacity)方法进行扩容,否则不进行扩容
最后add的元素则直接存放在elementData[size++]下标的位置,整个add结束!
- 分析grow(int minCapacity)方法的实现
private void grow(int minCapacity) {//minCapacity==10
//获取旧的elementData数组长度
int oldCapacity = elementData.length;//oldCapacity ==0
//定义新的elementData数组长度容量=oldCapacity的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity==0
//newCapacity取newCapacity和minCapacity中的最大值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//newCapacity == 10
if (newCapacity - MAX_ARRAY_SIZE > 0)
//newCapacity 最大值限定只能为2147483647或2147483647-8
newCapacity = hugeCapacity(minCapacity);
//真相来了,看到此处,我们豁然开朗
//原来ArrayList的扩容就是,通过把elementData数组按照最新的容量newCapacity ,进行数组的复制
//然后生成新的elementData数组重新赋值给elementData数组对象,扩容结束
//此时elementData.length==newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
//int的最大值:2147483647
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结ArrayList集合扩容的过程
- ArrayList集合创建时,会初试化成员elementData数组的容量capacity长度大小,既elementData.length
- 在add元素时,元素个数size+1大于当前elementData.length时,ArrayList将进行扩容
- 扩容标准为原elementData.length的1.5倍,最大值不能超过Integer的最大值2147483647
- 扩容过程实际就是elementData数组按照新的capacity,进行数组的复制,生成1个新elementData数组,替换掉旧的elementData数组
ArrayList使用注意事项
通过上面ArrayList的源码分析,我们知道了ArrayList集合在添加元素过程中,会因为元素的数量超过自身的容量限制,进行强行扩容
扩容结果影响:1消耗更多的时间,2创建新的数组对象
public static void main(String[] args){
List<String> strList = new ArrayList<String>();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
strList.add(i+"");
}
System.out.println("未指定容量花费时间:"+(System.currentTimeMillis()-start)+"ms");
List<String> strList1 = new ArrayList<String>(10000000);
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
strList1.add(i+"");
}
System.out.println("指定容量花费时间:"+(System.currentTimeMillis()-start)+"ms");
}
上面代码经过测试后结果是:
因此,日常在需要使用ArrayList存储大量元素时,最好是预先根据数据量指定集合的初始化容量大小,避免ArrayList的扩容影响代码执行性能,同时创建大量数组对象