声明:我用的Java的版本是
Java9
版本。版本不同,源代码也有差异。ArrayList源码中大量使用了一下两个函数,首先搞清楚这两个函数的含义
System.arraycopy()
1
2
3
4
5
6
7
8
9
10
11
12 // 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义
/**
* 复制数组
* @param src 源数组
* @param srcPos 源数组中的起始位置
* @param dest 目标数组
* @param destPos 目标数组中的起始位置
* @param length 要复制的数组元素的数量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);举例:
1
2
3 int[] array = new int[]{10,23,24,15,12,45,23,56,12};
System.arraycopy(array,5,array,4,4);
System.out.println(Arrays.toString(array));//[10, 23, 24, 15, 45, 23, 56, 12, 12]含义:将
array
数组的元素,从下标5
为起始,4个元素,赋给array
数组从4
索引开始的位置上。这个实现的是一个数组删除操作。
Arrays.copyOf()
1
2
3
4
5
6
7
8 public static int[] copyOf(int[] original, int newLength) {
// 申请一个新的数组
int[] copy = new int[newLength];
// 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}含义是:传入原始数组,复制出一个
newLength
长度的数组。
ArrayList
底层实现是数组。
1 | public class ArrayList<E> extends AbstractList<E> |
ArrayList
继承于 AbstractList
,实现了 List
, RandomAccess
, Cloneable
, **java.io.Serializable
**。
RandomAccess
是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在ArrayList
中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。ArrayList
实现了Cloneable
接口 ,即覆盖了函数clone()
,能被克隆。ArrayList
实现了java.io.Serializable
接口,这意味着ArrayList
支持序列化,能通过序列化去传输。
ArrayList成员变量
1 | private static final int DEFAULT_CAPACITY = 10; |
DEFAULT_CAPACITY
定义了ArrayList
的默认容量为 10。MAX_ARRAY_SIZE
要分配的数组的最大大小。EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
都定义了一个空数组。EMPTY_ELEMENTDATA = {}
:空数组(用于空实例)DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
:- 用于默认大小空实例的共享空数组实例
- 把它从
EMPTY_ELEMENTDATA
数组中区分出来,用来知道在添加第一个元素时容量需要增加多少。
elementData
是ArrayList
的底层实现,是一个Object数组。size
表明ArrayList
当前存有多少元素。
ArrayList构造方法
1 | public ArrayList(int initialCapacity) { |
ArrayList()
无参构造,将之前已经初始化的默认实例[
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
]赋值给elementData
初始时容量为0, 当添加第一个元素的时候数组容量才变成10
ArrayList(int initialCapacity)
- 带初始容量参数的构造函数(用户可以在创建
ArrayList
对象时自己指定集合的初始大小)- 如果
initialCapacity
大于0,就new
一个Object数组。 - 如果
initialCapacity
等于0,将被赋值一个空实例EMPTY_ELEMENTDATA
- 如果
initialCapacity
小于0,抛出IllegalArgumentException
异常。
- 如果
- 带初始容量参数的构造函数(用户可以在创建
ArrayList(Collection<? extends E> c)
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
首先将传入的集合转化为数组,赋值给
elementData
如果
elementData
的长度不为0如果
elementData
的类型不是Object[]
类型。c.toArray
可能返回的不是Object类型的数组将原来不是Object类型的
elementData
数组的内容,赋值给新的Object类型的elementData
数组
如果
elementData
的长度为0elementData
被赋值为一个空实例EMPTY_ELEMENTDATA
ArrayList常用方法
ArrayList
的常用方法有:
get()
add()
set()
remove()
get()
方法
1 | public E get(int index) { |
首先他会检查这个索引是否合法
然后调用
elementData(index)
方法,返回元素1
2
3E elementData(int index) {
return (E) elementData[index];//进行了强制转换
}
add()
方法
1 | public boolean add(E e) { |
调用了add(E e, Object[] elementData, int s)
方法,这是一个私有方法
1 | private void add(E e, Object[] elementData, int s) { |
这个是一个辅助方法,从add(E)中分离出来,以保持方法字节码大小小于35 。
E e
:表示要插入的元素
Object[] elementData
:表示要插入的数组
int s
表示要插入的位置
- 首先判断,
elementData
是否已满,如果已经满了,再调用grow()
方法进行扩容。 - 然后就是数组赋值操作了。
set()
方法
1 | public E set(int index, E element) { |
- 首先他会检查这个索引是否合法
- 然后获取该索引位置原始元素
- 该索引位置被新元素所取代
- 返回旧元素
remove()
方法
1 | public E remove(int index) { |
需要将删除点之后的元素向前移动一个位置。
需要注意的是:为了让GC
起作用,必须将最后一个元素置为null。
对象能否被GC
的依据是是否还有引用指向它,上面代码中如果不手动赋null
值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。
ArrayList的扩容策略
一步一步的看ArrayList
的扩容策略,扩容函数是grow()
1 | private Object[] grow() { |
内部又调用了grow(size + 1)
。
1 | private Object[] grow(int minCapacity) { |
增加容量以确保它至少能容纳最小容量参数指定的元素数量。
参数:minCapacity
—期望的最小容量
返回一个从原始数组elementData
,复制一个容量为newCapacity(minCapacity)
是数组。从而进行扩容。
这个newCapacity(minCapacity)
返回新的容量。newCapacity()
是扩容的核心。
1 | private int newCapacity(int minCapacity) { |
int newCapacity = oldCapacity + (oldCapacity >> 1)
表明新的容量是旧的容量的1.5倍。
如果新的容量小于等于期望的最小容量:
如果此时
ArrayList
的底层数组elementData
是默认的空实例DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:则
ArrayList
至少扩容为默认容量10。Math.max(10, minCapacity)
如果不是默认的空实例,但期望的最小容量小于0,就抛出异常。
如果以上两种情况都不符合,就返回期望的最小容量。然后
ArrayList
就会扩容为期望的最小容量。
如果新的容量大于期望的最小容量:
检查这新的容量,是否小于,允许扩容的最大容量【
Integer.MAX_VALUE - 8
】。如果小于,则返回新的容量,即
ArrayList
将扩容为旧容量的1.5倍。如果大于,则调用
hugeCapacity(minCapacity)
函数,在对期望的最小容量进行进步判断。1
2
3
4
5
6
7private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}- 如果期望的最小容量大于允许扩容的最大容量,则
ArrayList
扩容为int整数的最大值。 - 否则
ArrayList
扩容为允许扩容的最大容量
- 如果期望的最小容量大于允许扩容的最大容量,则
扩容策略总结:
- 默认容量:10
- 最大扩容容量:
Integer.MAX_VALUE - 8 = 2147483639
- 最小期望容量:当前容量+1
当添加第一个元素时,任何带有
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的空数组列表将被扩展为DEFAULT_CAPACITY
。即
如果
ArrayList
使用无参构造初始化,初次添加元素,会扩容为10。其他情况,当需要扩容时
minCapacity
最小期望容量为当前容量+1。如果
minCapacity
<10,就会扩容为最小期望容量如果
minCapacity
>10,新容量为原始容量的1.5倍。- 如果这个新的容量小于允许最大容量,则扩容为原始容量的1.5倍。
- 如果这个新的容量大于允许最大容量
- 最小期望容量小于允许最大容量,则扩容为,允许的最大容量
- 最小期望容量小于允许最大容量,则扩容为,int整型的最大值。
并发修改异常
并发修改异常是发生在ArrayList
使用迭代器遍历过程中,对ArrayList
进行修改而发生的异常。
比如:
1 | public class Main { |
引发异常ConcurrentModificationException
。
源码分析
我们知道增强式for循环,它本质是使用迭代器。
ArrayList
继承了AbstractList
抽象类,它里面定义了一个变量modCount
。
1 | public abstract class A AbstractList<E>{ |
modCount
表示预期修改次数。
1 | public class ArrayList<E> extends AbstractList<E> implements List<E>{ |
当我们通过iterator()
方法获取迭代器,expectedModCount = modCount
此时预期修改次数和实际修改次数相等。
当调用迭代器的next()
方法时,每次调用都会检查预期修改次数和实际修改次数是否相等,如果不相等就会抛出异常。
而,我们调用add()
方法时,modCount++
,实际修改次数加一,但预期修改次数不变。等下次调用next()的时候,就会引发并发修改异常。
除了add()
方法,一下方法,在迭代器遍历过程中调用也会产生并发修改异常,因为它们内部也同样modCount++
:
trimToSize()
ensureCapacity()
remove()
clear()
addAll()
removeRange()
batchRemove()
…….
附:查看集合容量的方法
1 | //查看ArrayList集合容量方法 -- 反射 |
__END__