ArrayList 类是 List 接口的可调整大小的数组实现。实现所有可选的列表操作,并允许所有元素,包括null。除了实现 List 接口之外,该类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相当于Vector,除了它是非同步的)
size、isEmpty、get、set、iterator 和 listIterator 操作在常量时间内运行。 add 操作在 amortized constant time (平摊常数时间)内运行,即添加 n 个元素需要 O(n) 时间。所有其他操作都在线性时间内运行(在大体上讲)。与 LinkedList 实现相比,该常量系数较低。
每个 ArrayList 实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少和列表大小一样大。 当数组列表中添加元素时,它的容量会自动增长。除了添加一个元素具有固定的平摊时间成本这一事实之外,没有指定增长策略的详细信息。
在使用 ensureCapacity 操作添加大量元素之前,应用程序可以增加 ArrayList 实例的容量。这可能会减少增量再分配的数量。
注意,ArrayList 的实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,并且至少有一个线程在结构上修改了该列表,那么它必须在外部进行同步(结构修改是任何添加或删除一个或多个元素,或显式调整备份数组大小的操作;仅仅设置元素的值不是结构修改)。这通常是通过同步某个自然封装了列表的对象来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法”包装“该列表。这最好在创建时完成,以防止意外地不同步访问列表。如:List list = Collections.synchronizedList(new ArrayList(...));
该类的 iterator 和 listIterator 方法返回的迭代器是快速失败(fail-fast) 的:如果在迭代器创建后的任何时间对列表结构进行了修改,除了通过使用迭代器自己的 remove 或 add 方法,迭代器将抛出一个ConcurrentModificationException。因此,面对并发修改时,迭代器会快速而干净地失败,而不是在未来不确定的时间里冒任意的、不确定的行为的风险。
请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬保证。fail-fast 迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写依赖这个异常的程序来判断其正确性是错误的:迭代器的快速失败行为应该只用于检测 bug。
| 属性 | 说明 |
|---|---|
| 数据结构 | 数组 |
| 容量 | 自动扩容、回收 |
| 是否允许null | 允许 null 值 |
| 时间复杂度 | size、isEmpty、get、set、iterator 和 listIterator 方法都以固定时间运行,时间复杂度为 O(1)。add和 remove 方法需要 O(n) 时间。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低; |
| 是否同步 | 不是同步的 |
| 迭代器 | fail-fast 迭代器 |
构造器
public ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。
public ArrayList():构造一个初始容量为 10 的空列表。
public ArrayList(Collection<? extends E> c):构造一个包含指定集合元素的列表,按照集合迭代器返回元素的顺序。
字段
int modCount:在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。通过这个字段,实现了迭代器中 fail-fast 的行为。
private int size:数组元素大小。
方法
以下方法中,加粗的为 ArrayList 中新添的方法(省略了基类中的部分接口):
-
通过索引访问
E get(int index):通过索引查找元素的效率是很高的。E set(int index, E element)void add(int index, E element)E remove(int index) -
查询操作
int size()boolean isEmpty()boolean contains(Object o) -
搜索操作
int indexOf(Object o)int lastIndexOf(Object o) -
修改操作
boolean add(E e)boolean remove(Object o)boolean addAll(Collection<? extends E> c)boolean addAll(int index, Collection<? extends E> c)boolean removeAll(Collection<?> c)boolean retainAll(Collection<?> c)void removeRange(int fromIndex, int toIndex)boolean removeIf(Predicate<? super E> filter)void replaceAll(UnaryOperator<E> operator)void sort(Comparator<? super E> c)void clear():从该列表中删除所有元素。该调用返回后,该列表将为空。(这个操作不改变列表的数组容量大小) -
迭代器
Iterator<E> iterator()ListIterator<E> listIterator()ListIterator<E> listIterator(int index)Spliterator<E> spliterator() -
遍历
void forEach(Consumer<? super E> action) -
视图
List<E> subList(int fromIndex, int toIndex):返回一个视图,在这个视图上的操作会影响数据本身。 -
拷贝
Object clone():返回这个ArrayList实例的浅拷贝(元素本身不会被复制)。使用了 Arrays.copyOf(elementData, size); 操作。 -
数组与 List 的桥梁
Object[] toArray()<T> T[] toArray(T[] a) -
容量管理
void trimToSize():将这个ArrayList实例的数组容量调整为列表的当前大小。使用的是Arrays.copyOf(elementData, size) 方法。void ensureCapacity(int minCapacity):如果有必要,增加这个 ArrayList 实例的容量,以确保它至少能容纳由最小容量参数指定的元素数量。
源码
ArrayList 整体结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/* 默认初始容量 */
private static final int DEFAULT_CAPACITY = 10;
/**
* 要分配的数组的最大大小
* 有些虚拟机在数组中保留了一些标题字
* 尝试分配更大的数组可能导致 OutOfMemoryError:Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 实际存储元素的数组
* 注意到这里的访问控制是 non-private 的,并且使用了 transient 修饰
*
* non-private to simplify nested class access
(非私有,以简化嵌套类访问)
*/
transient Object[] elementData;
/* 数组元素大小 */
private int size;
}
ArrayList 类图
- 实现了 RandomAccess、Cloneable、Serializable 接口,可以随机访问、克隆、序列化|反序列化。
- 实现了 List 接口,是 List 的实现之一;实现了 Collection 接口,是 Java 集合框架的成员之一。
- 实现了 Iterable 接口,可以使用 for-each 迭代。
构造器
/**
* 构造一个初始容量为10的空列(虽然在当前 elementData 中是一个空数组,但是在新增一个元素前,会先将容量扩为 10)。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个具有指定初始容量的空列表。
*/
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);
}
}
/**
* 构造一个包含指定集合元素的列表,按照集合迭代器返回元素的顺序
* 在这个里面,为了防止 c.toArray() 方法不正确的执行,导致没有返回 Object[],特殊做了处理
* 不管传进来的是什么类型的数组,都会转为 Object[]
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 可能不正确的,不返回 Object[]
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
在以上 3 种构造器方法中,分别对 elementData 赋予了不同的值 EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 或者具体的数组:
- 当使用无参构造器
ArrayList()时,elementData 的值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。 - 当指定容量为 0,或者使用空集合创建时,elementData 的值为 EMPTY_ELEMENTDATA。
对于上述构造器,仅仅是申请了数组大小,对 size 依旧是 0;如果紧接着使用 set() 更改数组内元素,则会引发数组索引越界异常(这是因为判断是否越界是根据 size 进行的)!
部分方法
增删逻辑
都说 ArrayList 的增删是很慢的,接下来看看为什么慢:
-
新增
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } 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++; }对于新增位置之后的元素都要进行复制;每次操作之前都会进行扩容检测,如果进行扩容,那速度将会更慢。
-
删除
// 删除指定位置的元素 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; } private void fastRemove(int index) { modCount++; 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 } // 清空 list 列表 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }对于删除,都要讲删除位置之后的元素进行复制。
ArrayList 容量管理机制
以 new ArrayList<>().add("Hello"); 为例分析 ArrayList 的扩容机制:
1)对于构造器中的 elementData:
- 如果
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA则将会将元素扩充为 10; - 如果
elementData == EMPTY_ELEMENTDATA则将会将元素扩充为 1;
2)通常情况新容量是原来容量的 1.5 倍;如果 原容量的 1.5 倍 < minCapacity 那么就扩容到 minCapacity;(那是不是说,每次扩容最小扩容 1.5 倍?!)
3)特殊情况会扩容到 Integer.MAX_VALUE(这个操作可能引发 OutOfMemoryError)
4)add、addAll、readObject 等操作都会使用 ensureCapacityInternal() 进行扩容;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 增加 modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 如果该实例是通过使用无参构造器 ArrayList() 创建的,第一次扩充容量为 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// "如果要扩容的容量 > 当前容量时" 才进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 具体的扩容策略
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); /* new = old * 1.5 */
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:已经超出 int 的范围了
throw new OutOfMemoryError();
// 如果要扩充的容量大于 MAX_ARRAY_SIZE 时,将返回 Integer.MAX_VALUE。
// 注意:这时可能抛出 OutOfMemoryError
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
在扩容的时候,老版本的 jdk 和 8 以后的版本是有区别的,8 之后的效率更高了,采用了位运算,右移一位,其实就是除以 2 这个操作。
写代码验证一下这个扩容机制:
public class ListGrowTest {
public static void main(String[] args) throws Exception {
ArrayList defaultCapacity = new ArrayList();
System.out.println("new ArrayList() 初始化长度:" + getElementData(defaultCapacity));
defaultCapacity.add(1);
System.out.println("new ArrayList().add() 后长度:" + getElementData(defaultCapacity));
System.out.println("--- --- ---");
ArrayList emptyCapacity = new ArrayList(0);
System.out.println("new ArrayList(0) 初始化长度:" + getElementData(emptyCapacity));
emptyCapacity.add(1);
System.out.println("new ArrayList(0).add() 后长度:" + getElementData(emptyCapacity));
}
public static int getElementData(ArrayList arrayList) throws Exception {
Class<? extends ArrayList> aClass = arrayList.getClass();
Field field = aClass.getDeclaredField("elementData");
field.setAccessible(true);
Object[] o = (Object[]) field.get(arrayList);
return o.length;
}
}
/*
new ArrayList() 初始化长度:0
new ArrayList().add() 后长度:10
--- --- ---
new ArrayList(0) 初始化长度:0
new ArrayList(0).add() 后长度:1
*/
5)ArrayList 还提供了一个 public 的扩容方法进行扩容;
public void ensureCapacity(int minCapacity) {
// 如果是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则 minExpand = 0,否则为 10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
6)当一直往数组中添加元素,然后删除大百分比元素时,将会造成空间上的浪费,ArrayList 提供了一个相应的 public 方法来对容量进行回收。如下:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ArrayList 中的迭代器
在 ArrayList 中,总共有 3 种迭代器:普通的迭代器、List 专有的迭代器 和 可分割的迭代器(并行遍历迭代器)。同时应该注意到,这些迭代器都是 fail-fast 的。
先来看一个通用的迭代器(通过 iterator() 获得):
public Iterator<E> iterator() {
return new Itr();
}
/**
* 内部类 ArrayList.Itr 是 AbstractList.Itr 的优化版本
*/
private class Itr implements Iterator<E> {
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; // 返回的最后一个元素的索引; 如果没有返回 -1
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification(); // 注意这个方法:fail-fast 的实现
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];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
// 迭代器 fail-fast 机制的实现:如果 modCount 不等于当前 expectedModCount 则意味着改实例被修改过
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在上述迭代器中,注意到:如果在并发环境下,不能保证迭代器的快速失败行为(不能保证 modCount 和 expectedModCount 的一致性)。
再来看一个 ListIterator 迭代器(通过 listIterator() 获得):
public ListIterator<E> listIterator() {
return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 内部类 ArrayList.ListItr 是 AbstractList.ListItr 的优化版本
*/
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@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];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} 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();
}
}
}
相比于通用的迭代器,ListIterator 实现了双向移动、set 方法替换访问过的最近一个元素等操作。
最后,来看一个 Java 8 新增的 Spliterator 迭代器(通过 spliterator() 获得):
@Override
public Spliterator<E> spliterator() {
return new ArrayListSpliterator<>(this, 0, -1, 0);
}
static final class ArrayListSpliterator<E> implements Spliterator<E> {
private final ArrayList<E> list;
private int index; // current index, modified on advance/split
private int fence; // -1 until used; then one past last index
private int expectedModCount; // initialized when fence set
/** Create new spliterator covering the given range */
ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
int expectedModCount) {
this.list = list; // OK if null unless traversed
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList<E> lst;
if ((hi = fence) < 0) {
if ((lst = list) == null)
hi = fence = 0;
else {
expectedModCount = lst.modCount;
hi = fence = lst.size;
}
}
return hi;
}
public ArrayListSpliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null : // divide range in half unless too small
new ArrayListSpliterator<E>(list, lo, index = mid,
expectedModCount);
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
if (i < hi) {
index = i + 1;
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
action.accept(e);
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null && (a = lst.elementData) != null) {
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
public long estimateSize() {
return (long) (getFence() - index);
}
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
ArrayList 中的序列化与反序列化
/**
* 序列化方法
* 将 ArrayLisy 实例的状态保存到一个流里面
*/
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);
// 按照顺序写入所有的元素
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 反序列化方法
* 根据一个流(参数)重新生成一个ArrayList
*/
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();
if (size > 0) {
// be like clone(), allocate array based upon size not 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();
}
}
}
可以看到,对于 elementData 为什么使用 transient 修饰,是因为 JDK 不想将整个 elementData 都序列化或者反序列化,而只是将 size 和实际存储的元素序列化或反序列化,从而节省空间和时间。
ArrayList 中的视图
ArrayList 对 List 的视图提供了支持,可通过 subList 方法获取,在视图上的操作将会影响其实例本身。解析如下:
public List<E> subList(int fromIndex, int toIndex) {
/* 范围校验 */
subListRangeCheck(fromIndex, toIndex, size);
/* 通过当前 this 实例创建一个 SubList 实例,也因此,对视图的修改通过 this 这个关系影响了原数据 */
return new SubList(this, 0, fromIndex, toIndex);
}
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
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;
}
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;
}
/* 省略了一些方法 */
}
- SubList 的一些方法,是基于 elementData[] 的 index + 偏移位置 进行修改,是会直接修改 ArrayList 中 elementData 数组的,使用中应该注意;
- SubList 没有实现
Serializable接口,是不能序列化的。
ArrayList 中的克隆
ArrayList 的 clone 操作是浅拷贝(元素本身不会被复制),实现如下:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
小结
- ArrayList 底层的数据结构是 Onject 数组
- ArrayList 可以自动扩容,不传初始容量或者初始容量是
0,都会初始化一个 EMPTY_ELEMENTDATA 空数组,但是如果添加元素,会自动进行扩容,所以,创建 ArrayList 的时候,给初始容量是必要的 Arrays.asList()方法返回的是的Arrays内部的 ArrayList,并且这个 list 是不可修改的,用的时候需要注意subList()返回内部类,不能序列化,和 ArrayList 共用同一个数组- ArrayList 提供了多种迭代器,在使用迭代器遍历时,删除时要用迭代器支持的操作,迭代器的
remove方法,或者可以用倒序的for循环 - ArrayList 使用 transient 修饰 elementData[],重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间
elementData不使用private修饰的原因是:non-private 可以简化内部类的访问- 对 ArrayList 的管理中,因底层是 Object 数组,使用了 Arrays 的 copyOf、sort 和 System 的 arraycopy 等工具类进行操作。
- 问:ArrayList 是线程不安全的,那又是如何解决 Spliterator 中多线程安全问题呢?
相关面经
通过这些练习更好的帮助吸收关于 ArrayList 的一些常见的、易混淆的知识点。
ArrayList 的总览
-
简单介绍下 ArrayList。
- ArrayList 是可调整大小的数组实现;允许存放重复元素,元素可以为 null;实现不是同步的;提供了多种 fail-fst 迭代器;引入了泛型机制。
- 与数组相比,不能存放基本类型的数据,有容量管理机制对数组大小进行扩容/回收。
- 与 LinkedList 相比,查找和访问元素的速度较快,但新增,删除的速度较慢。
ArrayList就是动态数组,是Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,灵活的设置数组的大小等好处。
-
为什么 ArrayList 线程不安全还使用他呢?
因为我们正常使用的场景中,都是用来查询,不会涉及太频繁的增删,如果涉及频繁的增删,可以使用LinkedList,如果你需要线程安全就使用Vector,这就是三者的区别了,实际开发过程中还是ArrayList使用最多的。
不存在一个集合工具是查询效率又高,增删效率也高的,还线程安全的,至于为啥大家看代码就知道了,因为数据结构的特性就是优劣共存的,牺牲了性能,那就安全,牺牲了安全那就快速。
-
ArrayList 是线程安全的么?
当然不是,线程安全版本的数组容器是 Vector,Vector 的实现很简单,就是把所有的方法统统加上 synchronized 就完事了。
也可以不使用 Vector,用 Collections.synchronizedList 把一个普通 ArrayList 包装成一个线程安全版本的数组容器也可以,原理同 Vector 是一样的,就是给所有的方法套上一层 synchronized。
-
ArrayList 用来做队列合适么?
ArrayList极不适合做队列。 队列一般是FIFO(先入先出)的,如果用ArrayList做队列,就需要在数组尾部追加数据,数组头部删除数组,反过来也可以。但是无论如何总会有一个操作会涉及到数组的数据搬迁,这个是比较耗费性能的。
ArrayList 是很适合做堆栈的。
-
那数组适合用来做队列么?
数组是非常合适的。 比如ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。
另外著名的 Disruptor 开源 Library 也是用环形数组来实现的超高性能队列,具体原理不做解释,比较复杂。
简单点说就是使用两个偏移量来标记数组的读位置和写位置,如果超过长度就折回到数组开头,前提是它们是定长数组。
-
ArrayList的遍历和LinkedList遍历性能比较如何?
论遍历 ArrayList 要比 LinkedList 快得多,ArrayList 遍历最大的优势在于内存的连续性,CPU 的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
ArrayList 结构相关
-
ArrayList 都有哪些字段,有什么作用?
elementData、size、继承基类的 modCount;
和一些辅助字段:EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA、DEFAULT_CAPACITY、MAX_ARRAY_SIZE。
-
Object[] EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与 elementData 在哪里用到?
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA,仅在 使用 ArrayList() 时使用;
-
当 ArrayList(int) 入参为 0 或者 ArrayList(Collection<? extends E>) 集合容量为空时,指定 EMPTY_ELEMENTDATA;
-
而 elementData 则是实际存储元素的数组。
-
-
ArrayList 的最大长度为多少?为什么 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ?
这是有些虚拟机在数组中保留了一些标题字,尝试分配更大的数组可能导致
OutOfMemoryError:Requested array size exceeds VM limit -
对于 elementData 为什么不是 private 的,为什么使用了 transient?
-
对于 non-private 的 elementData:我们知道的,使用
private修饰的变量,内部类也是可以访问到的。难道注释中non-private to simplify nested class access(非私有,以简化嵌套类访问)的这句话有毛病?当我们看表面看不到什么东西的时候,不妨看一下底层。
虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,
private修饰的确会影响内部类的访问。 -
对于使用 transient 进行修饰 elementData:是因为 JDK 不想将整个 elementData 都序列化或者反序列化,而只是将 size 和实际存储的元素序列化或反序列化,从而节省空间和时间。
-
容量管理机制
-
ArrayList 的底层是 Object[],但数组的大小是固定的,如果不断的往里面添加数据的话,不会有问题吗?是如何进行扩容的?
ArrayList 通过构造器初始化的时候指定底层数组的大小,在添加元素时进行容量的扩充。
-
为什么 ArrayList 的默认数组大小为什么是 10?
据说是因为 sun 的程序员对一系列广泛使用的程序代码进行了调研,结果就是 10 这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8 个 12 个都没什么区别,只是因为 10 这个数组比较的圆满而已。
-
问个真实的场景,ArrayList(int initialCapacity)会不会初始化数组大小?
不会初始化数组大小!而且这里有一个经典的BUG:
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(10); System.out.println(list.size()); // 0 list.set(1, 10); // 抛出:IndexOutOfBoundsException }这是因为使用 ArrayList(int initialCapacity) 的时候进行了容量分配(new Object[]),但是其 size 依旧为 0,在 set 时,根据 size 进行判断(此时 size == 0),下标越界,抛出索引越界异常。
总结就是说,初始化的时候通过 capacity 申请数组,而不改变 size 的大小。
常用方法
-
ArrayList 的增删很慢,你能说一下 ArrayList 在增删的时候是怎么做的么?主要说一下他为啥慢。
- 每次操作之前都会进行扩容检测,如果进行扩容,那速度将会更慢;
- 对于新增位置之后的元素都要进行复制。
-
ArrayList插入删除一定慢么?
这个取决于删除的元素离数组末端有多远,ArrayList拿来作为堆栈来用还是挺合适的,push 和 pop 操作完全不涉及数据移动操作。
经典 BUG
-
使用 Arrays.asList() 获取 List,然后 add 操作报错。
这是因为 Arrays.asList 返回的是内部类 ArrayList,而这个内部类的数组类型为
private final E[] a;所以在使用 add 操作时报错。public static void main(String[] args) { List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); System.out.println(list.getClass()); // class java.util.ArrayList System.out.println(list.toArray().getClass()); // class [Ljava.lang.Object; // 注意 toArray() 返回的类型 List<String> asList = Arrays.asList("1", "2"); System.out.println(asList.getClass()); // class java.util.Arrays$ArrayList 是 Arrays 的内部类 ArrayList System.out.println(asList.toArray().getClass()); //class [Ljava.lang.String; // asList.add("4"); // 异常:UnsupportedOperationException ArrayList<String> list1 = new ArrayList<>(asList); System.out.println(list1.getClass()); // class java.util.ArrayList System.out.println(list1.toArray().getClass()); // class [Ljava.lang.Object; list1.add("3"); // is OK } /* 通过这个例子可以看出来,`java.util.ArrayList.toArray()`方法会返回`Object[]`没有问题。 而`java.util.Arrays`的私有内部类ArrayList的`toArray()`方法可能不返回`Object[]`。 通过 Arrays.asList() 源码不难看出,`java.util.Arrays`的内部ArrayList的`toArray()`方法,是构造方法接收什么类型的数组,就返回什么类型的数组。 */ -
使用 new ArrayList(10) 创建 list,然后 set 操作,报错。
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(10); System.out.println(list.size()); // 0 list.set(1, 10); // 抛出:IndexOutOfBoundsException }这是因为使用 ArrayList(int initialCapacity) 的时候进行了容量分配(new Object[]),但是其 size 依旧为 0,在 set 时,根据 size 进行判断(此时 size == 0),下标越界,抛出索引越界异常。
-
使用迭代器时抛出的 ConcurrentModificationException 异常。
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6)); Iterator<Integer> itr = list.iterator(); while (itr.hasNext()) { Integer i = itr.next(); list.remove(i); /* 抛出 ConcurrentModificationException */ } }在迭代的时候,会校验
modCount是否等于expectedModCount。不管单线程还是多线程,只要不等于就会抛出著名的ConcurrentModificationException异常。