我对ArrayList常用方法的底层源码的理解
常规的思路
add():只能在数组的末端进行增加元素操作
因为Arraylist是基于数组实现的一种集合;首先我们要明确数组的基本特点:①数组的地址是连续的; ②当你对数组进行增删改的时候,如删除操作:你只能用你想删除的元素的后面的元素一个个向前移动一个单位,来达到覆盖你想删除的元素的位置,从而达到删除的操作;举个例子:数组为int[] arr={1,2,3,4};你想删除索引为1(即数值为2)的元素,则你将3 和 4 向前移动一个单位(即3向前移动一个位置覆盖2的位置,4移动覆盖了3的位置)。③数组的容量是固定的,一旦设置是无法改变的
基于上述的特点:
代码展示:
//添加元素
public void add(T data) {
Object[] NewArr = new Object[arr.length + 1];
//实现扩容
for (int i = 0; i < arr.length; i++) {
NewArr[i] = arr[i];
}
//将添加的元素放在数组的末尾
NewArr[arr.length] = data;
//更新内存地址
arr = NewArr;
size++;
}
源码分析
我以 add(1) 为例
进入底层,
public boolean add(E e) {
ensureCapacityInternal(size + 1);
/* 下面public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) 方法执行完了之后;这里相当于int[] copy = new copy[10] */
elementData[size++] = e;
return true;
}
然后进入ensureCapacityInternal()方法, size开始为 0 ,加1之后就成为了 1
private void ensureCapacityInternal(int minCapacity) { //即此时的minCapatity = 1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); //而这个elementData是个数组
}
调用calculateCapacity()方法
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//是个常量的空数组
private static final int DEFAULT_CAPACITY = 10; //默认容量为10,这里有个注意的点,创建无参构造Arraylist集合的时候,并没有把10作为容量,只有进行添加元素操作的时候才真正分配容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); //这里取最大值 Math.max(10,1);
}
return minCapacity;
}
以无参数构造方法创建 ArrayList
时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
然后这里调用了calculateCapacity()方法
private void ensureExplicitCapacity(int minCapacity) { // minCapacity = 10
// 这里是修改集合的次数的意思
modCount++;
if (minCapacity - elementData.length > 0)//此时elementData.length = 0
grow(minCapacity);
}
上述有需要注意的代码 if (minCapacity - elementData.length > 0)
:当你进行add
第二个元素时候,就不会进行grow方法了,直到elementData的长度为10的时候你才会进行这个新的扩容
进入扩容的核心代码grow()
注意:
这里的 >> 含义:向右移一位,也就是oldCapacity除以 2 的 一次幂
若为 << 含义:向左移一位,也就是oldCapacity乘 2 的 一次幂
也就是扩容的机制是当数组空间不足的时候会扩大为原来数组长度的1.5倍(oldCapacity为偶数才是1.5倍,否则是1.5倍左右)
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // oldCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);// newCapacity = 0 + 0/2 = 0
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // newCapacity = minCapacity = 10
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
接下来进入Arrays.copyOf()方法
public static <T> T[] copyOf(T[] original, int newLength) {
// 这里original.getClass()是获取original数组元素的对象
return (T[]) copyOf(original, newLength, original.getClass());
}
然后进入copyOf()方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 三目运算符,不论是哪种结果都是生成一个新的数组而且数组的长度就是newLength
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength] //生成一个新的长度的数组 newLength此时为10
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 进行数组的拷贝
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
// 这个结果运行完了之后返回到add()方法里面
return copy;
}
上述代码用到System.arraycopy()方法
/**
* 这里的native是c++写的底层源码
* 这里讲一下各个参数的作用
* @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);
add(Object index,object target)
源码分析
/**
* 首先进入这个方法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++;
}
//rangeCheckForAdd()进行判断你输入的索引是否在list长度之内和是否规范
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
之后就跳到了ensureCapacityInternal()方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
之后的操作都是和上面第一个方法差不多,都是需要经过calculateCapacity()方法、ensureExplicitCapacity()方法(这个方法判断是否需要扩容 有grow方法),但是这里不需要扩容,因为第一次添加的时候,list已经扩容到容量为10了,也就是说添加到第10次的时候才需要再扩容。
然后到了这个方法的核心代码
System.arraycopy(elementData, index, elementData, index + 1,size - index);
/**
* 拷贝数组
* @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);