1、揭开ArrayList真面目
作者将在本文详细赘述日常开发中最常用集合类-ArrayList,本次JCF源码分析基于JDK1.7,主要从以下几个方向分析:
- UML类图关系
- 数据结构
- 接口介绍
- 常用、重要方法的实现
1.1 UML类图关系
(
UML类图
)
从UML关系类图,我们可以直观的看出ArrayList的类结构,图中虚线表示实现(implements
)关系,实线表示继承(extends
)关系,我们不必在还不熟悉的情况下对这种关系进行“死记硬背”,因为在理解整个集合框架之后自然不用看都会比较清晰,辣么下面我们将对接口或类一一介绍:
1.2 数据结构
1.2.1 JAVA常用数据结构
在介绍Collection具体的实现类或者某一种抽象之前,笔者需要对JAVA最常用的数据结构进行简单赘述
- 线性结构:线性结构的元素在物理内存上有序/连续的分布,使其可以使用下标(
Index
)快速访问,查找复杂度为O(1),如:数组 - 链表结构:链表结构的元素在物理内存上无序/随机的分布,当前元素保留上一个或下一个元素的引用叫单向链表,保留上一个且下一个元素的引用叫双向链表 ,链表中第一个元素的上一个索引位置指向最后一个元素且最后一个元素的下一个索引位置指向第一个元素叫循环链表(
简单理解成一根绳子头尾相连成圆
),查询复杂度为O(logn),如:LinkedList、LinkedHashMap - 散列结构:
以上是笔者觉得最常见的一些数据结构,笔者会在后续介绍Collection不同抽象的时候讲解集合每一种API对应的数据结构是哪一种,理解这些数据结构的结构及优缺点有助于读者在根据具体的场景下选用是和数据结构的API提高性能,当然JDK里面还有一些跳表、树、图的数据结构实现,因为文章是介绍常用的集合类,所以就不一一介绍了,如果读者有兴趣可以自行学习
1.3 接口介绍
1.3.1 接口的语义
在这之前笔者希望读者对接口的理解可以上升到一个逻辑层次,而不是仅仅知道实现了接口就必须实现接口的方法,比如Iterable接口是可迭代的语义,那么子类就必须是具备可迭代需求才能实现该接口,否则就属于一种错误的设计,再看看JDK中没有任何方法的Serializable
、以及上图中出现的RandomAccess
这些接口的出现本身就是约束着一种逻辑定义,笔者在这里称之为接口的语义
1.3.2 Iterable接口
集合顶层接口,接口的语义标志实现类是可迭代的(循环
),所有需要被for(String str : strs)
语法迭代的支持都必须实现此接口(数组除外
),接口方法就一个Iterator<T> iterator();
此方法返回一个迭代器(Iterator接口
)的实现类。
1.3.3 Collection接口
几乎是JAVA所有数据结构集合的接口(二元散列数据结构除外,如:Map
),接口的语义标志实现类必须为集合,集合的解释也就是:“一堆东西”。集合里的“东西”,叫作元素(element
),既然是一堆东西而我们目的是要对这堆东西进行操作,自然Collection的接口就需要约束每个集合都必须有以下方法:
-
size()
: 集合中元素的个数 -
contains(Object element)
: 集合中是否包含某个元素 -
iterator()
: 集合的迭代器 -
add(Object element)
:往集合里添加元素 -
remove(Object elemnt)
:从集合里面移除元素
理论上一个简单的集合接口有以上方法就可以正常工作,但是为了操作方便JCF作者Josh Bloch增加了以下几个方法的约束:
-
isEmpty()
:集合是否为空 -
toArray()
:转换成数组 -
containsAll(Collection collection)
:集合是否包含collection变量中的所有元素 -
addAll(Collection collection)
:添加collection中所有元素到当前集合 -
removeAll(Collection collection)
:移除所有在collection中的元素 -
retainAll(Collection collection)
:取两个集合的交集 -
clear()
:清除集合所有元素
有了以上方法的约束可以让我们在使用集合的时候操作更方便
1.3.4 List接口
介绍了常用数据结构,接下来我们来看Collection的第一种集合抽象——有序List列表,List有以上两种数据结构的实现: 线性结构(ArrayList
)、链表结构(LinkedList
),本文我们将赘述线性结构的实现(ArrayList
),List接口在extends了Collection之后,增加了对有序列表操作的几个方法约束:
-
get(int i)
:获取i位置的元素 -
set(int i,Object element)
:替换集合中的i位置插入element元素,返回原位置的元素 -
add(int i,Object element)
:集合i位置插入element,i位置以后的元素向后移一位 -
remove(int i)
:移除集合中i位置的元素 -
indexOf(Object element)
:查找element并返回元素在集合的下标索引,如果没有返回-1 -
lastIndexOf(Object element)
:查找element元素最后一次在集合中的下标索引,如果没有返回-1 -
listIterator()
:创建一个集合的迭代器 -
listIterator(int i)
:从i位置创建一个List集合的迭代器 -
subList(int star,int end)
:返回当前集合的一个子集,从当前集合的start位置到end位置,注意此子集并不是新new出来的对象,所以对子集的操作会影响到当前集合。
以上方法都是针对有序集合的操作,根据有序集合的特性约束一系列依靠下标索引(Index
)的操作
1.3.5 RandomAccess接口
前面提到过该接口,此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现了该接口的子类应该具有一个特性:表明其支持快速(通常是固定复杂度)随机访问。此接口的主要目的是允许一般的算法(二分法
、快速排序
...)更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能,所以遵循了该接口语义的实现类使用for(int i=0;i<xx;i++)
会比使用Iterator
迭代器速度要快,如:ArrayList根据下标索引直接访问任何位置的元素。
1.3.6 Serializable接口
此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现类可被序列化的。
1.3.7 Cloneable接口
此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现类可被克隆的。
1.4 常用、重要方法的实现
介绍完接口之后,我们来看下UML类图中AbstrctCollection
、AbstractList
、ArrayList
的具体实现类,在讲述过程中笔者会省去那些非重点的方法,比如toString()
、isEmpty()
等方法。
1.4.1 AbstractCollection抽象类
作为Collection的抽象类及几乎所有集合类的父类来说(笔者说的是几乎,比如Map就非其子类
),理应实现所有Collection接口中约束的最基本方法,但有些约束是要在具体场景下才能根据具体需求进行实现,如:add()
需要把对象添加到具体的数据结构中,可以是数组(线性
)、Node(链表
),所以 AbstractCollection中主要实现了以下方法:
contains(Object var1)方法实现
/**
* 方法作用:集合中是否包含var1元素
* 实现方式:使用迭代器进行迭代比对,如果先相同返回
*/
public boolean contains(Object var1) {
Iterator var2 = this.iterator();//抽象方法,具体在子类中会实现
if(var1 == null) {
while(var2.hasNext()) {
if(var2.next() == null) {
return true;
}
}
} else {
while(var2.hasNext()) {
if(var1.equals(var2.next())) {
return true;
}
}
}
return false;
}
toArray()方法实现
/**
* 方法作用:集合转成数组
* 实现方式:new一个集合大小一致的数组,然后用集合迭代器迭代复制到新数组,这个地方的复制
* 使用的Arrays.copy()方法,这个方法在整个线性结构的集合里面大量存在,几乎所
* 所有的集合修改操作都使用到了它,而这个方法底层的实现来自于一个JNI方法,
* System.arrayCopy(),由于是本地方法所以效率非常高
*/
public Object[] toArray() {
Object[] var1 = new Object[this.size()];
Iterator var2 = this.iterator();
for(int var3 = 0; var3 < var1.length; ++var3) {
if(!var2.hasNext()) {
return Arrays.copyOf(var1, var3);//线性结构的集合中大量使用的方法
}
var1[var3] = var2.next();
}
/* finishToArray(var1,va2)方法是当new的这个数组大小不足集合大小时完成多余的赋值
* 那么,明明是根据集合size new出来的数组为什么会放不下呢,因为很可能在复制的过程
* 中集合被修改了。
*/
return var2.hasNext()?finishToArray(var1, var2):var1;
}
add(E var1)方法实现,前面说过了不同子类有不同的实现
public boolean add(E var1) {
throw new UnsupportedOperationException();
}
remove(Object var1)方法实现
/**
* 方法作用:从集合中移除var1元素
* 实现方式:获取迭代器,使用迭代器中的remove删除元素
*/
public boolean remove(Object var1) {
Iterator var2 = this.iterator();//抽象方法,具体在子类中会实现
if(var1 == null) {
while(var2.hasNext()) {
if(var2.next() == null) {
var2.remove();//调用迭代器的remove
return true;
}
}
} else {
while(var2.hasNext()) {
if(var1.equals(var2.next())) {
var2.remove();
return true;
}
}
}
return false;
}
containsAll(Collections<?> var1);
/**
* 方法作用:集合中是否包含所有var1集合中的元素
* 实现方式:写法可以借鉴,如果是你会这样写吗?是不是代码整洁一些呢
*/
public boolean containsAll(Collection<?> var1) {
Iterator var2 = var1.iterator();
Object var3;
do {
if(!var2.hasNext()) {
return true;
}
var3 = var2.next();
} while(this.contains(var3));//调用上面的contains()方法,所以是两层循环
return false;
}
addAll(Collection<? extends E> var1)方法实现
/**
* 方法作用:把var1集合中的所有数据添加到集合
* 实现方式:没什么好说的~~
*/
public boolean addAll(Collection<? extends E> var1) {
boolean var2 = false;
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
Object var4 = var3.next();
if(this.add(var4)) { //调用上面的add()方法,实际是子类中实现的add()方法
var2 = true;
}
}
return var2;
}
retainAll(Collection<?> var1)
/**
* 方法作用:删除当前集合在var1集合中存在的元素(集合交集,但是会把原集合数据从内存删除)
* 实现方式:代码很好懂,不多说,只要删除过一个元素 返回True,否则False
*/
public boolean retainAll(Collection<?> var1) {
boolean var2 = false;
Iterator var3 = this.iterator();
while(var3.hasNext()) {
if(!var1.contains(var3.next())) {
var3.remove();
var2 = true;
}
}
return var2;
}
AbstractCollection总结:虽然抽象类中实现了很多方法,但实际还是基于抽象方法之上的,具体的业务逻辑都在子类实现的抽象方法中,而AbstractCollection中实现的是与业务没关系的公共代码,或者说最原始、最基本的实现,比如:所有查找都是使用的迭代器,实际上我们之前介绍过RandomAccess接口的语义,所以遵循了该接口语义的实现类使用
for(int i=0;i<xx;i++)
会比使用Iterator
迭代器速度要快,后面我们会看到ArrayList对contains()
方法进行了重写
1.4.2 AbstractList抽象类
indexOf(Object o)方法实现
/**
* 方法作用:查找元素o在集合中的位置,没有则返回-1
* 实现方式:利用迭代器进行查询
*/
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
iterator()方法实现,生成按从前到后顺序迭代的迭代器
public Iterator<E> iterator() {
return new AbstractList.Itr();//生成迭代器,即下面的迭代器内部类实现代码
}
/**
* 迭代器内部类实现,此迭代器只有next()获取下一个元素的方法,所以迭代的顺序是从前到后
*/
private class Itr implements Iterator<E> {
int cursor; //下一个元素索引
int lastRet; //调用next()方法返回对象的下标索引
int expectedModCount; //模数:集合每次的修改都会+1,用来判断在迭代过程中集合是否修改过,
//下面会会详细介绍
private Itr() {
this.cursor = 0; //初始化迭代器的时候下一个元素索引从0开始
this.lastRet = -1;//默认-1
this.expectedModCount = AbstractList.this.modCount;//赋值为集合的模数
}
public boolean hasNext() {
//如果当前索引 != 集合大小 说名还有下一个
return this.cursor != AbstractList.this.size();
}
public E next() {
//获取下一个的时候,校验迭代器模数是否==集合模数,如果不等于则证明集合在迭代器
//生成之后被修改过
this.checkForComodification();
try {
int var1 = this.cursor;
Object var2 = AbstractList.this.get(var1);
this.lastRet = var1;//var2对象的下标索引
this.cursor = var1 + 1; //这部分代码很好理解,返回下一个 当前索引+1
return var2;
} catch (IndexOutOfBoundsException var3) {
this.checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if(this.lastRet < 0) {
throw new IllegalStateException();
} else {
//校验迭代器模数是否==集合模数,如果不等于就说明集合在迭代器生成之后被修改过
this.checkForComodification();
try {
AbstractList.this.remove(this.lastRet);//remove当前index的元素
if(this.lastRet < this.cursor) {
--this.cursor; //删除成功后,下一个元素为-1
}
//重置-1
this.lastRet = -1;
//因为做了修改集合大小的操作,所以重新给模数赋值
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
/**
* 校验迭代器模数是否==集合模数,如果不等于则证明集合在迭代器生成之后被修改过
* 如果集合被修改过,说名迭代器失效,抛出异常
*/
final void checkForComodification() {
if(AbstractList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
listIterator()方法实现
public ListIterator<E> listIterator(int var1) {
//校验索引var1是否在0到集合size以内,否则IndexOutOfBoundsException
this.rangeCheckForAdd(var1);
return new AbstractList.ListItr(var1);
}
/**
* 迭代器内部类实现,迭代顺序随意的迭代器,并且在迭代过程中可以修改集合
*
*/
private class ListItr extends AbstractList.Itr implements ListIterator<E> {
ListItr(int var2) {
super();
this.cursor = var2;
}
public boolean hasPrevious() {
return this.cursor != 0;//当下一个索引 !=0 的时候说名还有上一个
}
public E previous() { //返回上一个元素
this.checkForComodification();
try {
int var1 = this.cursor - 1;
Object var2 = AbstractList.this.get(var1);
this.lastRet = this.cursor = var1;
return var2;
} catch (IndexOutOfBoundsException var3) {
this.checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return this.cursor;//返回下一个元素的索引
}
public int previousIndex() {
return this.cursor - 1;//返回上一个元素的索引
}
public void set(E var1) {
if(this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
AbstractList.this.set(this.lastRet, var1);//把元素var1替换当前位置的值
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var3) {
throw new ConcurrentModificationException();
}
}
}
public void add(E var1) {
this.checkForComodification();
try {
int var2 = this.cursor;
AbstractList.this.add(var2, var1);//添加元素,并且把模数更新
this.lastRet = -1;
this.cursor = var2 + 1;
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var3) {
throw new ConcurrentModificationException();
}
}
}
AbstractList总结:主要实现了List接口中的以上3个方法,注意此处
indexOf()
仍然使用的是迭代器遍历的,因为List的数据结构分为线性和链表结构,下面我们可以看到线性结构的ArrayList实现RandomAccess
接口后对indexOf()
进行了重写
- Itr迭代器实现
只能向后迭代,并且在迭代过程中不能对集合元素进行add、set操作,但可以remove
- ListItr迭代器实现
1.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
2.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
3.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现修改,add()可以实现添加,Iterator仅能遍历,不能修改。
1.4.3 ArrayList实现类
private int size;//集合里总共包含多少个元素,应该 <= elementData.length;
private transient Object[] elementData; //这行源码表示ArrayList底层为数组实现,那么既然底层是数组,怎么实现扩容的集合呢?请看下面这个grow()
方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);//前面提到让读者留意的工具类
}
/* 再看看Arrays.copy()方法实现,其实是System.arraycopy方法,这个方法是本地方法,效率会比较快 */
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
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));//底层System.arraycopy()实现
return copy;
}
前面说过ArrayList会对contains()方法重写,接下来我们详细看下
public boolean contains(Object o) {
return indexOf(o) >= 0;//调用的indexOf()方法,我们还记得这个方法在AbstractList中
//使用迭代器实现过,但是ArrayList又对其重写了
}
/* ArrayList已经使用for(int i=0;i<size;i++)来实现, 回想下前面我们说过的
* RandomAccess接口的语义就明白这个的用意,不记得的童鞋可以回到上面看下
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
get()方法实现
public E get(int index) {
rangeCheck(index);//范围校验,不在0到size内就IndexOutOfBoundsException
return elementData(index);//直接通过数组下标返回值
}
set()方法实现
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);//拿出index位置的元素
elementData[index] = element;//设置index位置的元素为新elment
return oldValue;//返回原index位置元素
}
add()方法实现
public boolean add(E e) {
ensureCapacityInternal(size + 1);//如果elementData数组长度不够当前size+1,就扩容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果elementData还是0,则取minCapacity和默认长度10两个数中的大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //因为是添加,所以模数需要加1
if (minCapacity - elementData.length > 0)
grow(minCapacity);//前面介绍过,如果长度不够则扩容
}
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);//本地方法copy数组,把index后的所有数组往前挪一个位置
//最后一个位置置为空,注意看很多JDK源码中都会写到 Help GC或者下面这段话,
//因为设置引用为null之后,GC可以检查到GC Roots 不可达,从而回收内存
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
subList(int fromIndex, int toIndex)方法实现
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);//校验fromIndex和toIndex是否合法
return new SubList(this, 0, fromIndex, toIndex);
}
/* 产生一个当前集合从fromIndex位置到toIndex位置的子集合,
* 这个地方大家看源码留意下子集合的产生过程?
*/
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;//保存父集合的引用
private final int parentOffset;//上面的fromIndex
private final int offset;//针对父集合的偏移量,后续根据用户输入的index进行偏移
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;//默认赋值为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;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
//其实就是获取父集合指定位置的元素,所以子集合并没有new出新内存,这点请读者理解
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
int cSize = c.size();
if (cSize==0)
return false;
checkForComodification();
parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
ArrayList总结:因为ArrayList的底层是数组实现,所以它是一个线性的结构,它在内存中是一段连续的地址,所以我们可以通过Index很快的访问元素,然而我们看到了每add或者remove操作都会调用System.arraycopu()复制改动之后的所有元素,所以我们说随机插入和删除操作线性结构没有链表结构效率高(这里请大家注意,后续讲到链表结构的时候会做一个对比,看看是否一定线性结构的效率比较高)
然后请大家注意的是subList()方法,上面有说过subList并非产生一个新对象,而是通过使用下标偏移量取父类数组中的元素,也就是说子集合和父集合其实在内存中是一个集合,所以对子集合的任何修改将直接影响到父集合,我们很多刚入门的童鞋,可能再使用中会遇到这个问题,所以在这里提出来
总结
ArrayList讲到这里就已经讲完了,因为ArrayList作为我们JCF框架源码分析系列
的第一集,所以里面着重的介绍了Iterable、Iterator、Collection、AbstractCollection、List、Abstract、RandomAccess等接口或抽象类,后续文章中如有重复的就不在赘述。
网友评论