ArrayList 定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
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;
}
首先可以看到ArrayList继承了AbstractList并实现了List,也就是说ArrayList是一个数组队列,拥有了基本的增删改查,遍历的操作。
同时实现了
Cloneable接口,即可以被cloneSerializable接口,意味着可以被序列化RandomAccess接口,RandomAccess接口是一个空的接口,和Serializable接口一样,也是作为一个标识,即可以快速访问,对于ArrayList来说,就是可以通过下标来访问元素。而LinkedList就没有实现这*个接口。
DEFAULT_CAPACITY 和 size 的关系
根据官方注解知道,DEFAULT_CAPACITY是数组总空间大小,而size是数组的当前的容量的大小。举个例子来说,一个可以装1L水的杯子,那么DEFAULT_CAPACITY就是1L,我们现在往杯子里倒入了0.5L水,那么size就是0.5L。当然在这,DEFAULT_CAPACITY的默认值是10,当size>10的时候,会进行扩容。
为撒 elementData 需要 transient 进行修饰
刚才我们讲到ArrayList内部其实就是用了一个Object[]来进行维护数据,那既然我们已经实现了Serializable接口,那为撒还要用transient来修饰elementData呢?来看看序列化/反序列化的代码
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
elementData = elements;
} else if (size == 0) {
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioral compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
ArrayList在序列化的时候会调用writeObject方法,将数组的size和elementData写入ObjectOutputStream,
在反序列化时调用readObject,从ObjectInputStream获取size和elementData,再恢复到elementData.
这样就可以很好的节省空间和时间。因为elementData整个的大小是CAPACITY,一般情况下都会预留一些容量,我们真正需要序列化/反序列化的只是当前存入的数据。
构造方法
无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
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);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
第一个空参构造函数,会默认将DEFAULTCAPACITY_EMPTY_ELEMENTDATA的引用传入elementData。在第一次添加元素的时候会扩容一个容量为10的数组。
第二个指定初始化的 capacity来创建 elementData, 一般推荐使用这种方式来创建ArrayList,减少扩容带来的内存开销。
第三个则是传入一个Collection来创建elementData
添加
add(E element)
添加方法有三个重载方法,最终都会调用下面这个add方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次扩容1.5倍
//如果扩容后的capcity的大小等于或小于mincapacity,且如果是使用空参构造器初始化的,那么就返回Math.max(DEFAULT_CAPACITY, minCapacity),否则返回 minCapacity(if minCapacity > 0)
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 使用空参构造函数elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
在第一个方法中,e是指当前add的元素,s指当前arrList的size。可以发现,当size的大小和elementData的长度相等时,才会进行扩容,即调用grow,从newCapacity方法中可以发现,每次扩容会是当前size(即 elementData.length)的1.5倍。
add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
在指定下标条件元素的步骤
- 检查
index,如果index小于0,或者大于size,那么会抛出IndexOutOfBoundsException异常 modCount++,后续会介绍用处- 判断当前
arrayList的size和当前elementData的长度是否相等,如果相当那么先进行扩容 System.arraycopy进行复制,把index这个下标空出来- 赋值
删除
remove(int index)
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
对于remove(int index)源码就比较简单了,唯一需要注意的就是在fastRemove中的判断,如果需要删除的是数组的最后一个元素,那么直接将最后一个元素设置为null,否则进行arraycopy,将index的值覆盖掉。
其它
Fail-Fast 机制
前面我们经常看到modCount++的操作,那么为什么要加这个呢?其实当每次对数组进行操作(修改)的时候,都会进行modCount++,这样做是为了记录修改次数。
我们知道 ArrayList 不是线程安全的,因此如果在使用迭代器的过程中如果有其他线程修改(新增/删除)了arrayList的数据(或当前线程在遍历的过程中对数据进行修改),那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
在每次进行遍历的时候,会先将modCount赋值给expectedCount,在迭代过程中,进行判断,如果它们不相等,则说明arrayList的数据已经被修改,抛出异常。
Arrays.copyOf 方法和 System.arraycopy 方法的区别?
Arrays.copyOf(T[], int length) 方法是 Arrays 工具类中用来进行任意类型数组赋值,并使数组具有指定长度的方法,ArrayList 中用这个方法来实现 elementData 数组的元素移动。但实际上 Arrays.copyOf 方法最终调用的是 System.arraycopy(U[], int srcPos, T[], desPos, int length) 方法,这个方法是一个native方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
- src 表示的是原始数组(源数组)
- dest 表示的是存放拷贝值的数组(目标数组)
- srcPos 是指原始数组中的起始位置(从原始数组的哪个位置开始拷贝)
- desPos 是指存放拷贝值的数组拷贝起始位置(从目标数组的哪个位置插入这些拷贝的值)
- length 表示要拷贝的元素数量(要从原始数组中拷贝多少个)。