ArrayList源码分析 By 哇塞大嘴好帅
ArrayList介绍
ArrayList的底层是动态数组,意思就是它可以动态增长,在我们添加大量的元素可以使用ensureCapatity增加实列的容量,这样可以减少扩容的次数。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
在我们ArrayList接口我们实现了RandomAccess和Cloneble,和Serializable空儿脸皮
RandomAccess主要标识了我们支持随机访问
Cloneble主要覆盖了clone函数,告诉我们list可以被克隆。
Serializable就是意味着我们可以支持序列化,可以通过序列化去传输
ArrayList和Vector的区别
ArrayList是List的主要实现类,底层使用Object Array 实现,适合频繁的查找工作,不是线程安全的
Vector是list的古老实现类,底层也是采用Object[] 存储,是线程安全的
ArrayList和LinkedList的区别
arrayList和linkedList都不是线程安全的
ArrayList的底层使用的Object数组,而LinkedList底层采用得到是双向链表,在jdk1.6之前采用的是循环链表。
当ArrayList插入一个元素的适合,他会受到插入位置的影响,当我们在尾巴插入的时候时间复杂制度是O(1),当我们在第i个位置插入的时候我们的时间复杂度是O(i-1),
当我们LinkedList插入元素的时候我们使用add的方法的时候时间复杂度是接近O(1)的,如果我们在第i个位置插入的情况,我们需要移动到指定的位置进行插入,时间复杂度是O(i)
ArrayList是支持快速随机访问的,因为Arraylist的底层就是数组,LinkedList的底层是双向链表,因为链表的特性不支持快速随机访问。
内存占用方面LinkedList有多少个元素就占用多少个内存,而ArrayList在尾部会预留一定的内存,因为他的底层是数组。
ArrayList扩容机制
构造方法
在Arraylist有3种方法创建
//默认容量大小
private static final int DEFAULT_CAPACITY = 10;
//使用默认容量,容量为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定容量大小的构造方法
public ArrayList(int initialCapacity) {
//初始容量大于0
if (initialCapacity > 0) {
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
}
//初始容量等于0
else if (initialCapacity == 0) {
//创建null数组
this.elementData = EMPTY_ELEMENTDATA;
}
//初始容量小于0,抛出异常
else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
在JDK1.8版本,如果我们指定长度是0,我们会创建一个空数组,当我们插入一个元素的时候,我们会对数组进行扩容。扩容的容量为10
Add方法
//add function
public boolean add(E e) {
//添加元素之前,先调用ensureCapacityInternal()
ensureCapacityInternal(size + 1);
//元素赋值
elementData[size++] = e;
return true;
}
ensureCapacityInternal
//获得最小扩容量 确保容量够大够插入新的元素
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取默认的容量和传入参数的较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断是否需要扩容
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果我们数组长度大于我们的阈值那么就代表我们需要扩容
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
模拟一下我们定义默认长度为0会发生什么?
首先我们插入了一个元素,我们会首先调用ensureCapacityinternal,这个方法主要做了我们当前数组的长度和我们的阈值谁比较大, 这时候我们的minCapacity会变成1,然后我们的default capacity是10,我们这时候判断他们的最大值是10,然后在将最大的值看为最小容量从换入给ensureExplicitCapacity方法,这时候我们会判断一下,如果minCapacity - 数组长度 >0的话,那么就需要扩容调用grow方法。
·当我们插入一个元素时,我们的elementData.length 为0,此时还是一个空的list,但是我们执行过ensureCapacityInternal方法,所以minCapacity此时容量是10,因为我们比较传入的size+1和Default_Capacity谁比较大。所以我们的mincapacity - elementData.length > 0 成立,所以会进入grow方法进行扩容
·当add第二个元素的时候,mincapacity为2,然后在elementDate.length()在添加第一个元素后扩容变成10,此时10 - 10 >0 不成立。
· 直到我们添加第11个元素mincapity为11比elementData.LENGTH(10)大,这时候进入grow方法扩容,。
grow 扩容核心方法
//要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//扩容核心方法
private void grow(int minCapacity) {
//oldCapacity是旧容量,newCapacity是新容量
int oldCapacity = elementData.length;
//将oldCapacity 右位移一位等于 /2
//新的容量更新为旧的容量1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果我们最新容量小于我们最小要求容量,那么就将我们的新容量设置为最大容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果我们的新容量大于MAX_ARRAY_SIZE则执行hugeCapacity方法来比较minCapacity和MAX_ARRAY_SIZE,如果不成立那么最大容量还是Int的最大值-8,如 //果成立最大值为Int的最大的val
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将旧数组的数据移动到新的数组上
elementData = Arrays.copyOf(elementData, newCapacity);
}
//如果我们的容量大于MAX_ARRAY_SIZE则执行hugeCapacity方法来比较minCapacity和MAX_ARRAY_SIZE,如果不成立那么最大容量还是Int的最大值-8,如 //果成立最大值为Int的最大的val
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) //
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
我们扩容流是这样的,我们的新容量是旧容量的1.5倍,如果我们的新容量小于最小容量,那么我们的新容量就是最小容量,如果大于最小容量那么就通过hugeCapacity方法判断一下 我们的mincapatity是否大于MAX_ARRAY_SIZE,如果大于那么我们的新容量就是Int的最大值,如果不大于就是int的MAX_val - 8.
System.copy()方法
* @param src 源数组
* @param srcPos 源数组中的起始位置
* @param dest 目标数组
* @param destPos 目标数组中的起始位置
* @param length 要复制的数组元素的数量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
在我们list的指定下标添加的地方
/**
* 首先调用rangeCheckForAdd() 进行边界检测,然后调用ensureCapacityInternal保证capacity足够大
* 然后再从index下标开始所有元素后移动一位
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//arraycopy()方法实现数组自己复制自己
//elementData:源数组;index:源数组中的起始下标;elementData:目标数组;index + 1:目标数组中的起始下标; size - index:要复制的数组元素的个数;
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
Arrays.copyOf()方法
public static int[] copyOf(int[] original, int newLength) {
// 申请一个新的数组
int[] copy = new int[newLength];
// 调用System.arraycopy,将源数组中的数据进行拷贝到新的数组,并返回新的数组
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
个人觉得Arrays.copyOf是为了给数组扩容使用,然后System.copy是为了给在某个地方插入元素或者删除元素。
ensureCapacity方法
通过这个方法预设大小我们可以调用这个方法们可以减少扩容的次数,
/**
* 使用此方法预设大小可以减少扩容次数
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}