从源码层面重新认识ArrayList结构
目录
前言
阅读源码可以提升我们编码的能力,同时可以让我们更好的理解原理和架构设计。OK,废话不说, 让我们一起来看下ArrayList底层属性和构造方法以及扩容策略吧!
一、ArrayList 属性+构造
package java.util;
import ...
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例。
private static final Object[] EMPTY_ELEMENTDATA = {};
//共享空数组实例,用于默认大小的空实例。 我们将其与EMPTY_ELEMENTDATA区分开来,以便知道在添加第一个元素时膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//非私有以简化嵌套类访问
transient Object[] elementData;
//ArrayList集合的大小
private int size;
//构造具有指定初始容量的空列表。
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);
}
}
//构造一个初始容量为10的空列表。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
}
二、底层扩容操作
涉及多个方法调用,建议顺序观看
1.add方法
代码如下(示例):
// 将指定的元素追加到此列表的末尾。
public boolean add(E e) {
// 方法调用
ensureCapacityInternal(size + 1); // Increments modCount!!
//下标后移一位
elementData[size++] = e;
return true;
}
add方法是我们常用的添加成员方法,内部先是调用ensureCapacityInternal()方法,传入扩容长度,并将参数值存入数组下标后移一位的成员中。
2.ensureCapacityInternal方法
代码如下(示例):
private void ensureCapacityInternal(int minCapacity) {
// 1 // 2
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
该方法有两个方法调用分别是calculateCapacity()传入数组和扩容长度,将返回值传入ensureExplicitCapacity方法中
3.calculateCapacity
方法
作用:判断数组是否为空,不为空则可以正常扩容
代码如下(示例):
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断传入的数组是否是空数组, DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前定义好的空数组实例
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//Math.max方法作用:返回两个参数中的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果判断这不是一个空数组,表示可以扩容,则正常返回minCapacity
return minCapacity;
}
4.ensureExplicitCapacity
方法
作用:modCount++;判断传入的容量长度是否大于数组的长度,大于则调用grow方法进行扩容,否则不做处理
代码如下(示例):
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果传入的容量值当前数组的长度,则调用grow方法进行扩容,否则不做处理
if (minCapacity - elementData.length > 0)
//调用grow,增量容量
grow(minCapacity);
}
5.grow
方法
作用:具体的扩容策略,正常扩容的话,新数组长度等于原数组长度的1.5倍,判断扩容长度和根据扩容机制扩容后的新数组长度的关系,谁大用谁,对扩容长度超过最大限定长度做特殊判断,调用hugeCapacity方法。
代码如下(示例):
//增加容量,以确保它至少可以容纳最小容量参数指定的元素数量。
private void grow(int minCapacity) {
/* 以下是具体的扩容机制 */
// overflow-conscious code
// 获取原数组的长度
int oldCapacity = elementData.length;
// 新数组长度等于原数组长度的1.5倍 oldCapacity右移一位(相当于除2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 判断如果新数组长度小于扩容后的长度 将传入扩容长度作为新数组长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果扩容长度超过限定的最大扩容长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 执行Arrays工具类的copyOf方法
// 把旧数组的值赋值给新数组,新数组赋值给原引用,旧数组等待GC回收
elementData = Arrays.copyOf(elementData, newCapacity);
}
6.hugeCapacity
方法
作用:对扩容长度超过限定的最大扩容长度做处理
代码如下(示例):
//限定的最大数组长度。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断传入的数组是否是空数组, DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前定义好的空数组实例
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//Math.max方法作用:返回两个参数中的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果判断这不是一个空数组,表示可以扩容,则正常返回minCapacity
return minCapacity;
}
6.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) {
@SuppressWarnings("unchecked")
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;
}
//从指定的位置开始,将数组从指定的源数组复制到目标数组的指定位置。
//将数组组件的子序列从src引用的源数组复制到dest引用的目标数组。
//复制的组件数量等于参数length。
//将源阵列中位置为srcPos到srcPos+length-1的组件分别复制到目标阵列位置为destPos到destPos+length-1的组件。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
到此为止就算是到java的底层了,再接下来就是C++来实现具体的copy功能了
总结
文章中可能会有部分纰漏,欢迎留言指出。