//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//数组的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//一个空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//一个空的默认大小的数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数据的数组
transient Object[] elementData;
//记录数组的大小
private int size;
//记录集合结构被修改的次数
protected transient int modCount = 0;
当调用有参构造指定容量的时候,会把数组elementData指向它。减少空数组频繁创建的内存开销。
调用ArrayList的无参构造器的时候,会把数组elementData指向它。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
思考1:为什么要区分为两个空数组实例呢,直接一个不行么?
是为了优化性能和内存使用:
细心的肯定发现,elementData是用transient关键字来修饰的。
思考2:为什么要用transient来修饰呢?
ArrayList通过重写writeObject和readObject方法来实现自定义序列化,只将必要的元素序列化。(具体后面会分析)
——使用transient关键字修饰elementData数组的好处是,它允许ArrayList在序列化时只关注实际存储的数据,而不是整个数组的容量。这样可以使得序列化过程更加高效,并且可以根据需要调整序列化的细节。例如,如果ArrayList中的某些元素是临时的或者不需要序列化,那么可以通过这种方式来避免不必要的序列化操作。
是Java集合类中的一个保护级别的字段,记录集合结构被修改的次数(改变集合大小或者以其他方式干扰正在进行的迭代器遍历的操作,如添加、删除或替换集合中的元素。)
作用:支持 Java 集合的 fail-fast 迭代器机制
——————
fail-fast(快速失败)迭代器会在迭代过程中抛出 ConcurrentModificationException 异常,以响应不可预测的并发修改。这种机制确保了在迭代过程中,如果有其他线程对集合进行了结构性修改,迭代器能够及时检测到这种修改并报错,从而避免出现不确定的行为。
这里就想到了平时会遇到的一种实际情况:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//使用for循环
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("b")) {
list.remove(i); // 可能会抛出 ConcurrentModificationException
}
}
//使用迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("b")) {
iterator.remove(); // 安全的,不会抛出 ConcurrentModificationException
}
}
思考3:为什么用for循环会抛出异常,而用迭代器不会呢?
这里就和modCount相关,当对集合进行删除/添加操作的时候,modCount就会++
(上面已有,不做赘述)
//指定容量大小
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) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
//如果传入集合也是ArrayList,就直接把elementData执行toArray后的数组a
elementData = a;
} else {
//如果不是,就调用Arrays.copyOf方法把a赋值给elementData
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
思考4:为什么要用Arrays.copyOf()
》不带参数
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) {
//如果是无参构造的空数组,那么就去默认容量10和传入容量的大者;否则直接为传入容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//保证显式的容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果需要的容量比目前数组的长度更大,就调用grow进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
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);
// 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;
}
总结:
》带参数
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++;
}
private void rangeCheckForAdd(int index) {
//这个就是我们常见的数组下标越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
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;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public void clear() {
// 将 size 设置为 0,表示没有元素
size = 0;
// 将数组中的所有元素位置置为 null,以帮助垃圾回收
for (int i = 0; i < size; i++) {
elementData[i] = null;
}
}
注意:clear之后只是把内部数组元素个数size置为0,但是内部数组的内存空间大小是不变的。
》writeObject()
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 behavioural 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();
}
}
writeObject方法在序列化的时候调用,负责把ArrayList的状态写入到ObjectOutputStream。
这个方法会先写modCount,然后写入ArrayList的实际大小size,最后逐个写入存储在elementData中的数据。
》readObject()
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();
}
}
}
readObject方法在反序列化的时候调用,负责从ObjectInputStream中读取ArrayList的状态。这个方法先读取modCount和size。然后根据读取的大小创建一个新的elementData数组,并且逐个读取元素来填充到这个数组中。
public Iterator<E> iterator() {
return new Itr();
}
//迭代器的内部类
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回元素的下标
int lastRet = -1; // 最近一次返回元素的下标,在调用next() 或 previous() 方法时,lastRet 都会被更新为返回元素的索引
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//modCount检查
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
//返回元素,并且更新lastRet
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
//remove后会把lastRet设置为-1,防止连续调用remove
lastRet = -1;
//更新expectedModCount为当前值
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//判断modCount与最开始记录的是否一致
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
}
从上述可以看出,ArrayList有一个内部迭代器,每次在调用next()、add()、remove()方法时,都会先判断modCount和expectedModCount是否相等,不相等说明在迭代期间是否被更改过表结构。
————
而上文提到的用迭代器的remove、add不会报错,就是因为内部迭代器在调用这些方法后,会把expectedCount更新。
注意:
remove方法使用lastRet来定位删除元素的,所以remove得在 previous() 、next()之后调用。如果 lastRet 为 -1,表示没有元素被迭代,那么调用 remove() 或 set() 就是非法的,迭代器也会抛出 IllegalStateException 异常。
因篇幅问题不能全部显示,请点此查看更多更全内容