我们在使用数组的时候,经常困惑的一点是怎样设置数组的大小:因为数组元素的个数可能是未知的,如果我们设置的容量太小,那么我们在添加元素的时候,不得不考虑扩容问题;如果设置的容量太大,又有可能造成空间浪费。而ArrayList的优势就在于此:我们可以在添加和去除数组元素的时候,不用去担心数组的大小设置带来的上述问题。
下面我将简单介绍一下ArrayList的基本原理和我的自定义ArrayList。(全部代码在文末)
1.ArrayList基本原理
ArrayList的内部机制其实就是维护一个数组(见下方代码),我们在添加元素时,它内部可以自己检测容量是否合适,并以此判断是否进行扩容,这就解决了容量过小的问题,当我们确定不再增删元素的时候,可以调用特定的方法,使得多余的内存得以回收,也就解决了容量过大的问题。
//默认的数组容量
private static final int DEFAULT_CAPACITY = 10;
//内部维护的数组
private static final Object[] EMPTY_ELEMENTDATA = {
};
2. 自定义MyArrayList的核心方法
下面简要介绍我实现的核心方法
//数据域
private int size;
private final static int DEFAULT_CAPACITY=10;
private Object[] elementData;
//构造方法
public MyArrayList();
public MyArrayList(int capacity);
//添加
public void add(E e);
public void add(int index,E e);
//删除
public boolean remove(E e);
public E remove(int index);
//获取
public E get(int index);
//修改
public void set(E e,int index);
2.0 下方经常见到的辅助方法含义
1.rangeCheck(index);
检查下标是否合法。
2.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
从源数组src的下标srcPos开始复制大小为length的数组元素到目标数组dest的对应下标destPos里去。
这句话可能有点绕,自己做做实验就能明白,而且不要纠结为什么参数是Object,而不是Object[]。简单说数组也是对象,我们现在能明白这个方法能将特定长度的数组内容复制到另一个数组里去就行。
2.1 MyArrayList构造方法
//无参构造函数,默认容量为10
public MyArrayList(){
elementData=new Object[DEFAULT_CAPACITY];
}
public MyArrayList(int capacity){
if (capacity>=0){
elementData=new Object[capacity];
}else{
throw new IllegalArgumentException("Illegal Argument:"+capacity);
}
}
2.2 add方法
添加元素的时候,其实思路很清晰,主要是判断容量是否已满,满则进行扩容。jdk1.8的版本扩容的核心代码是:
elementData.length+(elementData.length>>1)
这里不用纠结>>是什么意思,你只要知道它是 除二的意思 。至于为什么不是/2 ,网上说是效率的原因,大家关心的可以自行去了解,这里不讨论它。
jdk1.8的官方源码在实现扩容的代码这一块比较复杂,我就简单谈谈我自己写的代码。在扩容这一块,其实最容易犯错的是整除有截断,当数组大小为1或是0的时候,进行扩容之后的容量还是1或是0。所以我写了getNewCapacity()的方法,防止这种极端情况的发生,详见下方代码。
public void add(E e){
if(isFull()) expandCapacity();
elementData[size++]=e;
}
public void add(int index,E e)