一、什么是ArrayList
ArrayList是实现了List接口的一个集合
二、ArrayList底层是什么
在对ArrayList的源码分析前,我们首先要了解一点就是ArrayList是什么。这里我们可以看一下ArrayList的属性
transient Object[] elementData;// transient: 该属性不会被序列化
我们看这个属性我们可以看到其实ArrayList的底层就是一个Object的数组。
三、ArrayList的源码解析
先从构造函数开始
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final Object[] EMPTY_ELEMENTDATA = {};
// 这是ArrayList的无参构造
public ArrayList() {
// 这里就是将空数组赋值给elementData
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参参数为一个整形
public ArrayList(int initialCapacity) {
// 参数是否大于0
if (initialCapacity > 0) {
// 参数大于0就为数组赋初始长度,也可以理解为集合大小
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 等于0就赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 这个用的很少
public ArrayList(Collection<? extends E> c) {
// 将传入的集合转为数组
elementData = c.toArray();
// 看传入的集合是否为空集合也就是空数组
if ((size = elementData.length) != 0) {
// 不是这里就判断返回的是不是一个Object
if (elementData.getClass() != Object[].class)
// 如果不是Object就用copyOf生成一个新的数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 是就赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
我们再看看常用的几个方法
// 这个size指实际集合里有多少个数据
private int size;
// 获取ArrayList的当前有多少个数据
public int size() {
return size;
}
// 判断ArrayList集合里有没有数据
public boolean isEmpty() {
return size == 0;
}
// 获取该索引下的数据
public E get(int index) {
// 这个就是个抛出异常的
rangeCheck(index);
return elementData(index);
}
// 修改当前索引的指并返回原先的指
public E set(int index, E element) {
rangeCheck(index);
// 获取以前的指
E oldValue = elementData(index);
// 传递新指
elementData[index] = element;
return oldValue;
}
// 判断这个指是否在这个集合中
public int indexOf(Object o) {
// 判断查询的指是否为空
if (o == null) {
for (int i = 0; i < size; i++)
// 遍历查询所有数据返回第一个为空数据的索引
if (elementData[i]==null)
return i;
} else {
// 遍历查询所有数据返回第一个等于参数值的索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
四、ArrayList扩容源码分析
ArrayList的扩容首先要从添加开始说起:
// 这是向集合中添加数据的方法
public boolean add(E e) {
// ensureCapacityInternal()这个方法就是扩容的开始
ensureCapacityInternal(size + 1);
// 这就是一个正常的数组赋值方法
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 这个方法首先我们要看当前的数组是不是一个空数组
// 如果这不是一个空数组就不会执行判断体里的代码
// 就比如public ArrayList(int initialCapacity)
// 这个有参构造创建的就不是一个空数组因为有初始长度了
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是空数组使用Math.max比较传入的参数和默认的参数那个大
// DEFAULT_CAPACITY = 10,minCapacity=0
// 所以在初始化是一个空数组的时候默认的长度为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 继续进行下一步分析
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// modCount修改次数
modCount++;
// 在这里进行判断你是不是需要进行扩容
// minCapacity当前的集合大小
// elementData.length初始的集合大小
// 当你进行添加时发现当前的集合大小大于初始的集合大小
// 那么就需要进行扩容
if (minCapacity - elementData.length > 0)
// 扩容方法
grow(minCapacity);
}
private void grow(int minCapacity) {
/*
这里有两种说法:
第一种就是初始化时不设置长度
第二种就是初始化时设置初始长度
*/
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 这个不进行说明,这个判断基本就是false
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// Arrays,copyOf(原始数组,新数组大小);
// Arrays.copyOf方法是返回一个新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
先说第一种就是初始化时不设置长度:
- 初始化时没有设置长度所以就是一个空数组因此elementData.length就为0
- 初始长度为0导致扩容以后的长度还是为0,oldCapacity + (oldCapacity >> 1)通过这段代码我们可以得知扩容后的长度为扩容前的1.5倍 (>> 是位运算)
- newCapacity - minCapacity:在这里newCapacity = 0而minCapacity = 10,(为什么等于10前面说过了)所以就把minCapacity的值赋给了newCapacity:newCapacity = minCapacity;
- 最后通过Arrays.copyOf生成一个新的数组赋给了elementData
第二种初始化时设置长度
- 初始化时设置了长度因此elementData.length就是初始化的长度
- 因此后面的扩容也就是根据这个初始长度的1.5倍进行扩容
- 在这里newCapacity就不是0了是初始长度的1.5倍
- 后面都是一样的了
五、结语
其实通过源码的分析我们可以看出ArrayList集合的难度其实并不大,这里最主要的也就是ArrayList的扩容机制了,至于ArrayList什么时候扩容就是添加的实际数量大于默认或者初始的长度时,就会开始进行扩容。