Java工程师不得不知的ArrayList源码-揭开ArrayList扩容机制的面纱
前言:
学集合,光知道各种集合类的用法是不够的
我们知道,集合都是基于对基础数据结构的封装,而数据结构与算法的地位我们也知道有多重要
面试中经常问的集合类源码,其实也算是考你对数据结构及其算法的把握程度了
在之前,我总结了Java提供的几大集合类的知识,看这篇文章的朋友可视情况回顾一下:
Linux的内核作者linus曾经说过,学习计算机的本质方法就是
RTFSC:Read the fucking source code
步入正文
简单说一下ArrayList
ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity
操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量
它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
如果你懂点数据结构,那么应该知道,一般线性表的顺序存储,插入删除元素的时间复杂度为O(n)
求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
-
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能
-
ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问,这也是数组的特性,根据下标可以直接访问元素存放的位置
-
ArrayList 实现了Cloneable 接口即覆盖了函数 clone(),能被克隆
-
ArrayList 实现java.io.Serializable 接口这意味着ArrayList支持序列化,能通过序列化去传输
ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList
ArrayList的扩容机制
下面是arraylist的单参数add方法,会触发扩容机制
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //elementData->类属性
return true;
}
ensureCapcityInternal:
/*minCapacity就是添加后的数组长度*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
elementData
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData;
根据注释,可以得到以下信息
- elementData是一个数组缓冲区,存储ArrayList元素
- 这个缓冲区的长度就是ArrayList的容量大小
- 空的elementData定义为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 当有第一个元素进来时, DEFAULTCAPACITY_EMPTY_ELEMENTDATA将被拓展成DEFAULT_CAPACITY
DEFAULT_CAPACITY
/**
* Default initial capacity.默认初始化容量为10,
*/
private static final int DEFAULT_CAPACITY = 10;
回到上面这段代码
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
如果elementData为空,空的ArrayList添加第一个元素
minCapacity选为为10
即,第一个添加元素的容量将被初始化为10!
如果是不是添加第一个元素的情况,进入下一个方法:
ensureExplicitCapacity(minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //类属性 -->记录修改次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
看看modCount是什么东西
protected transient int modCount = 0;
关于transient:
Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化
什么情况下考虑用反序列化transient:
1、类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;
明显,Java认为这个字段无需写到硬盘
modCount看注释,大概是:
这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。
回到代码
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果minCapacity(新容量)大于缓存数组的长度,即超过之前扩容的容量了
调用grow(minCapacity),此时猜测,要进行真正的扩容了
grow()
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* @param minCapacity the desired minimum capacity
* Junsir注:扩容容量保证新容量至少能容纳新元素数量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //拿到原来的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量=旧容量*2 JunSir注:位运算右移一位等于乘2的一次方,位运算比直接*2效率更高
//异常情况处理,不用关心
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE?先看下面注释相关联常量的内容
//表示新容量已经或即将超负荷
if (newCapacity - MAX_ARRAY_SIZE > 0)
//hugeCapacity(minCapacity)?先看下面注释相关联方法的内容
//Junsir注:返回newCapacity是Integer.MAX_VALUE或者MAX_ARRAY_SIZE取决于你的旧容量是否大于Java定义的最大数组长度 :
newCapacity = hugeCapacity(minCapacity);
//Arrays.copyOf?
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*相关联的常量-MAX_ARRAY_SIZE*/
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/*Junsir注: 定义了一个最大数组的长度,为Integer.Max_Value-8,
*-8是为了使用Jvm所做出的保险操作,降低出错的概率*/
/*相关联的方法-hugeCapacity*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
至此,ArrayList的扩容机制我们已经理解到源码级了,现在可以小结一下:
ArrayList的扩容单位为旧容量的两倍,即新加入元素越容量的时候,容量翻一倍
copyOf
还差最后一个点:记得上面我没做出解释的这段代码么?
//Arrays.copyOf?
elementData = Arrays.copyOf(elementData, newCapacity);
这里可以看出来,是将旧的缓存数组进行复制,并且指定了新的容量,返回了一个新的缓存数组
没有看到任何针对ArrayList的操作,那现在明白了
elementData其实就是我们操作ArrrayList对象的真正结构,也就是说ArrayList不就是
一个数组么!!只是在ArrrayList这个类中,把它封装成了功能如此强大的结构!!