谈起ArrayList,首先它是基于数组的数据结构实现的,与我们普通定义的数组不同,数组的长度是可以动态扩展的,可以在插入数据时,实现动态扩容、数据拷贝等,灵活性更强。
下面来深入ArrayList源码,剖析内部原理。
1.构造器
首先来分析一下ArrayList的构造器
ArrayList构造器有三种:
1.默认构造器
没有给定初始容量大小,此时,初始化为空数组。这种情况下,数组的长度会在第一次插入元素时候设置。
List<String> list = new ArrayList<String>();
#作为ArrayList的缓冲数组
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.给定初始容量大小
当我们已知要填充的数组元素的大小时,为了提升性能,减少数组中的拷贝操作,可以在初始化时预先给定长度值。
List<String> list = new ArrayList<String>(20);
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
#如果给定长度大于0,以该长度创建数组对象
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
#如果给定长度等于0,实例化空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3.构造一个包含指定集合元素的列表
只要实现Collection类的都可以作为入参,通过转化为数组的方式赋值给elementData,如果入参转化为数组后不是Object[]类型,利用Arrays.copyOf方法,将elementData拷贝到Object[]中再赋值给属性elementData。
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;
}
}
2.初始化的几种方式
上面介绍了ArrayList的几种构造器,在此基础上,我们来看下ArrayList有哪几种初始化的方式:
1.普通方式
即我们日常比较常用的方式:
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
2.内部类方式
ArrayList<String> list = new ArrayList<String> (){{
add("a");
add("b");
add("c");
}};
3.使用Arrays.asList方式
ArrayList<String> list = new ArrayList<String> (Arrays.asList("a","b","c"));
4.使用Collections.ncopies
用于生成多份指定元素,这里是生成10个元素为”张三“的集合。
ArrayList<String> list = new ArrayList<String> (Collections.nCopies(10,"张三"));
3.插入操作
插入操作分为两种,一种是普通的插入,另一种是指定位置的插入。
3.1 普通插入
例如:
ArrayList<String> list = new ArrayList<String>();
list.add("a");
源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
插入元素时,ensureCapacityInternal方法会先判断数组的长度是否足够,是否需要扩容,然后再将新元素插入到数组中去。
ensureCapacityInternal方法源码:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
#calculateCapacity方法是计算当前数组的容量的,默认初始化的时候,数组为空,此时,数组容量为10
calculateCapacity方法源码:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
#如果插入元素后数组的容量>原数组的长度,则进行扩容
ensureExplicitCapacity方法源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
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);
}
扩容后数组的长度:newCapacity = oldCapacity + (oldCapacity >> 1) ,相当于扩容后的容量为原容量的1.5倍,准确来说应该是(int)3/2倍。
例如:原数组长度为7,扩容后的长度为:0111+0011 = 7+3 = 10。
完成扩容后,Arrays.copyOf方法将原数组的数据拷贝到扩容后的新数组中,Arrays.copyOf拷贝方法底层用的是System.arraycopy方法,System.arraycopy方法用法如下
int[] oldArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] newArr = new int[oldArr.length + (oldArr.length >> 1)];
System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
System.out.println("数组元素:" + JSON.toJSONString(newArr));
newArr[11] = 11;
newArr[12] = 12;
newArr[13] = 13;
newArr[14] = 14;
System.out.println("数组元素:" + JSON.toJSONString(newArr));
System.out.println("数组长度:" + newArr.length);
输出结果为:
数组元素:[1,2,3,4,5,6,7,8,9,10,0,0,0,0,0]
数组元素:[1,2,3,4,5,6,7,8,9,10,0,11,12,13,14]
数组长度:15
3.2 指定位置插入
例如:
ArrayList<String> list = new ArrayList<String>();
list.add(0,"张三");
list.add(1,"李四");
源码如下:
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++;
}
总结一下指定位置插入元素的步骤:
1. 检查插入位置index是否大于数组的长度size,如果大于,则抛异常
例如下面这种情况就会报错:
虽然一开始我们申请的数组容量是10,但是插入依赖于对size的判断
ArrayList<String> list = new ArrayList<String>();
list.add(1,"张三");
#报错信息
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
at java.util.ArrayList.rangeCheckForAdd(ArrayList.java:665)
at java.util.ArrayList.add(ArrayList.java:477)
at com.jesper.seckill.redis.MapTest.main(MapTest.java:46)
2.判断插入元素后,是否需要扩容
3.数据元素迁移,将待插入元素后的元素向后迁移
4.指定位置放入元素
5.数组长度自增
4.删除操作
1. 删除指定位置元素
源码:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
删除步骤:
1.检查索引是否在数组的范围
2.modCount自增(删除操作涉及数组结构的变化,故modCount需要加1)
3.将指定删除位置后的元素复制到原数组中,等同于将删除位置后的每个元素都往前移动了一位,这样会空出最后一位
4.最后将最后一位元素置为null。
2.删除指定元素
源码:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
删除步骤:
1.如果删除对象是null,遍历数组,找到为null的元素删除
2.如果删除对象非null,遍历数组,找到要删除的元素后删除,如果有多个相同的元素,则只会删除数组中第一个查找到的元素。
综合删除的两种方式,第一种不需要遍历,所以效率会比第二种要高。