ArrayList源码解析
简介
ArrayList
是Java
集合框架中非常常用的一种数据结构。继承自AbstractList
,实现了List
接口。底层基于数组来实现动态容量大小的控制,允许null
值的存在。同时还实现了RandomAccess
、Cloneable
、Serializable
接口,支持快速访问、复制、序列化操作。
了解数组
数组简单来说就是将所有的数据排成一排存放在系统分配的一个内存块上,通过使用特定元素的索引作为数组的下标,可以在常数时间内访问数组元素的这么一个结构;
数组优缺点
优点
- 简单方便已使用
- 访问元素快
缺点
- 大小固定:数组的大小是静态的,在使用前必须确定好数组的大小
- 分配一个连续空间块:数组初始分配空间时,有时候无法分配能存储整个数组的内存空间(当数组规模太大时);
- **基于位置的插入操作实现复杂:**如果要在数组中的给定位置插入元素,那么可能就会需要移动存储在数组中的其他元素,这样才能腾出指定的位置来放插入的新元素;而如果在数组的开始位置插入元素,那么这样的移动操作开销就会很大。
ArrayList解析
我们提到数组的特点是大小固定,ArrayList
的底层是基于数组来实现容量的大小动态变化的,那我们一起来结合源码看看,是如何实现这一功能的。
我们找到java.util.ArrayList
包查看代码。并通过注释的方式,一起来揭开面纱。
1、成员变量
// 默认的容量大小
private static final int DEFAULT_CAPACITY = 10;
// 空数组对象Object
private static final Object[] EMPTY_ELEMENTDATA = {
};
// 有一个空数据对象Object
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
// 默认修饰且不参与序列化的 数组对象,也是实际存储数据的地方
transient Object[] elementData; // non-private to simplify nested class access
// 实际大小容量
private int size;
我们发现有两个一样的空数组对象,为什么要用两个呢?源代码中也进行来解释 We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
也就是说,这是一个共享的空数组实例,通过与默认的空数组区分开,好处是,添加元素时知道该 elementData
从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
在AbstractList
父类中还有一个变量
protected transient int modCount = 0;
用来记录对List的操作次数。作用在使用Iterator
时,防止在迭代过程中集合被修改。
2、构造函数
无参数构造
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
默认的无参数构造中直接给elementData
赋值来一个空的数据。但我们看到注释上说初始容量为10的数组,这好像不太对啊。其实这只是一个延后的操作,当第一次添加数据进去时,容量会扩容到10,好处是避免无用的ArrayList
的出现。具体的实现我们接着往后看。
指定初始容量构造
public ArrayList(int initialCapacity) {
// 指定的容量大于0,直接new一个指定容量大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 指定容量等于0。那就赋值空数组。
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 无效容量大小
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
这个还是比较清晰的,根据指定容量初始化一个数组。加入了容量大小的判断操作。
指定Collection集合构造
public ArrayList(Collection<? extends E> c) {
// 首先转成数组
Object[] a = c.toArray();
// 有效大小的数组哈
if ((size = a.length) != 0) {
// 这里做了优化,如果也是一个ArrayList集合直接赋值即可
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 其他的类型就做拷贝啦
elementData = Arrays