ArrayList是开发中最常使用到的集合,要想深入了解绕不过源码,本篇文章简单讲解其源码,领略ArrayList的风采...
ps: 本文以openjdk8为主.
概述
- ArrayList是一种可以动态操作的集合类,基于数组实现.
- ArrayList允许空值和重复元素,该类封装了一个动态再分配的Obj数组.
- 当往ArrayList里添加元素数量大于其底层数组容量时,会通过扩容机制重新生成一个更大的数组.
- ArrayList是非线程安全的,多线程操作下需要手动保证该集合类的同步性
源码分析
继承结构
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable {}
ArrayList实现了List
接口,继承了AbstractList
类.其中RandomAccess
代表了它具备快速随机访问的能力.由于它是基于数组实现,天生带下标,可以实现O(1)复杂度的随机访问.
为什么要让AbstractList先实现List
ArrayList继承AbstractList是一种思想, 现在AbstractList里实现一些通用的方法,而具体的实现类ArrayList继承AbstractList,拿到通用的方法,再实现一些自己特定的方法.这样就算后面有多个List实现,都可以通过继承AbstractList来获取通用方法,减少代码的重复.
既然ArrayList已经继承了AbstractList,而AbstractList已经实现了List
有道翻译:
这是记住此类真正实现该接口的一种方法。
它不会有任何不良影响,并且可以帮助您理解代码,而无需遍历给定类的完整层次结构。
另一种可能:
public static void main(String[] args) {
System.out.println("Test1: " + Arrays.toString(Test1.class.getInterfaces()));
System.out.println("Test2: " + Arrays.toString(Test2.class.getInterfaces()));
}
interface TestInterface { void test(); }
abstract class AbstractTestInterface implements TestInterface {}
class Test1 extends AbstractTestInterface {
@Override
public void test() { System.out.println("test1"); }
}
class Test2 extends AbstractTestInterface implements TestInterface {
@Override
public void test() {System.out.println("test2"); }
}
输出结果:
从结果就可以发现,因为Test1本身没有再次实现接口,因此在获取Test1的接口时为空数组.
所以在实现代理时就会报错,如下:
public static void main(String[] args) {
new Test().testProxy();
}
private void testProxy() {
try {
TestInterface proxy1 = createProxy(new Test1());
proxy1.test();
} catch (Exception e) {
e.printStackTrace();
}
TestInterface proxy2 = createProxy(new Test2());
proxy2.test();
}
private static T createProxy(final T obj) {
return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), (proxy, method, args) -> method.invoke(obj, args));
}
在生成动态代理时调用class.getInterfaces(), 不实现接口调用函数会报错.
可能作者是为了更方便实现代理而精心设计的.
ps: Set和Map集合同理...
类中属性
属性比较少,直接看源码
/** 版本号,用于校验正反序列化时的一致性 */
private static final long serialVersionUID = 8683452581122892189L;
/** 默认容量 */
private static final int DEFAULT_CAPACITY = 10;
/** 空对象数组 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/** 默认空对象数组 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** ArrayList的核心,数据都是存在这的,注意这个数组自定义了序列化函数 */
transient Object[] elementData;
/** 实际元素数量 */
private int size;
/** 数组的最大容量
* 尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/** AbstractList里的属性, 记录集合 涉及到结构变化的次数(add/remove/clear等) */
protected transient int modCount = 0;
构造函数
ArrayList有三个构造函数:
- 无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
将elementData赋值为默认空数组, 实际上jdk8里的ArrayList进行了延迟初始化==(具体初始化在后面解释)==,而jdk8之前的版本是直接进行初始化
// by openjdk7
public ArrayList() {
// 调用有参构造器进行初始化默认容量
this(10);
}
- 有参构造器之一
没什么逻辑,直接上源码
public ArrayList(int initialCapacity) {
// 判断下这个初始容量是否大于0
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(Collection extends E> c) {
// c.toArray可能返回的不是Object对象 see 6260652
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
这里涉及到JDK bug库6260652
编号,附上官网链接,看下述例子:
class A {}
class B extends A {}
public void Test() {
B[] b = {new B(), new B()};
// 子类数组转换成父类数组是允许,向上转型
A[] a = b;
System.out.println(a[0]);
// 由于存储的都是子类型B,java是不允许向下转型的,因此这里会报错ArrayStoreException
a[0] = new A();
}
这里说明假如我们有个Obj数组,并不代表着我们可以把Obj对象存进去,这取决于数组中存储元素的实际类型.
回到源码c.toArray()
可能会发生类似于上述例子的A[] a = b
,为了防止这种情况,源码中进行了if
判断来防止错误的数组对象类型导致异常,Arrays.copyOf(elementData, size, Object[].class)
重新创建了个Obj数组,可以任意存储对象.
核心函数
添加函数
// 末尾添加一个元素
public boolean add(E e);
// 指定下标位置添加一个元素
public void add(int index, E element);
boolean add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// size++ 真实数据量+1
elementData[size++] = e;
return true;
}
ensureCapacityInternal
是检查扩容函数,size + 1
添加前先检查下数组中是否能放下.
/**
* 检测扩容时调用的中间方法
*
* @param minCapacity
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
- 先确认最小容量:
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
即判断初始化的elementData是不是空数组,然后找出默认容量和参数容量大的.对应无参构造器
延迟初始化 - 调用
ensureExplicitCapacity
函数,此函数才是判断容量是否够用的函数,如果不够用才会调用grow
来进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在ensureExplicitCapacity
里只有检测到真实容量不够的时候才会扩容, 而grow
才是真实扩容的函数,而modCount
属性作用比较泛,例如fast fail检查
,后续会讲到.
private void grow(int minCapacity) {
// 复制扩容前的容量
int oldCapacity = elementData.length;
// 生成新的大小 每次扩容为原有大小的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 这里适用于elementData为空数组的时候,即数组初始化时
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 当newCapacity超过最大限制,调用hugeCapacity把最大值赋予当newCapacity超过最大限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 开始进行 扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 超过最大限,实际上还是把int最大值给了,再大就溢出了,但给予MAX_VALUE,vm就超过限制了..
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
void add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index); // check index
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
在指定位置插入时,第一步先调用rangeCheckForAdd
来进行越界检查,触发扩容机制ensureCapacityInternal
.
再调用System.arraycopy
把index
后所有元素往后挪一位.==ArrayList随机插入的效率慢的原因之一==
注意: 当调用无参构造器进行构建ArrayList时,容量会在第一次调用add函数时进行初始化.
获取函数
较为简单,直接上源码
public E get(int index) {
// 检测是否下标越界
rangeCheck(index);
// 获取元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
// 内部使用时以Obj, 获取时进行向下转型 Object -> element
return (E) elementData[index];
}
修改函数
较为更加简单
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
删除函数
// 根据下标移除元素
E remove(int index);
// 删除列表指定的元素
boolean remove(Object o);
// 清空list
void clear();
E remove(int index)
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);
// 同时清除最后一个多余的,方便gc
elementData[--size] = null; // clear to let GC do its work
// 返回旧值
return oldValue;
}
remove(int index)
整体逻辑就是移动元素,把index下面的往前移动一位,再清理最后一位多余的,把被删除的元素返回.
boolean remove(Object o)
public boolean remove(Object o) {
if (o == null) {
// null值的删除依赖 ==
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 非null值的删除依赖 equals
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);
// 删除最后一个多余的,方便gc
elementData[--size] = null; // clear to let GC do its work
}
remove(Object o)
逻辑和remove(int index)
差不多,相对多了个步骤寻找下标,if (elementData[index] == null)
这里可以看出,ArrayList是支持存入null值的.
void clear();
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
把所有元素置为null
方便gc,再重置实际元素数量.
Iterator迭代器
private class Itr implements Iterator {
int cursor; // 要返回的下一个元素的下标
int lastRet = -1; // 上一次返回的元素 (删除的标志位)
int expectedModCount = modCount; //用于判断集合是否修改过结构的标志
boolean hasNext();
E next();
void remove();
void forEachRemaining(Consumer super E> consumer);
}
我们都知道Java中的foreach是个语法糖,编译成字节码后会被转成用迭代器遍历的方式.
来看看Itr
,它是ArrayList内部维护的迭代器.
for (String s : list) {
System.out.println(s);
}
// 等价于
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
System.out.println(s);
}
来看看hasNext
和next
函数
public boolean hasNext() {
// 判断是否到结尾
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) // 判断是否越界
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) // 再次判断越界,防止异步线程修改List
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i]; // 返回元素,并设置上一次返回的元素的下标
}
final void checkForComodification() {
// fast-fail检测, 如若在迭代的同时进行
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
next
首先会触发checkForComodification
,来检测在遍历的过程中是否其他线程修改了list并抛出异常,这就是fast-fail
==快速失败策略==
通过判断modCount和expectedModCount是否一样来判断是否有进行修改.
因此在foreach
时不能对元素进行add/remove
操作,remove
元素请使用Iterator
方式.并发操作,需要对Iterator
对象加锁.
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); // 删除 modCount会发生变化
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 需同步变化
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
快速失败机制
ArrayList 迭代器中的方法都是均具有快速失败的特性,当遇到并发修改的情况时,迭代器会快速失败,以避免程序在将来不确定的时间里出现不确定的行为。
总结
- ArrayList本质上就是一个
Object[]
数组 - ArrayList允许插入null,每次扩容1.5倍
- ArrayList是通过
System.arrayCopy
和Arrays.copyOf
来完成大部分操作的 - ArrayList使用了fast-fail机制,iterator迭代时,如若add/remove时,每次迭代都会检测modCount是否有改变抛异常
- ArrayList由于本质上是数组,所以它在数据查询方便会很快,在随机插入/删除性能会下降很多,因为每次操作都要移动很多元素
- ArrayList是非线程安全的,很多操作非原子性的,例如
elementData[size++] = e