后端源码学习计划之ArrayList
ArrayList源码学习
在学习源码前,我们先来看看其中几个重要的成员变量
//序列化id,用于在序列化和反序列化过程中进行核验的一个版本号
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 也是一个空数组
* 和上面的区别
* 当无参构造时,Obeject数组elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
* 当有参构造时,如果给定初始容量为0,或者传入集合为空集合(不是null),那么,将空数组EMPTY_ELEMENTDATA赋给elementData;
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* ArrayList的缓冲区
*
* 注意:
* 被transient修饰的变量默认不参与序列化和反序列化。
* 当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
*ArrayList的大小
* @serial
*/
private int size;
ArrayList的构造函数
通过对ArrayList源码,我们可以明显看见ArrayList一共有3个构造方法
- 无参构造函数
public ArrayList() {
//初始化一个空的数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 类型为int的有参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果参数大于0,就初始化一个长度为initialCapacity的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果输入参数等于0,就初始化一个空的数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果输入参数小于0,表示初始化不合法,直接抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 传入集合的构造函数
public ArrayList(Collection<? extends E> c) {
//首先将传入的集合转换为数组
Object[] a = c.toArray();
//判断传入的集合是否是一个空集合
if ((size = a.length) != 0) {
//不是空集合,判断是否是Object对象数组
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
//不是Object数组,进行一次拷贝转换为Object数组
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 如果是空集合,初始化一个空数组
elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的add()方法
public boolean add(E e) {
//新增时,将当前size+1作为参数调用ensureCapacityInternal方法,判断数组是否存的下
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
新增时,首先调用ensureCapacityInternal方法,传入参数为当前集合大小+1(即 size+1)。size+1表示elementData需要的最小长度。
//判断当前集合能不能放下即将被添加的元素,如果不能,扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
该方法只做了一件事,判断能不能放下即将被添加的元素,如果不能,扩容
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果集合还没有被初始化,则返回初始容量和所需容量的较大的一个。初始容量是10,可知当我们第一次调用add()方法时,calculateCapacity返回的是10。如果已经初始化过了,直接返回minCapacity。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
该方法的作用是:获取了当前集合需要的最小长度
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断是否需要进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureExplicitCapacity方法判断了当前集合是否需要进行扩容
private void grow(int minCapacity) {
// 当前容量赋值给oldCapacity
int oldCapacity = elementData.length;
//新容量为老容量的1.5倍。oldCapacity >> 1相当于除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后还不够,则容量为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于MAX_ARRAY_SIZE,调用hugeCapacity()方法
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
grow方法是集合的扩容方法,可以看出扩容量是原数组的1.5倍。
在该方法中还调用了一个hugeCapacity方法。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
hugeCapacity方法只在扩容时可能被调用,它的逻辑很简单,先做了个简单的判断,之后执行了一个三元表达式,如果扩容前所需最小容量大于数组最大长度,返回Integer的最大值,否则返回MAX_ARRAY_SIZE。MAX_ARRAY_SIZE是集合设定的默认最大扩容长度,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8=2^31-8
总结
ArrayList的add方法做了什么事呢?第一次进行add()操作时,会判断是否初始化,如果没有初始化,会将默认的空数组扩容为长度为10的数组,然后将数据放入新数组中。
注意:《阿里巴巴Java开发手册》里面建议初始化集合时尽量显示的指定集合大小,原因:1、如果未指定集合大小,在add时会进行扩容操作,会降低性能。
2、节约内存。需要多长的数组就开多长,避免内存浪费
添加到指定位置 add(int index, E element)
public void add(int index, E element) {
//判断index是否合法,即判断是否小于零,是否大于当前size
rangeCheckForAdd(index);
//和add()中的一样
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index及其后面的元素向后移一位,空出index
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//插入元素
elementData[index] = element;
//当前长度+1
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
添加所有元素addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
//将集合转为对象数组
Object[] a = c.toArray();
//获取数组长度
int numNew = a.length;
//确保对象数组elementData有足够的容量,可以将新加入的a对象数组加进去
ensureCapacityInternal(size + numNew); // Increments modCount
//将数组a拷贝到element中
System.arraycopy(a, 0, elementData, size, numNew);
//现在element长度为size+numNew
size += numNew;
//若加入的是空集合则返回false
return numNew != 0;
}
添加所有元素addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
//判断index是否合法,即判断是否小于零,是否大于当前size
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//确定数组需要移动的长度
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
总结
1、ArrayList的底层是数组,初始容量是10,当数组满了之后,继续添加元素时,会扩容到原来的1.5倍+1。
2、ArrayList保存了一个modCount属性,修改集合的操作都会让其自增。如果在遍历的时候modCount被修改,则会抛出异常,产生fail-fast事件(即当我们在调用add()、remove()这些修改集合的方法时,都会修改一个属性modCount。而我们在遍历集合时,首先会保存一份modCount,然后在遍历时,将保存的modCount和成员变量modCount对比,如果不一样,说明被集合已经被修改,抛出ConcurrentModificationException,产生fail-fast事件)。
3、ArrayList内部还维护了一个size属性,它是用来记录数组中的实际元素个数。
size,modCount,elementData这些成员变量,都注定了ArrayList线程不安全。