1,ArrayList的秘密都在构造函数里
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2,接着 elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
/**
* 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;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
翻译elementData的注释
-
存储ArrayList元素的数组缓冲区。
-
ArrayList的容量是这个数组缓冲区的长度。
-
添加第一个元素时,任何elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为默认的DEFAULT_CAPACITY容量。
3,接着DEFAULT_CAPACITY
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
默认容量为10
4,看下添加第一个元素时发生了什么
/**
* Appends the specified element to the end of this list.
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
-
确定内部容量,size作为成员变量private int size;,实例化时默认赋值为0,也就是用(0+1)1去确定内部容量。
-
size 加 1 ,并给索引位置赋值。
-
返回true。
这样添加就完成了,是否扩容并在下标位置赋值。
5,进入ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
ensureExplicitCapacity(minCapacity);
}
如果是空数组,用传入的容量和默认的容量 之中 大的那一个作为初始化容量。也就是10个元素以内容量一直是10;
6,上面是初始化,下面才是真正的确定容量。进入ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) grow(minCapacity);
}
修改次数 加1。这个参数用作并发修改控制。
如果需要的容量大于数组的长度,用需要的容量扩容。
7,扩容,grow()
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
-
数组的原容量oldCapacity
-
准备的容量oldCapacity + (oldCapacity >> 1);>>1向右位移1位,高位补0,即除以2,结果为1.5倍的原容量。
-
准备的容量不够,就用需要的容量。
-
准备的容量大于最大的数组容量,就取最小的大于需要的容量的容量。
-
原则就是实现最小扩容。
8,常问:从list里面移除元素
//从下标处移除元素,注意参数是int,如果是integer,会调用第二个方法。
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;
}
//移除指定元素,快速移除,跳过边界检查并且不返回已删除的值。
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;
}
第一个方法从下标处移除元素,注意参数是int,如果使用integer,会调用第二个方法。
第二个方法移除指定元素,移除的时候也是用的下标移除,但不是调的第一个方法,因为index一定是合法的而且移除的值是用户传进来的。也就是它跳过边界检查并且不返回已删除的值。
重点:list移除只需要调用remove方法,不需要使用循环遍历,迭代器,stream等实现。
9,ConcurrentModificationException
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
foreach时,final int expectedModCount = modCount;
modCount已确定且不可修改,而添加移除等方法中会调用checkForComodification方法,所以抛出异常。
10,fail-fast 快速失败
<p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
Arraylist类73行定义,翻译为
这个类的iterator和listierator方法是快速失败的:如果迭代器被创建,不管何时何地,只要不是调用迭代器自己的remove或add方法,迭代器将抛出一个ConcurrentModificationException。因此如果是并发修改,迭代器会迅速而彻底地失败。翻译的不行,可自行翻译。
11,序列化
transient Object[] elementData;
可见elementData被transient 修饰,也就是不参与序列化,但是如果这个参数都不参与序列化,那反序列化的时候得到的不就是一个空列表吗,所以肯定是参与序列化了,下面看看它怎么被序列化的。
转载申明:https://www.youfengxin.com/simpleapi/arraylist.html
本文深入剖析了ArrayList,介绍其构造函数,指出存储元素的数组缓冲区elementData相关特性,默认容量为10。阐述添加元素时的容量确定、扩容机制,实现最小扩容。还讲解了移除元素方法,以及foreach时ConcurrentModificationException的原因和快速失败机制,最后提及序列化问题。

2556

被折叠的 条评论
为什么被折叠?



