- 在逻辑结构上是:线性结构,线性表
- 在物理结构是:连续储存结构
实现的接口
ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
-
RandomAccess
RandomAccess
,没有方法,是一个标记的接口。List 实现这个接口用来表示其支持随机访问:即下标访问,(通常是固定时间)RandomAccess
这个标记接口就是标记能够随机访问元素的集合, 简单来说就 是底层是数组实现的集合。for (int i=0, n=list.size(); i < n; i++)
循环,比for (Iterator i=list.iterator(); i.hasNext(); )i.next();
要快。(快速随机访问速度 > 迭代器遍历速度。) -
Cloneable
空接口,实现了这个接口,在使用Object.clone()
方法进行每个字段的拷贝是合法的,没有实现这个方法会抛出CloneNotSupportedException
。接口不包含clone
方法,及时这个实现了这个接口,也不能进行 clone,即使是使用反射。实现此接口的类应该使用公共方法覆盖 clone 方法.引申思考 深克隆和浅克隆的区别?
ArrayList 属性
// 默认长度
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于默认大小空实例的共享空数组实例,与 EMPTY_ELEMENTDATA 的区别在于知道当第一个元素插入时该如何扩充数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
// 保存了多少个数据
private int size;
// 最大的长度,2^31 - 8 减去头关键字
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法 3 个
// 有初始容量
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) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
// 调用 Array.copyOf
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
扩容机制
在初始化 ArrayList
的时候,JVM 并不知道要存多少个数据,但是数组必须要声明其大小。这样的化,最迟在第一个元素插入时,必须指定数组容量大小、初始化数组以容纳元素。
查看添加方法
// 直接添加元素
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++;
}
首先看到的是带三个感叹号的注释 // Increments modCount!!
,这个 modCount
是干什么呢的?
-
点进去发现是继承自
AbstractList
protected transient int modCount = 0;
这个属性就是:已对该列表进行结构化的修改次数,否则的话会干扰正在迭代的,产生错误的结果。如果这个 field 变换不正常的话,就会在听下一个迭代(next,remove,previous,set,add 操作的时候)产生在并发里面很常见的并发修改异常(这就是根源。Man,I Learned so much.)
ConcurrentModificationException
。面对迭代过程中的并发修改,这提供了 快速故障 fast-fail 行为,而不是不确定的行为。
如果子类希望提供快速失败的迭代器(和列表迭代器),则只需在其 add(int,E)和 remove(int)方法(以及任何其他覆盖该方法导致结构化的方法)中递增此字段即可。 在添加或者移出的时候,这个加不能超过一个。否则会抛出虚假的
ConcurrentModificationException
。
接着看
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是第一次添加数据,是无参构造第一次添加数据
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取到 10 和 size+1
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 避免并发修改异常
modCount++;
if (minCapacity - elementData.length> 0)
// 进行扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 原来 * 1.5 使用位运算
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);
}
// 可以扩容的最大容量,当想要扩容的容量大于 MAX_ARRAY_SIZE 时调用
private static int hugeCapacity(int minCapacity) {
// 是否扩容的时候变成了负数
if (minCapacity < 0) // overflow 内存不足
throw new OutOfMemoryError();
// 返回
return (minCapacity> MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结:
- 有 index 添加会进行 index range check, 没有 index 就进行保证容量的的方法
- 先计算容量判断是否是默认容量 10,就返回
Math.max(DEFAULT_CAPACITY, index);
或者 index - 拿到上面的值 a, 先加
modCount++
,如果a大于存储元素的长度的话就进行grow
操作 - 加入需要
grow
,传入最大值- 拿到原来数组的长度
* 1.5
,用位运算 - 如过
*1.5
后,还小于 a,就返回 a - 如果大于
2^31-8
,就进行hugeCapacity
操作(判断是否溢出,就抛出来内存不足异常,否则就返回,MAX_ARRAY_SIZE
),否则就调用Arrays.copyOf
方法进行拷贝。
- 拿到原来数组的长度
什么时候扩容?
- 主动扩容
- 添加时候扩容
从
public void ensureCapacity(int minCapacity)
可以看出,我们可以主动调用该方法进行扩容。
此外,在调用 add/addAll
等添加元素的方法时,ArrayList
也会调用内部扩容方法(ensureCapacityInternal)来主动扩容
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);
}
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length> 0)
grow(minCapacity);
}
数组底层的方法
扩容的时候其实调用了 Arrays.copyOf
方法,最后也是调用了 System.arraycopy
// Arrays.copyOf
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public native Class<?> getComponentType();
// System中的 copy :native 方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
其他几个 public 方法
clear()
fori
将每个元素设置为 null,让 gc 做工作。clone()
调用父类的 clone 方法,生成新ArrayList,然后把数据 copy 过去(Arrays.copy),modCount=0
。toArray()
Arrays.copy 返回新拷贝的数据。
总结
-
基于数组实现
-
可以动态扩容,每次
*1.5
倍的长度增加 -
元素可以为 null
-
不是并发安全的,是通过
modCount
来进行判断的 -
如果想并发安全:
Vector
不行,效率低- 使用
Collections.synchronizedList
() - JUC 中的并发容器
new CopyOnWriteArrayList<>()
;
-
为什么数组中的最大元素是
Integer.MAX_VALUE - 8
- stackoverflow
数组对象(例如int值的数组)的形状和结构类似于标准Java对象的形状和结构。主要区别在于,数组对象还有一条额外的元数据,它表示数组的大小。然后,数组对象的元数据包括:Class:指向类信息的指针,该信息描述对象的类型。对于int字段数组,这是指向int []类的指针。 标志:描述对象状态的标志的集合,包括对象的哈希码(如果有的话)以及对象的形状(即,对象是否为数组)。 Lock:对象的同步信息,即对象当前是否同步。 Size:数组的大小。
- From Java code to Java heap
- stackoverflow