ArrayList源码基本实现和效率分析
1.ArrayList底层原理
1.1 存储结构
arraylist底层使用数组作为数据存储结构,使用一个Object[]数组来存储数据,当创建一个arraylist集合时,会创建一个Object[]数组,具体数据中存储的数据类型根据泛型进行约束,这里对源码进行一个基本功能的实现
1.2 成员属性
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
底层数组的初始化默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};
当用户创建集合未设置容量时elemrntData的引用指向
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
当用户创建集合设置容量为0时elemrntData的引用指向
transient Object[] elementData;
arrayList底层存储数据的数组
private int size;
记录arrayList底层数组中元素的个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
数组结构扩容的最大范围,即数组中元素最大的个数
1.3 构造方法
ArrayList提供了三个构造方法来创建ARrayList集合
1.3.1ArrayList(int initialCapacity)
该方法要求用户传入初始化容量大小,用于分配空间
当传入容量为0时,elementData= EMPTY_ELEMENTDATA
当传入容量大于0时,elementData=new Object[initialCapacity]
1.3.2ArrayList()
该方法不要求用户传入参数,默认构建空数组elementData= DEFAULTCAPACITY_EMPTY_ELEMENTDATA
1.3.3ArrayList(Collection<? extends E> c)
该方法可以将Collection对象转换为ArrayLIst集合,实现是调用了toArray() 和 Arrays.copyOf()方法
2.ArrayList方法
Collection是接口List的实现类,间接是Collection的实现类,在方法的命名上遵循List接口的命名规则,实现对集合【底层数组】中数据的增删改查的操作
2.1添加元素方法add
添加元素的方法有两个,一是添加元素到集合末尾,二是添加元素到指定下标位置
在源码中,对底层数组是否进行扩容操作进行了方法封装,这里没有进行,如果想要查看封装,请移步到源码进行查看
添加元素到指定下标位置实现,只需要将底层数组中指定下标包括之后元素向后移动一个位置,将元素插入即可
同时,插入操作需要对记录集合元素个数的成员属性size进行++操作
/**
* 添加元素到数组末尾的方法
*
* @param e 泛型约束的数据类型 要添加的元素
* @return 返回添加状态 true代表成功
*/
public boolean add(E e) {
if (size == elementData.length) {//判断容量是否满足要求
grow(size + 1);//扩容
}
elementData[size] = e;
size++;
return true;
}
/**
* 添加元素到数组指定下标的方法
*
* @param index int类型 插入位置的下标
* @param e 泛型数据类型 要添加的元素
* @return 返回true 代表添加成功
*/
public boolean add(int index, E e) {
//index检查
checkIndexAdd(index);
if (size == elementData.length) {
grow(size + 1);
}
for (int i = size; i > index; i--) {
elementData[i] = elementData[i - 1];
}
elementData[index] = e;
size++;
return true;
}
2.2删除元素方法remove
删除集合中元素,这里实现了源码中的两个方法:指定下标删除,指定元素删除寻找到的第一个
/**
* 删除指定下标元素
*
* @param index int类型 数组下标
* @return 返回被删除的元素
*/
public E remove(int index) {
//检查下标-> 数据左移 -> 最后一位数据赋值空 -> size-1
checkIndex(index);
E oldValue = (E) elementData[index];
for (int i = index; i < size; i++) {
elementData[i] = elementData[i + 1];
}
elementData[size - 1] = null;
return oldValue;
}
/**
* 删除指定元素 从头遍历 只删除第一个指定元素
*
* @param obj 用户指定的数据 Object数据类型
* @return 返回删除状态 返回false代表数据没有找到
*/
public boolean remove(Object obj) {
if (null == obj) {
for (int i = 0; i < size; i++) {
if (elementData[i] == null) {
remove(i);
return true;
}
}
} else {
/*
使用了equals进行元素对比,针对于List中的对象,需要重写equals方法?
*/
for (int i = 0; i < size; i++) {
if (obj.equals(elementData[i])) {
remove(i);
return true;
}
}
}
return false;
}
2.3更改元素方法set
更新方法,将更新前的元素返回给用户
/**
* 更新指定下标的元素
*
* @param index int数据类型 指定下标未知
* @param e 泛型约束的数据类型 要更新的数据
* @return 泛型约束的数据类型 返回修改前的数据
*/
public E set(int index, E e) {
checkIndex(index);
E oldValue = (E) elementData[index];
elementData[index] = e;
return oldValue;
}
2.4查询元素方法
这里实现了五个源码中的方法
/**
* 获取指定下标元素的方法
*
* @param index int数据类型 指定下标
* @return E泛型约束类型 返回指定下标未知的元素
*/
public E get(int index) {
checkIndex(index);
return (E) elementData[index];
}
/**
* 返回指定元素的第一个下标
*
* @param obj Object 指定元素
* @return int数据类型 元素下标位置 返回-1代表数据不存在
*/
public int indexOf(Object obj) {
if (obj == null) {
for (int i = 0; i <size ; i++) {
if (elementData[i] == null){
return i;
}
}
} else {
for (int i = 0; i <size ; i++) {
if (obj.equals(elementData[i])){
return i;
}
}
}
return -1;
}
/**
* 寻找指定元素的最后一个下标
* @param obj Object 指定元素
* @return int数据类型 元素下标位置 返回-1代表数据未找到
*/
public int lastIndexOf(Object obj) {
if (obj == null) {
for (int i = size-1; i >= 0 ; i--) {
if (elementData[i] == null){
return i;
}
}
} else {
for (int i = size-1; i >= 0 ; i--) {
if (obj.equals(elementData[i])){
return i;
}
}
}
return -1;
}
/**
* 判断指定元素是否存在集合中
* @param obj Object 指定元素
* @return Boolean 返回true存在 返回false不存在
*/
public boolean contains(Object obj) {
return indexOf(obj) > 0;
}
/**
* 集合转换成Object数组
* @return Object[]
*/
public Object[] toArray() {
return Arrays.copyOf(elementData,size);
}
/**
* 从指定下标开始截取新集合到指定下标
* @param start int 开始下标
* @param end int 截至下标
* @return List 截取后的集合
*/
public List<E> subList(int start, int end) {
checkIndexSubList(start,end);
ArrayList<E> newArrayList = new ArrayList<>();
newArrayList.elementData = Arrays.copyOfRange(elementData,start,end);
return (List<E>) newArrayList;
}
2.5其他私有方法
在以上增删改查的方法中,需要调用一些私有的封装方法来完成操作,例如:在增加、删除、修改、查询指定下标元素、截取指定集合时,对用户传入下标的验证操作、当底层数组容量不足时的扩容操作,
2.5.1检查下标方法
/**
* 检查截取集合方法的下标是否合法
* @param start 开始下标
* @param end 结束下标
*/
private void checkIndexSubList(int start, int end) {
if (start > end){
throw new IndexOutOfBoundsException("您输入的下标不正确 start > end" );
}
checkIndex(start);
checkIndex(end);
}
/**
* 检查添加元素时传入的下标是否合法 当下标小于0时不合法
*
* @param index int数据类型
*/
public void checkIndexAdd(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
/**
* 检查元素下标是否合法 当大于size 或者小于0 时不合法 用户删改查时对输入下标的判断
*
* @param index int类型
*/
public void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
/**
* 当对下标进行检查后下标不合法时抛出IndexOutOfBoundsException异常时的message信息
*
* @param index int类型 数组下标
* @return String类型 msg提示信息
*/
public String outOfBoundsMsg(int index) {
return "下标不合法:" + index + "当前size为:" + size;
}
2.5.3扩容方法
/**
* 扩容方法
*
* @param minCapacity 数组的最小容量
*/
private void grow(int minCapacity) {
//获取新数组容量 ->判断新数组容量是否满足最小容量要求 ->判断新数组容量是否超出最大容量 ->创建新数组->数据拷贝->转移引用
int newCapacity = elementData.length + elementData.length / 2;
if (minCapacity > MAX_ARRAY_SIZE) {
throw new IllegalArgumentException("无法进行扩容,容量已经达到最大:" + MAX_ARRAY_SIZE);
}
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.6tips
在以上代码中,使用了Arrays工具类的一些方法,这里不做解释,请自行查找资料
4.ArrayList效率分析
由于ArrayList集合底层使用了数组作为数据存储结构,同时也继承了数组的优点和缺点
向集合中增加元素时,需要对底层数组的容量进行判断,当容量不足时需要调用扩容方法进行扩容的操作,在扩容时数据的复制需要耗费较长时间;指定下标位置插入数据的话,又需要将指定下标位置往后元素整体向右移动,需要耗费较长时间。
当批量删除指定下标元素时,需要将指定下标后方数据整体左移,耗费较长时间;当批量删除时,会造成底层数组空间冗余的问题。
查询时,由于底层时使用数组作为存储结构,每个元素都在内存中有自己的"空间地址",使用数组+下标的方式进行寻址操作效率较高。
因此,ArrayList集合,在增加或者删除元素时的效率相对较低,在查询元素时的效率相对较高。