1、ArrayList类的说明
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组。数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集合类更好一些,这是使用数组的一大优势。但是我们知道数组存在致命的缺陷,就是在初始化时必须指定数组大小,并且在后续操作中不能再更改数组的大小。在实际情况中我们遇到更多的是一开始并不知道要存放多少元素,而是希望容器能够自动的扩展它自身的容量以便能够存放更多的元素。ArrayList就能够很好的满足这样的需求,它能够自动扩展大小以适应存储元素的不断增加。它的底层是基于数组实现的,因此它具有数组的一些特点,例如查找修改快而插入删除慢。本篇我们将深入源码看看它是怎样对数组进行封装的。首先看看它的成员变量和三个主要的构造器。-------转自 https://www.cnblogs.com/liuyun1995/p/8286829.html
1.1 解释说明
* 可变数组(ArrayList)实现了List的接口. 实现了所有List的操作,和允许所有类型的元素,包括
* 空值,除了实现List接口之外,这个类还提供了操作数组大小(size)这个作为一个元素
* 存储在数组内部。这个数组粗略(roughly)的等同于Vecotr,除了ArrayList不保证线程
* 安全,(当然了还有扩容的方式,ArrayList是1.5倍,Vertor是2倍)
*
*
* ArrayList中的size()计算元素个数,isEmpty()判断数组是否为空,get()获取元素,set()
* 修改元素,iterator()迭代这些操作都是常量O(1)的时间复杂度。而add()增加操作是
* 平均时间复杂度(amortized constant time),换言之,增加一个元素需要O(n)时间复杂度
* 一般来说,所有的其他操作都是线性的时间复杂度。这个算法的常量因子(constant factor)
* 低于LinkedList的。
*
*
* 每一个ArrayList都有一个容量。这个容量时数组的用于存储元素于数组的大小。它总是
* 至少与数组元素个数大小相等。当元素被加入ArrayList,这个容量是会增长的。这个容量
* 扩容的方法的平均时间复杂度没有比增加一个元素还复杂。
*
*
* 可以应用ensureCapacity()方法来对数组扩容当要加入大量的元素的时候,这样可以减少
* 因为增加元素而重分配的次数。(也就是说最好一开始确定好所需要的容量大小)
*
*
* 需要注意的是这个方法不是同步的方法,需要同步的应用(copyonwriteArrayList),如果多个
* 线程同时操作ArrayList实例和至少有一个线程修改list的结构,必须在外部加同步操作。关于
* 结构性操作可以看前面的HashMap的介绍。这个同步操作通常是压缩在某些对象头上面。(synchronized
* 就是存储在对象头上面。)
*
*
* 如果对象头不存在这样的对象,这个列表应该使用{@link Collections#synchronizedList Collections.synchronizedList}工具
* 来封装,这个操作最好是在创建List之前完成,防止非同步的操作。
* List list = Collections.synchronizedList(new ArrayList(...));
* 但是一般不用这个方法,而是用JUC包下的CopyOnWriteArrayList更加高效。
*
*
* 快速失败机制(...好像在集合容器下面都有这个机制来抛出异常...)
* 当一个list被多个线程成同时修改的时候会抛出异常。但是不能用来保证线程安全。
* 所以在多线程环境下,还是要自己加锁或者采用JUC包下面的方法来保证线程安全,
* 而不能依靠fail-fast机制抛出异常,这个方法只是用来检测bug。
1.2 数据结构
ArrayList大概可以用这张图来描述,主要是一个ArrayList实例,里面封装着一个elementdata数组。其中有一个size字段,每次增加一个元素的时候就加1,删除就减1。这样当需要获取数组长度的时候,就可以在时间复杂度为O(1)下。
2、方法字段
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量为10,Vector也是10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享空对象数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
-* 默认的空对象数组,以此来区分当添加第一个元素的时候,数组是怎么样膨胀的
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList元素的数组缓冲区。
* ArrayList的容量是这个数组缓冲区的长度。任何
* 使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA清空ArrayList
* 将在添加第一个元素时扩展为DEFAULT_CAPACITY。
*/
//内部封装的数组
transient Object[] elementData; // 非私有来简化嵌套类的访问
/**
* ArrayList中的元素个数
*/
private int size;
.
.
.
/**
* 能分配的最大数组大小,因为有些数组会在头分配一下信息,
* 所以要注意这里的最大大小是Integer.MAX_VALUE - 8。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
.
.
.
/*
* 多线程环境下,当涉及到结构性修改(add,remove)时,用modCount来判断是否会有多个线程同时修改,
* 当modeCount不是期望值时候,抛出异常
* fail-fast
*/
protected transient int modCount = 0;
}
ArrayList的方法字段比较少,主要的是 private static final int DEFAULT_CAPACITY = 10;
,初始化默认为10的容量大小。还有两个默认的空数组。另外两个就是前面提到。
3、主要方法
构造函数
/**
* 构造具有初始容量的空数组
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//当给出初始容量且初始容量大于0时,用初始容量来初始化
} else if (initialCapacity == 0) {//当给出的默认容量为0时,则构造一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//当给出的是小于0的,则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 如果没有给初始容量,则初始化一个大小为10的默认容量
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 当传入一个数组的时候,则将这个数组包装成ArrayList,并返回这个数组的迭代顺序
*/
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);//将数组赋值到ArrayList
} else {
// 如果传入的是一个空数组,则构造一个空的对象
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的构造函数主要有3种,一种是传入初始容量的,采用默认的,还有就是传入一个数组,以这个数组的大小为容量,里面的元素进行初始化。
trimToSize()
/**
*将ArrayList大小修改为元素个数的大小。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* ArrayList扩容
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//获取原始数组大小
int newCapacity = oldCapacity + (oldCapacity >> 1);//关键是这一句,1.5倍的扩容
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);//拷贝原来的数组到新的数组
}
size(),isEmpty()
/**
* 返回当前ArrayList中的元素个数
*
*/
public int size() {
return size;
}
/**
* 返回是否包含元素
*
*/
public boolean isEmpty() {
return size == 0;
}
indexOf(Object o)
/**
* 返回第一次出现该元素的下标,如果不存在则返回-1
*/
public int indexOf(Object o) {
if (o == null) {//如果对象为空,因为ArrayList允许空对象,所以查找到第一个为空的
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;//找不到,返回-1
}
lastIndexOf(Object o)
/**
* 返回最后一次出现的index,不存在则返回-1
* 与indexOf一样,只不过是从后往前面扫描
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
get() set() add(E e)
// 返回指定位置的元素
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 获取指定下标的元素
*/
public E get(int index) {
rangeCheck(index);//检查下标是否合法
return elementData(index);//返回相应下标的元素
}
/**
* 设置指定下标的元素为新值
*/
public E set(int index, E element) {
rangeCheck(index);//检查下标是否合法
E oldValue = elementData(index);//获取相应下标的旧值
elementData[index] = element;//设置为新值
return oldValue;//返回旧值
}
/**
*增加元素,没有指定下标则在最后添加
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 涉及到结构性修改,modcount++,
elementData[size++] = e;//在末尾追加元素
return true;
}
add(int index, E element)
/**
* 插入元素至指定位置
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // 涉及到结构性修改,modcount++
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//元素后移,实际是复制到另外一个数组中,只不过这里的数组是原数组
elementData[index] = element;//添加元素
size++;//数组中元素个数加1
}
remove()
/**
* 删除指定下标元素
*/
public E remove(int index) {
rangeCheck(index);//检查数组下标是否合法
modCount++;// 涉及到结构性修改,modcount++
E oldValue = elementData(index);//获取下标值
int numMoved = size - index - 1;
if (numMoved > 0)//元素前移,实际上就是一个元素复制覆盖的过程
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 将最后一个元素置空,以便回收
return oldValue;
}
/**
* 删除第一次出现的元素,成功则返回true,否则返回false
*/
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
}
4、总结
每次添加元素前会调用ensureCapacityInternal这个方法进行集合容量检查。在这个方法内部会检查当前集合的内部数组是否还是个空数组,如果是就新建默认大小为10的Object数组。如果不是则证明当前集合已经被初始化过,那么就调用ensureExplicitCapacity方法检查当前数组的容量是否满足这个最小所需容量,不满足的话就调用grow方法进行扩容。在grow方法内部可以看到,每次扩容都是增加原来数组长度的一半,扩容实际上是新建一个容量更大的数组,将原先数组的元素全部复制到新的数组上,然后再抛弃原先的数组转而使用新的数组。至此,我们对ArrayList中比较常用的方法做了分析,其中有些值得注意的要点:
- ArrayList底层实现是基于数组的,因此对指定下标的查找和修改比较快,但是删除和插入操作比较慢。
- 构造ArrayList时尽量指定容量,减少扩容时带来的数组复制操作,如果不知道大小可以赋值为默认容量10。
- 每次添加元素之前会检查是否需要扩容,每次扩容都是增加原有容量的一半。
- 每次对下标的操作都会进行安全性检查,如果出现数组越界就立即抛出异常。
- ArrayList的所有方法都没有进行同步,因此它不是线程安全的。
- 以上分析基于JDK1.8,其他版本会有些出入,因此不能一概而论。