List
ArrayList
重要属性
- 通过无参构造方法创建ArrayLit 的时候,默认的数组空间是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}, 在第一次添加元素的时候会扩容并初始化数组的大小 ,所以对于需要添加元素的ArrayList的初始化尽量指定数组容量,避免扩容
/**
* 数组默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 数组为空时,默认的对象值
* 参考 new ArrayList(0);
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于提供默认大小的实例的共享空数组实例.
* 参考 new ArrayList()
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList 元素的数组,用transient修饰 序列化时并非序列化整个数组,而是逐个序列化数组中的元素
*/
transient Object[] elementData; // non-private to simplify nested class access 不使用private修饰简化内部类的访问
/**
* ArrayList元素个数,可读取的数据个数
*
* @serial
*/
private int size;
/**
* 继承自父类AbstractList, 用于记录集合的修改(添加,删除元素,清除集合)次数
*/
protected transient int modCount = 0;
-
为什么 elementData 被transient 修改?
- elementData里面不是所有的空间都有数据,因为容量的问题,elementData里面有一些元素是空的,这种是没有必要序列化的,可以避免序列化空的元素, 本质是代码性能优化问题
-
ArrayList 如何支持序列化
-
ArrayList 实现了writeObject() 和 readObject() ,通过for 循环的方式逐个序列化或者反序列化数组中的元素
-
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(); } }
-
反序列化,反序列化后会返回一个新对象
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(); } } }
-
-
modCount 的作用?
- 通常在迭代器中校验集合修改次数和预计修改次数是否一致,如果不一致会抛出ConcurrentModificationExceptions
-
为什么会出现 ConcurrentModificationExceptions 异常?为什么集合的遍历更新操作不要直接使用ArrayList 提供的 add 和 remove 方法?
-
迭代时直接通过集合的更新方法(remove,add)修改集合元素会导致modCount被修改,导致和迭代器中维护的预期的修改次数expectedModCount不一致,从而抛出异常
-
expectedModCount : Itr类里有一个成员变量expectedModCount,它的值为创建Itr对象的时候List的modCount值
-
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
-
-
迭代器通过游标cursor的方式记录当前下标,指向对应的元素, 当直接通过ArrayList 调用 add, remove 方法时,会导致size 的变更,从而导致数据越界或者读取不到对于的元素 操作前校验 fail-fast
public E next() { 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; return (E) elementData[lastRet = i]; }
-
-
为什么ArrayList 的遍历更新要 通过 迭代器的 remove 和 add 方法
-
迭代器更新元素的时候会同步更新迭代器的 游标 cursor 和 expectedModCount
-
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
-
主要api
插入方法 add
- 新增元素前会校验数组的长度,当数组长度小于新增后的长度时会进行扩容
- 扩容后的大小为原来的1.5倍,最大容量是Integer 的最大值,并且会考虑溢出
- 扩容其实是将旧数组复制到一个容量更大的新数组, elementData 指向新数组
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 修改modCount, 扩容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 当数组长度小于需要的长度时,ArrayList 进行扩容 考虑内存溢出
if (minCapacity - elementData.length > 0)
grow(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); // 超大容量, ArrayList 的最大容量是 Integer 的最大值
// 此处可以看出,扩容其实是将旧数组复制到一个容量更大的新数组, elementData 指向新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 考虑溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
删除元素 remove
- 移除实际是通过System.arrayCopy对原数组进行截取操作, 指定位置插入
public void add(int index, E element)
也是同理的操作
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; // 将引用置空,让GC回收
return oldValue;
}
截取子集合 subList
- 截取的子集合 和 ArrayList 共用同一个数组,SubList 只定义了重新指向该数组的偏移量,对于子集合元素的修改都会改变原集合内容
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex); // SubList 为ArrayList 的一个内部类
}
// subList 构造方法 只定义了重新指向该数组的偏移量
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
// SubList 的 set 方法
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e; // 更新外部类的数组元素
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index); // 从外部类获取元素
}