只知道在ArrayList 添加一个元素在尾部添加元素,如果容量不够就会扩容1.5倍,也没有通过源码去研究过这个过程。今天我们就来研究研究:
从 中间插入,和末尾插入 这两种方式 来进行研究。
尾部添加
首先我们实现add方法
@Test
public void testEndAdd(){
ArrayList arr = new ArrayList();
arr.add(1);
arr.add(1);
}
点进add方法我们可以看到add里面有一个ensureCapacityInternal
方法,并且传了一个size(list里面数据的长度)+1的参数到这个方法里面
elementData 是Arraylist中存放元素的真正数组,size是当前数组中存了几个元素,而不是数组的长度!!!
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
点进ensureCapacityInternal方法,我们可以看到我们传进来的size+1是minCapacity这个参数,这个参数就是加入一个元素后元素的个数
private void ensureCapacityInternal(int minCapacity) {
//判断数组是不是空,我们第一次的添加是空,minCapacity就变成了DEFAULT_CAPACITY--> 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确定是否需要扩容,下一步进入
ensureExplicitCapacity(minCapacity);
}
点击进入ensureExplicitCapacity
我们首先看到是一个modCount++
并且在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,所有使用modCount属性的全是线程不安全的,而且只有在本数据结构对应迭代器中才使用。 这里我不深究,大家可以参考这篇文章
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//判断minCapacity(元素的个数)是否大于 elementData.length(数组的长度),这里因为是初始化所以是10,其他情况不一样
//进入grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//由于是第一次添加oldCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);//这里是扩容到的1.5倍,所以newCapacity = 0
//情况一:
//如果扩容后的长度还是小于当前需要存入的个数minCapacity ,为扩容为minCapacity 的大小
if (newCapacity - minCapacity < 0)//判断是否大于0,这里是 0-10 < 0
newCapacity = minCapacity;//newCapacity = 10
//情况二:
//判断传入的参数minCapacity是否大于MAX_ARRAY_SIZE,如果minCapacity大于MAX_ARRAY_SIZE,返回Integer.MAX_VALUE,否者返回MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//这个Arrays.copyOf()我们很熟悉,程序走到这里就代表老老实实的扩容了。将原来的数据也复制在这个数组中。
elementData = Arrays.copyOf(elementData, newCapacity);
}
这里我们也不深究这个还与jvm储存头部字有关系,而且我们一般也用不到这么大的空间,有兴趣看这篇文章
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
到下一步,这个代码看起就有点打脑壳,意思就是说先判断这个数组的是不是Object类型的,如果是直接创建一个的Object的数组Object[newLength]
数组,这个newLength
就是我们需要扩容后的大小,如果不是Object的就直接new一个目标类型,长度也是newLength
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//最终目的就是保证传入给System.arraycopy() 方法中参数"copy" 的值是一个我们扩容后的新数组
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//把添加的元素放入数组中已存入元素个数的+1位置!
elementData[size++] = e;
return true;
}
自此完成了尾部的添加
中部插入
插入代码
@Test
public void testMidAdd(){
ArrayList arr = new ArrayList();
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(3,2);
}
进入add方法
public void add(int index, E element) {
//首先检测范围,看是否越界
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
进入ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
//判断数组是否为空,这里不为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//直接跳到这一步
ensureExplicitCapacity(minCapacity);//minCapacity = 6
}
进入ensureExplicitCapacity
,if (minCapacity - elementData.length > 0)
这个是判断minCapacity
(元素个数)与elementData.length
(数组长度)的大小关系,如果元素个数大于数组长度就会进入grow
方法进行扩容,这里面就与上面尾部添加的流程一样了。这里minCapacity (元素个数)- elementData.length(数组长度)= 6 - 10 < 0,所以就不进入grow方法。
private void ensureExplicitCapacity(int minCapacity) {
//修改次数+1
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
然后就会回到add方法 进行System.arraycopy
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//这一步就是中部的插入的核心语句,我放在外面说
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
核心语句
如果不需要扩容的话(需要扩容的话就先扩容),系统会将index下标以后的元素,统一向后移动一位,也就是把elementData
中从index
开始以后的元素,复制到elementData
的index+1
的位置,复制个数呢,就是index
以后的所有元素,也就是size-index
个,这里源数组和目标数组一样。
System.arraycopy(elementData, index, elementData, index + 1,size - index);
我们可以点进去看一下每个参数的含义:
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
五个参数分别为
src:源数组
srcPos:从源数组的下标开始复制
dest:目标数组
destPos:目标数组的下标开始
length:复制多少个元素
我们插入的是这段代码,我们对其进行分析
@Test
public void testMidAdd(){
ArrayList arr = new ArrayList();
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(1);
arr.add(3,2);
}
这里就是 从源数组elementData
以index作为起始位置,截取长度为size - index
:5 - 3(size是以0开头的),截取了三个数。截取到的新数组,向目标数组elementData
以index+1
:4 ,为起始位置进行复制添加。刚刚就中间就空出了一个索引为index:3 的位置,然后在执行下面两句话,将指定元素赋值到索引位置,然后size+1 ,就完成了元素从中间插入的过程。
elementData[index] = element;
size++;
执行结果: