List集合之ArrayList集合分析
这里方便阅读代码,加深印象。自定义List接口,自定义ArrayList实现自定义接口。LsArrayList类模仿ArrayList类实现了LsList的核心接口:size、add、get、remove,并分析ArrayList源码。
自定义List接口,定义核心接口
public interface LsList<E> {
/**
* 定义数组长度
*/
int size();
/**
* 添加元素
* @param e
* @return
*/
boolean add(E e);
/**
* 使用下标查询元素
* @param index
* @return
*/
E get(int index);
/**
* 删除元素
* @return
*/
E remove(int index);
}
LsArrayList实现LsList接口
public class LsArrayList<E> implements LsList<E>{
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 初始化大小为0
*/
private int size;
/**
* 调用add方法时,为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* transient表示elementData数组不能被序列化,elementData存放数组所有数据
*/
transient Object[] elementData;
/**
* 初始化集合为空的数组
*/
public LsArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
@Override
public int size() {
return size;
}
@Override
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 如果数组元素为空,给他默认大小为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// modCount++; 作增删时 变量++保证安全性
// 10-0>0 扩容 判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 扩容
* @param minCapacity
*/
private void grow(int minCapacity) {
// 获取数组长度 oldCapacity原容量 newCapacity新容量 原容量=0
int oldCapacity = elementData.length;
// 新容量=原容量+原容量向右位移1位 相当于原容量/2 oldCapacity >> 1 二进制右移 >>1 /2 || <<1 *2 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量-最小容量 = 0-10<0 新容量=-10
if (newCapacity - minCapacity < 0) {
// 第一次对数组做初始化容量操作 newCapacity=10
newCapacity = minCapacity;
}
// 扩容,将旧的数据复制到新的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
@Override
public E get(int index) {
// 检查下标是否越界
rangeCheck(index);
return (E) elementData[index];
}
@Override
public E remove(int index) {
// 检查下标是否越界
rangeCheck(index);
// modCount++;
// 获取要删除的对象
E oldValue = get(index);
// 计算需要移动的位置
int numMoved = size - index - 1;
// 判断需要删除的元素不是最后一个元素,将需要删除的元素后面的所有元素往前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// numMoved为0时,也就说明 需要删除的是最后一个元素,直接将最后一个数据置为null,让GC回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 检查下标是否越界
* @param index
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("下标位置越界了!index为:"+index);
}
}
ArrayList数组移除元素图解
举例: 若此时有一个装有五个元素(突然好想你、顽固、好好、天使、知足)分别对应下标为(0、1、2、3、4)的数组,并对该数组作移除元素操作。大致可分为两种情况:需要移除的元素下标为数组最后一个位置和需要移除的元素下标不是最后一个元素。
移除最后一个元素(下标为4)
需要移除的元素下标为数组最后一个位置:直接将该下标的元素置为null,让GC回收。
移除第一个元素(下标为0)
需要移除的元素下标为数组第一个位置:将下标分别为1234的元素往前移动一个位置,将最后一个元素置为null,让GC回收。
移除第二个元素(下标为1)
System的arraycopy方法参数作用
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
src 原数组
srcPos 原数组该位置作为起始位置开始复制
dest 目标数组、destPos 目标数组的起始位置
length 要复制数组的长度
测试
public class TestLsArrayList {
public static void main(String[] args) {
LsArrayList<String> list = new LsArrayList<>();
list.add("突然好想你");
list.add("顽固");
list.add("好好");
list.add("天使");
list.add("知足");
// 移除元素
list.remove(3);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println(list.size());
System.out.println(list.get(4));
}
}
运行结果
突然好想你
顽固
好好
知足
4
Exception in thread “main” java.lang.IndexOutOfBoundsException: 下标位置越界了!index为:4
总结
- ArrayList集合查询、修改效率高。因为可以直接根据下标位置定位到该数据。时间复杂度为O(1)。
- ArrayList集合新增、删除效率低。因为如果在集合中间插入或删除数据,该数据后的数据的下标位置需要重新排序,所以效率低。
如文章有误,欢迎批评指正,欢迎交流。
参考:蚂蚁课堂集合源码分析视频。