目录
成员变量
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量。
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小的空实例的共享空数组实例。 我们将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素 *时要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
ArrayList 的元素存储在其中的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度。 添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。
*/
transient Object[] elementData; // transient 不可序列化
/**
* ArrayList 的大小(它包含的元素数)。
*
* @serial
*/
private int size;
...
}
由此可看出ArrayList继承自AbstractList抽象类,并且实现了一些接口,首要就是List。其中需要注意的就是RandomAccess:即标注它需要支持随机访问这样的行为,java.io.Serializable:即表示它需要支持序列化和反序列化,但是注意到elementData中的transient关键字,意味着不能被简单序列化,需要后续自身的实现。
构造函数
/**
构造一个具有指定初始容量的空列表。
参数:
initialCapacity – 列表的初始容量
抛出:
IllegalArgumentException – 如果指定的初始容量为负
*/
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);
}
}
/**
* 构造一个初始容量为 10 的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。
参数:
c – 其元素将被放入此列表的集合
抛出:
NullPointerException – 如果指定的集合为空
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {//size设置
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
可以看出构造函数主要用来初始化elementData数组,设置size属性。
添加(尾插法)
/**
将指定的元素附加到此列表的末尾。
参数:
e - 要附加到此列表的元素
返回:
true (由Collection.add指定)
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//涉及到扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); //原始为空时,容量也得在10以上
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) //所需最小容量不够,扩容elementData
grow(minCapacity); //扩容
}
/**
要分配的数组的最大大小。 一些 VM 在数组中保留一些头字。
尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超出 VM 限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
增加容量以确保它至少可以容纳由最小容量参数指定的元素数量。
参数:
minCapacity – 所需的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍
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.5倍,若不够则扩容到所需容量的值。扩容后如果发生容量溢出,需要把容量设置到Integer.MAX_VALUE()。
这里注意ensureCapacityInternal(size + 1),elementData[size++] = e两句是分开的,这也是为什么多线程环境下ArrayList不是线程安全的一个重大原因,因为可能会发生数组越界。
查询
/**
返回此列表中指定位置的元素。
参数:
index – 要返回的元素的索引
返回:
此列表中指定位置的元素
抛出:
IndexOutOfBoundsException –
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
检查给定的索引是否在范围内。
如果不是,则抛出适当的运行时异常。
此方法*不*检查索引是否为负数:它总是在数组访问之前立即使用,如果索引为负数,则抛出 ArrayIndexOutOfBoundsException。
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
主要是需要检查是否发生索引下表越界, 会出现IndexOutOfBoundsException异常,这里明确指明了index>=size时会抛出这个异常,其实显然当index<0时也会发生ArrayIndexOutOfBoundsException异常。
删除
/**
移除此列表中指定位置的元素。 将任何后续元素向左移动(从它们的索引中减去一个)。
参数:
index – 要删除的元素的索引
返回:
从列表中删除的元素
抛出:
IndexOutOfBoundsException –
*/
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; // size -1
return oldValue;
}
/**
从此列表中删除第一次出现的指定元素(如果存在)。 如果列表不包含该元素,则它保持不变。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果这样的元素存在)。 如果此列表包含指定的元素(或等效地,如果此列表因调用而更改),则返回true 。
参数:
o - 要从此列表中删除的元素(如果存在)
返回:
如果此列表包含指定的元素,则为true
*/
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;
}
private void fastRemove(int index) {
modCount++;
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
}
这里很明显地体现出了ArrayList这种数据结构对于删除操作的高成本性,极端情况当你删除第一个元素后剩余的N-1个元素需要一次向左移动写入,这种时间复杂度是O(n)的,写入成本也很高。
插入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); //如果可能的话,需要扩容
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //依次向后移动一位
elementData[index] = element; //放入元素
size++; //size +1
}
与删除操作类似,这里看出来插入位置之后的元素需要依次向右移动一位。时间复杂度也是O(n)的。
更新
/**
用指定的元素替换此列表中指定位置的元素。
参数:
index – 要替换的元素的索引
element – 要存储在指定位置的元素
返回:
之前在指定位置的元素
抛出:
IndexOutOfBoundsException –
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
这个就体现出了随机访问的优势,直接写入,时间复杂度O(1)。
序列化与反序列化
对象实现java.io.Serializable接口以后,序列化的动作不仅取决于对象本身,还取决于执行序列化的对象。
以ObjectOutputStream为例,如果ArrayList或自定义对象实现了writeObject,readObject或readObjectNoData方法,那么在序列化和反序列化的时候,就按照自己定义的方法来执行动作,所以ArrayList就自定义了writeObject和readObject方法,然后在writeObject方法内完成数组元素的自定义序列化动作,在readObject方法内完成数组元素的自定义反序列化动作。
其他的一些执行序列化的对象的处理方式可能会和ObjectOutputStream不一样。
/**
将ArrayList实例的状态保存到流中(即序列化它)。
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 防止序列化期间有修改
int expectedModCount = modCount;
//将当前类的非静态和非瞬态字段写入此流。
s.defaultWriteObject();
// 写出大小作为与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实例(即反序列化它)。
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
可以看出这里序列化操作需要先序列化当前类的非静态和非瞬态字段。然后对于elementData数组,先写入size,然后再依次写入elementData对应size内的所有元素。反序列化操作是对应的。
这里给出一个demo。
//序列化
try( FileOutputStream fileOutputStream = new FileOutputStream("test.txt")) {
try( ObjectOutputStream out = new ObjectOutputStream(fileOutputStream)){
out.writeObject(list);
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println("hello world");
//反序列化
try(FileInputStream fileInputStream = new FileInputStream("test.txt")){
try(ObjectInputStream in = new ObjectInputStream(fileInputStream)){
ArrayList<Integer> getList= (ArrayList)in.readObject();
for (Integer integer : getList) {
System.out.println(integer);
}
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}