java中的ArrayList底层是通过数组实现的,下面是我对ArrayList源码阅读之后整理并添加了注释之后的整理,为了方便,就只贴出ArrayList的add方法以及它的一些思想还有边界条件处理:
public class ArrayList<E> implements List<E> {
private Object[] elementData;//存储数据的数组
private int size;//数组的当前个数 而不是最大个数
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//为什么最大是2^31-8? As the Array itself needs 8 bytes to stores the size 2147483648(原文)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
private boolean RangeCheck(int index){
return (index>-1)&&(index<size);
}
//保证newSize不会越界 将集合的capacity增加到minCapacity minCapacity为所需最小容量
//采用的思路和jdk中源码思路类似 如果不足则添加原来容量的一半 用移位来实现
public void ensureCapacity(int minCapacity) {
//如果elementData为空 最小扩展到10 如果不为空 最小扩展到0
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)?0:DEFAULT_CAPACITY;
if(minCapacity>minExpand){
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity){
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//确保明确的容器
private void ensureExplicitCapacity(int minCapacity){
//如果空间不足 进行扩充
if(minCapacity - elementData.length > 0){
grow(minCapacity);
}
}
//扩充容器
private void grow(int minCapacity){
int oldCapacity = elementData.length;
//扩充1.5倍 原来的数加上原来的数右移一位
int newCapacity = oldCapacity + (oldCapacity>>1);//为什么用右移而不是除以2?因为右移比除以二快
if(newCapacity - minCapacity < 0){
newCapacity = minCapacity;//选出大的一方
}
if(newCapacity - MAX_ARRAY_SIZE > 0){
newCapacity = hugeCapacity(minCapacity);//传入整型太大时候的处理
}
//经过确定扩容的大小后直接创建
elementData = Arrays.copyOf(elementData, newCapacity);
}
//对创建时传入数字太大的情形做出处理
private static int hugeCapacity(int minCapacity){
if(minCapacity < 0){
throw new OutOfMemoryError();
}
//如果1.5倍超出了MAX_ARRAY_SIZE则传入Integer.MAX_VALUE否则传入MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
public ArrayList(int initialSize){//带初始化容量大小的构造函数
super();
if(initialSize>0){//大于0直接创建
this.elementData = new Object[initialSize];
//原版jdk没有修改size 为什么不修改size?
}
else if(initialSize == 0){//等于0 直接赋空值
this.elementData = EMPTY_ELEMENTDATA;
}
else{//小于0直接抛出异常
throw new IllegalArgumentException("非法参数:"+initialSize);
}
}
public ArrayList() throws Exception{
this(DEFAULT_CAPACITY);//调用有参构造函数 默认大小为10
}
public ArrayList(Collection<? extends E> c){
elementData = c.toArray();//返回Object[]
size = elementData.length;
if(elementData.getClass() != Object[].class){//如果类型不同 则统一类型
elementData = Arrays.copyOf(elementData, size, Object[].class);//ArrayList的底层实现 数组 Arrays.copyOf() 赋值指定数组
}
}
@Override
public boolean add(E e) {
ensureCapacityInternal(size+1);//确保容器空间没问题
elementData[size++]=e;
return true;
}
}
在阅读jdk中ArrayList的相关源码实现后的一些感悟:
1、jdk中对边界条件的处理(对边界情况的处理很全面,但是源代码中可生成ArrayList的尺寸的上限为Integer.MAX_VALUE,然而其实达到不了那么大,最大只能是:Integer.MAX_SIZE-8,因为这个ArrayList对应的底层数组本身需要8byte来进行储存)
2、每当ArrayList需要add,但是容量又不够的时候就需要扩容,扩容之后尺寸为扩容前尺寸的1.5倍,即原来尺寸加上原来尺寸右移一位,之所以使用右移一位而不是除以2是因为右移比除以2快
3、当ArrayList的所需要的尺寸特别大的时候,如果不是事先开辟好对应的空间,而是一次一次的添加,则会在添加的过程中一次又一次地遇到需要调用ensureCapacityInternal();的情况,效率很低。所以在使用ListArray的时候最好在初始化的时候传入恰当的尺寸,或者ListArray本身提供了ensureCapacity(int minCapacity)方法,可以一次创建对应的空间,从而很好地提高效率
4、ArrayList底层实际上是通过数组实现,开辟新空间的关键在于Arrays.copyOf(elementData,size),所以对ArrayList的查找和替换的时间复杂度为O(1),然而插入删除的需要涉及到后面大量元素的移动,时间复杂度为O(n)
5、ArrayList的contains()、indexOf()方法以及包含调用了这些方法的containsAll()、retainAll()、removeAll()方法等底层实际上都是使用了继承自Object对象的equals()方法,所以如果想要这些方法能够正常运行,需要保证存储在ArrayList里的数据类型的equals()方法经过了重写,比较的是对象之间的值而不是对象之间的内存地址。