关键方法
主要成员变量
//默认数组大小
private static final int DEFAULT_CAPACITY = 10;
// 当ArrayList的构造方法中显示指出ArrayList的数组长度为0时,类内部将EMPTY_ELEMENTDATA 这个空对象数组赋给elemetData数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 当ArrayList的构造方法中没有显示指出ArrayList的数组长度时,类内部使用默认缺省时对象数组为
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList的底层数据结构,只是一个对象数组,用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候此字段是不会被序列化的
transient Object[] elementData; // non-private to simplify nested class access
// 实际ArrayList中存放的元素的个数,默认时为0个元素。
private int size;
3个构造方法
public ArrayList(int initialCapacity) {
// 数组设为指定的容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//若指定容量小于0 数组仍然被设置为默认的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*数组默认为空数组
由于java 类特性 size会被初始化为0
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
主要操作:add(E), get(int), size(),remove(int),set(int, E) 、addAll(Collection)
add 添加元素,添加的过程会进行动态扩容
public boolean add(E e) {
//先确定数组容量,确保容量足够,不足时则进行扩容
ensureCapacityInternal(size + 1);
//此时容量一定足够,直接将元素放置到size+1处即可
elementData[size++] = e;
return true;
}
//计算对象当前所需的实际容量,若新容量小于最初初始值则返回初始值,否则返回新容量值
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//为true表示对象尚未发生过扩容,此时仍是原始数组,返回新容量值目标与默认初始容量中的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 确定内部最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//根据计算出的实际容量需求判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// o若所需的最小容量大于当前数组大小则需立即扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//开始扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新容量大小为旧值的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将数组复制到一个新数组上去,扩容成功
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
* 数组最大长度设置为 Integer.MAX_VALUE-8 以防止内存溢出,但具体解释网上没固定的答案好像
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
get(int) 获取元素 直接将元素以下标形式从数组中获取即可,这也是ArrayList查找速度极快的原因
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
remove(int) 移除元素时需要集体前移后面位置的元素前移一位,比较消耗资源,移除过程不会修改数组的大小
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//移除中间位置的元素时,直接将后面所有的元素前移一位,并将最后一个元素位置设为null即可,
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
set(int, E) 设置元素不能超出原有size值,即使容量允许也不行,即只能替换原有的值而不能增加
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
**size()**计算元素个数时 不用遍历,直接返回size即可
public int size() {
return size;
}
addAll相对与add方法来说addAll会直接将元素一次性复制到原有数组上去,并且最多只有一次扩容,效率相对与一个个调用add方法高很多
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
ArrayList遍历既可用forEach 也可用迭代器,若是遍历过程中含有删除操作,那么必须用迭代器,因为ArrayList删除元素时会将后面元素集体前移,forEach方法每次获取的下标却并没有改变,就会造成前移的元素部分没有遍历到,并且由于size大小也变了,还会抛出数组溢出异常
而迭代器是怎么避免这种情况的,我们来追寻源码
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
//判断遍历结束条件用的size会变化,所以利用游标来判断是否到达了数组末尾
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//每次remove后 原数组就会变化,所以此处也每次都去拿最新的数组
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//直接调用remove方法移除元素,此刻可能会发生集体前移
ArrayList.this.remove(lastRet);
//游标进行回拨一位,下一次进行next时 返回的是前移的第一个元素,由此成功避免了直接for循环时的删除+遍历的漏掉元素的情况
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
..... 其他方法在此略过,有兴趣者可查看详细源码
}
ArrayList的父类AbstractList中放置了一个参数modCount,每次调用Add方法或者remove方法时都会对其加1,迭代器检测到遍历的过程中modCount发生了变化就会抛出ConcurrentModificationException异常,以加快效率
protected transient int modCount = 0;
迭代器每次调用remove方法后都会更新存储的modCount值,但此时不是迭代器调用了remove或者add方法后,modCount值就会跟迭代器缓存的值不相等,此时会直接抛出异常来结束迭代,因为数据源发生了变化,继续遍历已经没有意义。
存储
ArrayList底层由Object数组变量存储数据,容量由size存储,这样size()方法直接返回size就可以
每当add元素时直接将其放到数组size+1下标位置即可,容量不足则扩容,具体见扩容
transient Object[] elementData;
private int size;
扩容
每次扩容后数组大小扩充为原数组长度的1.5倍
add() --> ensureCapacityInternal()计算实际容量需求 -->ensureExplicitCapacity()超出则扩容 -->grow()
|
elementData()
addAll方法与add流程一样,唯一得区别是下一个容量值不再是简单加一,而是加上新集合得size值,走完扩容流程后直接调用System.arraycopy批量复制数组
grow方法中对容量有一个新的标准,由于ArrayList不能无限扩容,当计算出的新数组长度大于Integer.MAX_VALUE-8后,最终扩容长度就一直是Integer.MAX_VALUE,不再上涨。
序列化
由于elementData被transient 修饰,因此ArrayList不能直接序列化,原因在于设计者认为Arraylist的数组大小经常大于实际数据量大小,因此序列化多出的空间不划算,因此需要额外处理以求只处理实际存在的数据。为此ArrayList提供了writeObject与readObject两个方法来实现序列化与反序列化。
序列化:ArrayList循环将数组elementData的元素写入ObjectOutputStream的状态信息中,源码如下
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// 写入数据大小
s.writeInt(size);
// 写入数组中的数据
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
反序列化: 调用readObject方法,读出writeObject方法写入的size与数组元素
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
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
源码可看出writeObject与readObject方法均是私有方法,因此不能直接调用。它们实际上是重写了ObjectOutputStream的ObjectInputStream的方法,
ObjectOutputStream序列化对象时会调用这个writeObject方法。
ObjectInputStream读取时也会调用readObject方法
数据结构优缺点
优点:数组结构赋予了很高的读取效率,以及不频繁扩容状态下的写入效率
缺点:由于扩容时需要批量将旧数据写入新数组中去,这个操作比较耗费时间,还有一点是在移除中间的元素后需要将后面的数据批量往前移动,这些都是不利因素。