ArrayList 的 elementData 为什么要用 transient 修饰?
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
Java中的ArrayList底层实现机制是动态扩容数组,elementData数组相当于容器,当容器不足时就会再扩充容量,但是容器的容量往往都是大于或者等于ArrayList所存元素的个数。
例如:
List list = new ArrayList(10);
这段代码的实际构建过程是:【 new Object[initialCapacity] 】
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
}
// ...
}
因此当我们通过【 new ArrayList(10) 】构建一个新对象时,它的实际空间占用情况是:
Object[] objs = new Object[10];
System.out.println(objs.length); // 10
注:动态扩容内容后面再详解
根据上面的内容,如果不使用 transient 修饰 elementData ,会导致序列化elementData数组时,将未使用的空间也序列化了,无疑会浪费一部分空间。因此设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组。
ArrayList覆写的writeObject()方法代码:(只保留关键代码)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// ...
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]);
}
// ...
}
注意在代码的 for 循环中,使用的是【 i < size】,而不是 i < elementData.length,也可以看出来这里只序列化了存储的实际元素。通过这样的方式,可以节约一部分的空间和时间。
ArrayList 中 modCount 的作用
在ArrayList中有个成员变量modCount,继承于AbstractList。
在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
【注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。】
如果modCount的值意外改变,那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常,通过 checkForComodification 判断:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
ArrayList实现了自己的迭代器:
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; // expectedModCount 在遍历时被固定
Itr() {}
// ...
}
例如:
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("a".equals(str)) {
strList.remove(str);
}
}
在执行了remove操作之后就会抛出ConcurrentModificationException,原因是利用了迭代器进行遍历,遍历时发生了异常并抛出。具体的流程是:
1.strList 添加 a、b、c 后共有三个元素,此时 modCount 为 3
2.遍历时,当碰到 b 时,调用了 strList 上的 remove 方法,这里会让 modCount 值加1,也就是4,但是 expectedModCount 在遍历时被固定,这就会导致 modCount 不再等于 expectedModCount,由此在下一次执行 iterator.next() 时就会抛出ConcurrentModificationException
因此,在遍历中删除元素的正确做法应该是使用Iterator:
// ...
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("a".equals(str)) {
iterator.remove();
}
}
补充:测试以下代码
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("b".equals(str)) {
strList.remove(str);
}
}
运行后可以发现,这里并没有抛出异常,与之前的代码唯一的区别仅在于删除的是 b 元素。这是因为,执行 strList.remove 是,modCount 会加一,但同时需要注意的是,remove 后 size 也减一,这导致 hasNext 在判断时,因为 cursor != size 而返回 false,也就不会执行循环体内容,自然也就不会抛出异常了。
public boolean hasNext() {
return cursor != size;
}
经检验,只要是当remove删除倒数第二个元素时,恰好逃脱了对最后一个元素的迭代,没有next,便没有报错。
通常来说,在迭代时不应该擅自去改动 arrayList,但是删除元素可以通过迭代器本身修改,如 iterator.remove();
ArrayList 的扩容机制
在 ArrayList 中,一共有三个构造函数:
public ArrayList() { //... }
public ArrayList(int initialCapacity) { //... }
public ArrayList(Collection<? extends E> c) { //... }
- 无参构造器扩容机制
public ArrayList() {
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当使用无参构造器创建ArrayList对象时,初始容量为0。但是当第一次添加元素时,则直接扩容为10。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 1、添加元素时,首要要确定能不能添加,调用 ensureCapacityInternal,传入需要的最小容量
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); // 2、首先通过 calculateCapacity 计算
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 3、当我们使用无参构造器时,这里条件正好满足
return Math.max(DEFAULT_CAPACITY, minCapacity); // 4、那么这里返回 DEFAULT_CAPACITY ,DEFAULT_CAPACITY 为 10
}
return minCapacity;
}
- 参数为容量大小(initialCapacity)的构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果指定大于 0 的initialCapacity,则直接构建指定大小的 Object[] 即可
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
/**
* 如果传入容量为 0,则赋值为 EMPTY_ELEMENTDATA,其中:private static final Object[] EMPTY_ELEMENTDATA = {};
* 需要注意的是,这里和无参构造器不同,在添加时,calculateCapacity 方法判断 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为false,
* 那么 calculateCapacity 方法返回的就是 minCapacity,在当前情况下 minCapacity 为 1,也就是第一次添加容量也为 1,而不是 10
*/
this.elementData = EMPTY_ELEMENTDATA; //
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
- 参数为 Collection<? extends E> 的构造器
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray(); // 首先将 Collection 转为 Object[]
if ((size = a.length) != 0) { // a 不为空
if (c.getClass() == ArrayList.class) {
elementData = a; // 如果传入的也是 ArrayList,直接将 a 赋值即可
} else {
elementData = Arrays.copyOf(a, size, Object[].class); // 否则,通过 Arrays.copyOf 复制数据
}
} else {
// a 为空,则直接相当于 new ArrayList(0)
elementData = EMPTY_ELEMENTDATA;
}
}
- ensureExplicitCapacity 及 grow 方法
在上面提到,calculateCapacity 方法计算容量后,将返回值传入 ensureExplicitCapacity 中:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
/**
* 如果所需容量 minCapacity 大于 elementData 的长度,则需要扩容,这里的 minCapacity = size + 1
*/
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
再进入 grow 中看一下:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 不能超过最大安全值减8 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); // 扩容,并复制数据
}
- 其他
/**
* 将此ArrayList实例的容量修剪为列表的当前大小。应用程序可以使用此操作来最小化ArrayList实例的存储。
* 简单来说,就是把未使用的空间给抹去
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* 如有必要,增加此ArrayList实例的容量,以确保它至少可以容纳最小容量参数指定的元素数。
* 简单来说,就是确定当前空间是够存放 minCapacity 多的元素的,如果不能保证,则执行 ensureExplicitCapacity 扩容
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
其他常规方法看源码即可。