本系列是Java详解,专栏地址:Java源码分析
ArrayList 官方文档:ArrayList (Java Platform SE 8 )
ArrayList .java源码共1760行,下载地址见我的文章:Java源码下载和阅读(JDK1.8/Java 11)
文件地址:openjdk-jdk11u-jdk-11.0.6-3/src/java.base/share/classes/java/util
在看源码前,先说一下为什么要看源码。因为我关注的公众号三天两头推送这种文章:《为什么阿里巴巴要求谨慎使用ArrayList中的subList方法》,这让我很恼火。于是今天就打算自己看源码。
1.实现了List
接口,并允许空元素。
2.提供了操作内部的数组的方法,类似Vector
,但不是线程安全的。
3.size
, isEmpty
, get
, set
, iterator
, listIterator
操作是O(1)时间复杂度的。add
操作在时间均摊后是O(1)时间复杂度。
4.与LinkedList
相比,constant factor
常数因子较低。
5.每个ArrayList
实例都有capacity
,也就是在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList后,其容量会自动增长。
6.可以使用ensureCapacity
手动扩容,减少自动扩容带来的开销。
7.ArrayList
不是synchronized
,不是线程安全的。可以通过Collections.synchronizedList
实现线程安全。
8.迭代器是fail-fast
,如果在创建迭代器之后的任何时间对列表进行结构修改(除了通过迭代器自己的 方法remove或 add方法外),迭代器都将抛出异常ConcurrentModificationException
。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
继承自AbstractList
,实现了接口List
,RandomAccess
。
private static final int DEFAULT_CAPACITY = 10;//默认初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空实例的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//使用无参构造初始化ArrayList时默认的空数组
transient Object[] elementData;//存储ArrayList元素的数组。
private int size;//the number of elements it contains
之所以存储数据的数组不设为private
是为了让嵌套类访问更方便。
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
add操作可以说是ArrayList
最重要的操作之一了。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
这里有个问题,那就是代码明明很简单,为什么要写2个函数,不直接在一个函数中写完。根据注释,这是为了让add(E)
函数的字节码的长度小于35,更有效的被调用于C1-compiled loop
,这应该是JIT优化的手段。根据我查阅资料,函数的字节码小于35才有可能成为内联函数。
当数组满时,自动扩容:
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
扩容函数表示一般扩容50%
一个很明显的缺点:使用数组的拷贝,并没有利用原来的数组。
既然我们知道了ArrayList
的底层实现就是数组,那么我很好奇ArrayList
会如何实现删除操作:
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
根据注释,移除元素后,剩下的所有元素都要左移。使用系统提供的拷贝函数进行拷贝。
到了关键的subList
函数。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList<>(this, fromIndex, toIndex);
}
private static class SubList<E> extends AbstractList<E> implements RandomAccess {
private final ArrayList<E> root;
private final SubList<E> parent;
private final int offset;
private int size;
/**
* Constructs a sublist of an arbitrary ArrayList.
*/
public SubList(ArrayList<E> root, int fromIndex, int toIndex) {
this.root = root;
this.parent = null;
this.offset = fromIndex;
this.size = toIndex - fromIndex;
this.modCount = root.modCount;
}
public E set(int index, E element) {
Objects.checkIndex(index, size);
checkForComodification();
E oldValue = root.elementData(offset + index);
root.elementData[offset + index] = element;
return oldValue;
}
public E get(int index) {
Objects.checkIndex(index, size);
checkForComodification();
return root.elementData(offset + index);
}
}
看到返回一个新的对象,内部类的实例SubList
。
代码中的注释提到了要点:SubList
函数提供原始list
的一个视图。
所以就有了以下注意事项:
1.subList函数返回的对象不能被转为ArrayList
,只能当做List
用。因为SubList只是ArrayList的内部类,他们之间并没有继承关系,故无法直接进行强制类型转换。
2.SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。
3.当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。
4.对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。
5.对子List做结构性修改,操作同样会反映到父List上。
6.对父List做结构性修改,子List会抛出异常ConcurrentModificationException。
1.ArrayList 是一个数组队列,相当于 动态数组。
2.ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
参考: