集合对象-ArrayList实现原理源码分析
Posted hymKing
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合对象-ArrayList实现原理源码分析相关的知识,希望对你有一定的参考价值。
最近再重看并发编程,又有新的体会,读书真是一个神奇的事情,读书能够提升认知水平,认知水平提高后,再复读往往能够获得新知。扯远了,在读构建线程安全类章节中,看到了CopyOnWriteArrayList,今天咱们结合ArrayList,Vector做一个简单的对比记录。
主要从以下几个维度来看他们之间的联系和差别,比如代码底层的数据结构的实现、扩容机制上的区别、是否线程安全、性能上的差别。
一、ArrayList
继承体系图:
ArrayList直接实现了了四个接口如上,其中List提供了基础的添加、删除、遍历操作;
RandomAccess提供了随机访问能力、Cloneable接口,提供了可以被克隆、Serializable接口,提供了可以序列化的能力;
RandomAccess、Cloneable、Serializable都是空接口,这种空接口在Jdk的机制中,这种标记接口被实现后,起到给类打标记的作用,程序在运行期间通过识别标记,实现相应的功能。当然有兴趣了解更多标记接口的实现和运行机制,可以查阅相关其它资料。
二、源码分析
内部存储结构声明部分:
/**
* 默认容量为10,通过new ArrayList()创建时的默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,用于传入的容量为0的时候使用,是通过new ArrayList(0)创建的时候使用这个数组
*/
private static final Object[] EMPTY_ELEMENTDATA = ;
/**
* 空数组,通过new ArrayList()时,或传入容量时使用,添加第一个元素的时候会重新初始化为默认容量大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = ;
/**
* 存储元素的数组,transient是序列化排除作用,这个字段不会被序列化
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 包含元素个数,即list的大小,注意非存储元素数组的大小
* @serial
*/
private int size;
上面定义了三种不同的数组,都是怎么用的呢,接着看构造函数方法源码
/**
* 依据初始化容量构造一个空列表
*
* @param initialCapacity list的初始化容量
* @throws IllegalArgumentException 容量为负数的时候会
*/
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 boolean add(E e)
//检查是否要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
//检查是否要扩容的过程,检查当前容量,确定明确的符合本次添加调用的所规划容量值
private void ensureCapacityInternal(int minCapacity)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//通过new ArrayList()方式创建,元素素组elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//计算最小容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
//确定明确的容量
ensureExplicitCapacity(minCapacity);
确定最小容量是通过这句代码实现:minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
其中DEFAULT_CAPACITY=10,第一次添加元素size=0,minCapacity=size+1=1,取最大值。那么通过new ArrayList()默认构造方法创建的Arraylist,元素数组会在被第一次添加元素的时候,初始化默认长度为10的数组。接着看确定明确容量的源码细节,ensureExplicitCapacity(minCapacity)的源码如下:
private void ensureExplicitCapacity(int minCapacity)
modCount++;
// 容量溢出扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
如果最小容量超出了计算出来的最小容量minCapacity,则执行扩容,调用grow(minCapacity)方法,方法源码如下:
//为支撑最小容量,进行扩容,并不一定扩容到最小容量相等的容量值
private void grow(int minCapacity)
// overflow-conscious code
//旧的容量值
int oldCapacity = elementData.length;
//新的容量值 oldCapacity >> 1 右移运算符 相当于除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍
//扩容后不满足最小容量值,确定新的容量值为最小容量值minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量已经超过最大容量了,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//方法内部的实现,比较简单就不多注解了
//以新容量拷贝出来一个新数组,并指向原元素存储数组elementData
elementData = Arrays.copyOf(elementData, newCapacity);
至此,arrayList的核心实现,就分析完了,其它相关方法包括 addAll、remove等等其它实现,大部分操作的最终也都调用了原始数组的copy,这里就不详述。
关于arrayList的线程安全性,我简单看一下方法声明部分的代码如下:
public void add(int index, E element)
public E remove(int index)
public E get(int index)
整个arrayList的操作,没有加入任何线程安全维度的设计,arrayList是非线程安全的动态数组集合类。简单再看下remove方法的源码:
/**
* 按索引位置移除元素
*/
public E remove(int index)
//越界检查
rangeCheck(index);
.......
/**
* 移除元素
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o)
//区分null和非null元素
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;
fastRemove(index)的源代码:
/*
* 跳过边界检查,移除的元素不返回
*/
private void fastRemove(int index)
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
//代码中的大部分操作都使用了数组复制,调用的是native层的方法,效率会高
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
总结一下
- arrayList的实现的数据结构很简单,就是数组;
- arrayList的设计一套扩容机制,通用情况下的扩容量是原数组大小的1.5倍
- arrayList是非线程安全的,并发编程不建议使用。
- arrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
- arrayList添加或删除元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
- arrayList支持集合并集、交集、差集操作。
落笔写的初衷是做几个集合对象的对比,ArrayList的内容篇幅就够长了,再分析下去篇幅就更长了,后续再补充集合对象的对比总结。
以上是关于集合对象-ArrayList实现原理源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Java集合框架 List接口实现类--ArrayList源码分析