本文以Java8为例
从ArrayList实现的接口说起
查看ArrayList源码会发现ArrayList继承了AbstractList类,实现了List, RandomAccess, Cloneable, java.io.Serializable接口。
AbstractList
AbstractList是抽象类,里面有众多List基本方法的基本实现。事实上JDK中有众多类似AbstractXXX的类,他的思想是就是把众多公共的实现提取出来,AbstractXXX面向的是工具类的开发者,我们可以基于AbstractList来实现自己的List。而ArrayList只是JDK已经帮我们实现好的,面向工具类的使用者。
List
List结构顶级接口,
RandomAccess
RandomAccess即随机访问。
先给出结论:集合中元素的访问分为随机访问和顺序访问。 随机访问类似于数组下标的访问,顺序访问类似于链表的访问,随机访问直接通过下标取值性能较好,顺序访问以迭代器遍历性能比较好。 查看RandomAccess接口,仅仅是一个空接口而已。
查看调用RandomAccess接口的类,发现除了实现RandomAccess,其他都是用于instaceof判断。
原来,RandomAccess的意义仅仅是一个标记而已,即标记接口,用于判断List是否为RandomAccess的实现。以Collections的binarySearch方法为例:
二分查找先判断list是否是RandomAccess的子类或大小是否小于阈值5000,如果为真则会执行indexedBinarySearch
会发现indexedBinarySearch是以数组下标的方式获取值。如果为假会执行iteratorBinarySearch
iteratorBinarySearch是以迭代器的方式获取值。即JDK以性能方面的考虑,如果list支持随机访问或size比较小,则以下标索引的方式获取内容,否则以迭代器的遍历获取内容。
所以RandomAccess仅仅是一个类的标记。
Cloneable
Cloneable是否可克隆,查看Cloneable接口
查看调用Cloneable接口的类,都是实现Cloneable接口。与RandomAccess类似,Cloneable接口也是标记接口,用于判断该类的对象是否可克隆。
这里会有一个疑问:什么是可克隆?
先给出结论:clone方法是基类Object的方法,只有实现了Cloneable接口的类才可以使用clone方法,否则会抛CloneNotSupportedException异常。下面是某版本JDK的clone方法的源码,会发现如果不是Cloneable的子类则会抛异常。
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
/*
* Native helper method for cloning.
*/
private native Object internalClone();
然而查看JDK1.8的clone方法,仅仅是一个本地方法而已,他又是怎么做到抛异常的呢?
protected native Object clone() throws CloneNotSupportedException;
原来在JDK1.8中,如果我们查看JVM的C++源码,会发现“对象是否实现了cloneable接口抛异常”的动作实现在了JVM层中。
所以ArrayList通过实现了cloneable接口,支持clone方法。
另外,有说法认为Java的类通过实现Cloneable来标识出对象是否可以使用clone是一种很糟糕的设计,未实现Cloneable确对外暴露clone方法的规则很奇怪。
java.io.Serializable
同样也是标记接口,表示该类的对象是否可以序列化。
成员属性
// 若初始化时未指定容量大小,默认为10
private static final int DEFAULT_CAPACITY = 10;
// 一个空数组而已
private static final Object[] EMPTY_ELEMENTDATA = {};
// 也是一个空数组,只不过与EMPTY_ELEMENTDATA做区分。
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示初始化是无参构造
// 而EMPTY_ELEMENTDATA是有参构造,只不过参数是0
// 之所以用来区分是因为无参构造是默认容量为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储对象的容器
transient Object[] elementData;
// 存储对象的多少
private int size;
主要方法
构造方法
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)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
三个构造方法参数分别是容量、空、集合。通过观察代码会发现容量为0时, this.elementData有两种情况
构造函数传入initialCapacity时this.elementData = EMPTY_ELEMENTDATA;
无参构造函数时this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
关于这两个属性,源码给的解释是
/**
* Shared empty array instance used for empty instances.
* 用于空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 共享空数组实例,用于默认大小的空实例。
* 我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时应该膨胀多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA其实就是实例化ArrayList方式的区分。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA在后面add方法中会详细解释,其实这里可以理解为DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示实例化时是无参构造,未指定容量,在调用add方法时这种情况会默认此刻容量为10。
而EMPTY_ELEMENTDATA表示在我们实例化对象时指定了容量就是0。
add方法
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++;
}
add方法的核心ensureCapacityInternal,内部确保容量。
内部确保容量即add元素时,程序里通过当前的size自动判断当前是否需要扩容。与其相对应的是显示确保容量。ensureCapacityInternal源码分析如下
// minCapacity表示容器要确保支持的容量
// elementData的长度至少要大于等于minCapacity,是需求的最低大小
// 比如add时 minCapacity就是size+1
// ensureCapacity调用ensureCapacity显示扩容时,minCapacity就是传入的参数
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 该方法仅仅是多做了一步判断,如果是以new ArrayList()无参构造函数实例化的对象,且是第一次add元素,则默认的最小容量为10。
// 如果没有默认容量,因为容器最初容量小,add元素时1.5倍的扩容方式会造成频繁的扩容。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 显示确保容量,在ensureCapacityInternal中调用只是方法的公用
// “显示”的含义主要体现在ensureCapacity方法的调用,ensureCapacity是人为的显示扩大容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 实际扩容,默认按1.5被扩容,如果1.5倍还是小于minCapacity,则以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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
如果未指定容量,add方法以10为初始值,每次扩容以1.5倍扩容。所以如果有大量数据add,也会触发多次扩容。而每次扩容实际上是将数组内容复制到新的数组里。
trimToSize
精简化容器。ArrayList在实际使用中,elementData的长度大于等于size,而trimToSize会将elementData按照size重新复制一份给elementData,最小化ArrayList实例的存储。如果实例内容不变,可以调用该方法节约内存。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ensureCapacity
相当于扩容,也是我们前面提到的显示确保容量。
给定一个minCapacity,保证ArrayList可以容纳至少minCapacity数量的对象。
public void ensureCapacity(int minCapacity) {
// 以此来判断我们实例化时是否为 new ArrayList()
// 如果是,他的默认长度在add时就是10,不需要扩容
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
clone
前面已经做过分析,通过实现Cloneable接口,使得clone方法可用。重写clone方法,复制出一份数组,但其本身还是浅拷贝,数组里引用的对象没有被复制。
modCount表示改动次数,新克隆的对象置为0。
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);
}
}
remove
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
}
remove其实就是将index后面的元素向前移动一位。所以若ArrayList很大,不适合使用remove方法。
与Vector对比
Vector就是线程安全版的ArrayList,它的实现与ArrayList基本类似,这里说下比较明显的3点区别
Vector线程安全
Vector针对容器的曾删改查操作都加上了synchronized关键字来保证线程安全,譬如:
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
构造方法略有不同
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
相比ArrayList,Vector多了一个构造方法,多传入一个参数capacityIncrement,可以用来指定每次扩容的增量。
另外new Vector()默认容量也是10,只不过在实例化时数组的长度就已经是10了。而ArrayList中是add第一个元素之后数组的长度才会变成10。
public Vector() {
this(10);// 默认容量为10
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
扩容规则略有不同
Vector的扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 如果有capacityIncrement,按照capacityIncrement扩容,否则按照当前容量的一倍扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector的扩容默认是按一倍扩容,如果指定capacityIncrement,则按照capacityIncrement扩容。
Vector无论是读还是写都需要加锁,对于读多写少的场景,可以使用CopyOnWriteArrayList。
与LinkedList对比
LinkedList就是个双向链表结构,每个节点维护其前置节点和后驱节点。
ArrayList是线性结构支持RandomAccess,LinkedList是链式结构。
总结
实现RadomAccess随机访问,使得ArrayList在一些方法中是以数组下标取值。
实现CloneAble支持克隆,ArrayList浅拷贝。
默认容量为10,以1.5倍递增的方式扩容。
数据量大时并不适合remove。
Vector是线程安全的List,但无论是读还是写都需要加锁,对于读多写少的场景,可以使用CopyOnWriteArrayList。