文章目录
前言
ArrayList是我们常用到的一个重要的java数据结构。从本质上说ArrayList就是一个数组,可以包含万物的数组,同时它又是一个动态增长的数组。下面就简单的分析下它的一些机制。(本文分析基于JDK1.8)
一.总体介绍
ArrayList的继承关系:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList主要是继承AbstractList类和List接口
这里有一个奇怪的接口RandomAccess,其实这个是有很大的用处的,后面将详细的介绍下该接口的使用。
ArrayList与Collection关系如下图:
二.ArrayList源码浅析
2.1 初始化
从本质上ArrayList是对Object[]数组的封装,ArrayList的设计是为了解决普通的数组只能固定长度。普通数组在使用中一方面可能会出现内存的浪费,另一方面可能会造成数组容量不够。
ArrayList的主要构造函数:
// 默认构造函数
ArrayList()
// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)
// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)
上面的是ArrayList的主要构造函数,这里的E是ArrayList的泛型。
/**
*有参构造函数
*1.根据你输入的initialCapacity生成initialCapacity长度的Object数组
*2.如果你输入的initialCapacity=0,会使用默认的长度为0的数组
*3.如果小于0直接非法参数异常
*/
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);
}
}
/**
* 无参构造函数
* 当使用无参构造函数的时候,内部的创建一个默认的长度为0的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 默认长度为0的Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 有参构造函数
* 将Collection的数据作为ArrayList的一部分
* 通过toArray转换为Object[]的数组,判断长度是否为0。
*/
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);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
这个Object[] elementData
的Object数组是整个ArrayList的真正的容器,ArrayList的add
,remove
,get
等API操作都是针对该数组操作的。
2.2 add
ArrayList之所以被称为动态数组,在于它可以进行扩容,扩容的秘密藏于add方法中
ArrayList的add方法有两个方法分别是add(int index, E element)
和add(E e)
先来看add(E e)
方法。
2.2.1 add(E e)
//add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 添加一个元素,在原有的size基础上增加1,但是注意这里成员变量size的值没有被改变
elementData[size++] = e; //添加一个元素
return true;
}
//ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
//判断是否是ArrayList内部提供的默认数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是,算出minCapacity,取出DEFAULT_CAPACITY和传入的参数minCapacity的最大值
// 同时这种写法也是避免出现minCapacity出现非正数的情况
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//调用ensureExplicitCapacity方法,这时候minCapacity的值一定是大于0的
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//modCount是在对list,Iterator迭代的时候判断是否改变了整体的数据结构的一种标记
modCount++;
//minCapacity > elementData.length才会调用grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//扩容!在原来的数组长度的基础上增加原长度的一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容后数组容量小于minCapacity的情况
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 扩容后的新数组超过MAX_ARRAY_SIZE(ArrayList所能容纳的最大数值)的容错机制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//使用Arrays.copyOf方法来增加原数组的的长度
elementData = Arrays.copyOf(elementData, newCapacity);
}
add方法中调用了ensureCapacityInternal方法,add方法的主要作用是在满足判断条件minCapacity - elementData.length > 0
的情况下,将数组的长度增加原长度的一半。
请注意这句代码:
oldCapacity >> 1
它表示oldCapacity除以2的一次方,实际上就是除以2。这种写法相对于直接写成"oldCapacity /2"的效率更高,java的源码中大量使用了这种写法。分析到现在,实际上还没有增加Object[]数组的容量,那么如何增加已经固定长度的Object[]数组大小呢?秘密就在Arrays
类的copyOf
方法。
2.2.2 Arrays.copyOf
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 保证copy数组是Object[]类型的
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));
return copy;
}
System.arraycopy参数的注释。同时,arraycopy函数是一个native的方法。
// Object src : 原数组
// int srcPos : 原数组的起始位置
// Object dest :目标数组
// int destPos :目标数组的起始位置
// int length :要copy的数组的长度
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
通过上面的add
方法分析,扩容实际上是重新创建了一个比原来数组大二分之一的新数组,再通过System.arraycopy拷贝原数组数据到新数组中。相当于elementData数组扩大了原来数组的二分之一。
2.2.3 add(int index, E element)
这个方法不仅可以向Object数组添加数据,还能在指定位置添加数据。接下来,我们来尝试分析一下。