ArrayList简单介绍
ArrayList是一个底层用数组实现的集合,支持随机访问,元素有序并且可以重复。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
①实现RandomAccess接口
这是一个标记接口,一般此标记用于List实现,表明支持快速的随机访问。
②实现Cloneable接口
也是一个标记接口,接口内无任何方法体和常量的声明,如果想克隆对象,必须实现Cloneable接口,表明该类是可以被克隆的。
③实现Serializable接口
也是标记接口,表示能被序列化。
④实现List接口
里面很多必须实现的方法,例如:size(),isEmpty().....
研究第一步:
先直接new一个ArrayList,向集合添加11个元素,为什么是11个等下解析
public class TestArrayList {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
stringList.add("4");
stringList.add("5");
stringList.add("6");
stringList.add("7");
stringList.add("8");
stringList.add("9");
stringList.add("10");
stringList.add("11");
}
}
从List<String> stringList = new ArrayList<>();可以看出我调用的是ArrayList的无参构造方法
研究第二步:
打开ArrayList源码,查看字段属性
//默认容量10
private static final int DEFAULT_CAPACITY = 10;
// 空实例使用的空数组实例对象
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容量的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正存放集合数据的数组
transient Object[] elementData;
//真实集合的大小(集合里面元素的个数)
private int size;
其实默认容量不是调用构造方法的时候就为集合创建了的,这个时候我们要先看看构造函数,构造函数有三种
第一种无参构造,就是我们上面测试代码要调用的构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当我们调用无参构造函数的时候,就将真正存放集合数据的数组设置成了DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA就是一个Object类型的空数组,里面什么都没有,也就是size=0。
第二种构造方法:
//initialCapacity代表你想要创建的集合的大小
public ArrayList(int initialCapacity) {
//先判断这个大小是否大于0
if (initialCapacity > 0) {
//大于0的时候就直接创建你想要的大小的Object类型的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//等于0时就还是创建一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//大小是负数则抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
第三种构造方法:
//这是将已有的集合复制到ArrayList中去
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
在调用完无参构造函数之后,我们知道的是,我们通过new ArrayList<>()创建了一个空的数组,里面什么都没有,size=0,接着我们就要往该集合里面添加元素了,我们执行了add(E e)方法(添加方法有两个,这里是调用了一个参数的我们就直接解析一个参数的add方法吧)。
研究第三步:
我们查看源代码add(E e)方法的实现
public boolean add(E e) {
//检测是否需要扩容
ensureCapacityInternal(size + 1);
//赋值操作,因为已经执行完扩容了的,长度加了1
/**
* elementData[size++] = e;
* 相当于elementData[size] = e;
* size++;
* */
elementData[size++] = e;
return true;
}
add方法里面调用了ensureCapacityInternal()方法,因为我们之前调用无参构造的时候创建的是空数组,size=0,所以现在ensureCapacityInternal方法的参数值为0+1=1.
接着查看ensureCapacityInternal方法的实现
//扩容入口
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里elementData就是空数组(因为上面调用无参构造的时候给elementData赋值就是空数组), minCapacity就是1(是前面的size+1得到的结果)
这里继续调用calculateCapacity()方法,查看calculateCapacity方法的实现
//计算容量应该是多少
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//当前的数组是否是默认容量的数组(在调用无参构造函数的时候创建的就是默认容量的数组)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//把默认容量10与minCapacity比较返回较大的(minCapacity就是当前数组的长度+1了的)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//不是默认容量数组那么就直接返回minCapacity
return minCapacity;
}
因为我传过来的elementData就是空数组,minCapacity就是1,所以在if判断的时候就将DEFAULT_CAPACITY值给返回了,这里才动用到了默认值10.所以这时候ensureExplicitCapacity(calculateCapacity(elementData, minCapacity))就等价于ensureExplicitCapacity(10)
查看ensureExplicitCapacity方法的实现:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//最小需要的容量大于当前数组的大小的时候就要进行扩容了
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这函数里面的minCapacity=10,因为elementData还是个空数组,长度为0所以在if判断的时候就要去调用grow(10)方法了,调用grow方法就是进行真正的扩容了
查看grow方法的实现
private void grow(int minCapacity) {
//1、先记录原来数组的容量
int oldCapacity = elementData.length;
//2、右移一位就相当于除2,那么新的容量=旧的容量+旧的容量/2
//也就是新的容量是原来的1.5倍了
int newCapacity = oldCapacity + (oldCapacity >> 1);
//3、新的容量还是小于你最小的容量的时候就是说明你扩容之后还是不够
if (newCapacity - minCapacity < 0)
//4、直接把你最小的容量给新的容量
newCapacity = minCapacity;
//5、如果你扩容的容量已经大于最大值了
if (newCapacity - MAX_ARRAY_SIZE > 0)
//6、给新容量一个最大值
newCapacity = hugeCapacity(minCapacity);
//7、把原来的数据拷贝,把新的容量给新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
在1时,因为目前elementData是空的,所以oldCapacity=0
在2时:newCapacity 还是等于0
在3时:0-10 < 0,进入4,所以新的容量现在被赋值为10了
(当程序执行5的时候就数组大小不断扩容之后比最大值还要大的时候才执行的)
紧接着执行7:将这个空数组进行拷贝到一个容量为10的数组中去了,所以执行到这里的时候真实的数组现在容量是10了,只要数组的数据还没有达到10的时候都不需要再扩容了,这也就是为什么一开始的时候是添加了11个数据,就是因为第11个数据想要存的时候,这时候又要去扩容了。
执行完7之后elementData就是一个容量为10的数组了,这时候grow方法执行完成了,回到ensureExplicitCapacity方法,他也执行完了,再回到ensureCapacityInternal方法,也执行完了,最后回到了add方法,执行elementData[size++] = e;这个时候才将元素添加进来,添加了一个元素size就变成了1,最后返回true表示添加成功。
对于ArrayList集合添加元素最后总结一下:
①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
④、第 Integer.MAX_VALUE - 8 = 2147483639,然后 2147483639%1.5=1431655759(这个数是要进行扩容) 次添加元素,为了防止溢出,此时会直接创建一个 1431655759+1 大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。
⑤、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
⑥、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:能向集合中添加 null 的,因为数组可以有 null 值存在。