ArrayList源码简单分析
一. ArrayList简介
ArrayList是Java中使用最频繁的集合之一,实现了List接口。它的底层基于Array数组实现容器容量自动变化,可以存入任何值,包括null。ArrayList还是实现了RandomAccess, Cloneable, Serializable接口,所以它还支持随机访问/克隆和序列化。
二. 成员属性
2.1 DEFAULT_CAPACITY
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
- 默认集合容量初始大小
- 第一次向集合(无参构造)中放入数据时,会根据DEFAULT_CAPACITY对集合的容量进行初始化
2.2 EMPTY_ELEMENTDATA
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
- 空数组对象
- 出现在有参构造方法中
2.3 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- 空数组对象
- 出现在无参构造方法中
- 该属性和EMPTY_ELEMENTDATA进行区分,当第一次添加元素时集合该如何进行扩容。
2.4 elementData
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData;
- 数组对象
- 添加进来的元素会被储存在该buffer数组中
- ArrayList集合的容量是该数组的长度
- 当elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,第一次添加元素,集合的容量将会被扩展为DEFAULT_CAPACITY = 10。
2.5 size
/**
* The size of the ArrayList (the number of elements it contains).
* @serial
*/
private int size;
- 集合实际包含元素的数量
2.6 注意
虽然DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的值相等都是空对象{},但它们的作用相当于标识符。
如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那么就意味着ArrayList集合是通过无参构造函数创建的。
相反,elementData == EMPTY_ELEMENTDATA,ArrayList集合是通过两个有参构造函数中的任意一个创建的。
这些都会在主要操作方法中发挥作用,之后会看到
三. 构造函数
3.1 无参构造
- 使用默认的容量大小来构造一个空的列表
- 具体的生成会在第一次添加元素时体现(调用add方法)
3.2 有参构造
3.2.1 initialCapacity
- 通过指定的容量大小来构造空列表(数组对象)
- initialCapacity > 0,直接new一个Object类型的数组
- initialCapacity == 0,将默认的空对象赋值给elementData
3.2.2 Collection
- 将集合转换为数组,elementData引用指向数组
- 数组elementData的长度赋值给size,判断size是否不等于零
- size == 0,数组elementData的值等于空数组
- size != 0,先判断elementData数组的类型是否为Object (c.toArray可能出错,并没有返回Object[])
- 如果数组elementData的类型不是object,那么就复制一个全新的类型为Object的数组并赋值给elementData
四. 主要操作方法
4.1 add
- 将元素添加到集合
- minCapacity最小容量,插入第一个元素,所以集合的容量最小为1
- 调用ensureCapacityInternal方法(作用:每次调用add方法时都会检查集合的容量是否够用)
- 在ensureCapacityInternal方法中
- 直接调用了ensureExplicitCapacity方法,参数由calculateCapacity方法返回值提供。
- calculateCapacity方法,判断集合的构建是无参构造还是有参构造。如果是无参构造,返回DEFAULT_CAPACITY,所以集合的初始容量为10;如果是有参构造,返回minCapacity,集合的初始容量是根据有参构造传递的参数决定的。
- ensureExplicitCapacity方法
- modCount操作数+1
- minCapacity - elementData.length > 0,意味着此时的集合的容量已经没办法添加元素了,所以接下来要调用grow方法来给集合扩容。
- 调用grow方法
- oldCapacity + (oldCapacity >> 1) 相当于oldCapacity * 1.5,将集合原来的容量扩大到原来的1.5倍
- 扩容之后也不一定会适合,有可能太大了也有可能太小了,所以就有了最后两个if语句。
- 如果扩容之后太小了,那么就直接将我们需要的最小容量赋值给newCapacity
- 如果扩容之后太大了,那么就将MAX_ARRAY_SIZE或者是Integer.MAX_VALUE赋值给newCapacity
- 然后将原数组中的数据复制到大小为newCapacity的新数组中,并将新数组赋值给elementData。
4.1.1 其它的一些add方法
- 以上三种方法和前面讲的add方法都差不多。
- 在插入数据之前都要先判断当前集合的容量是否足够,如果不够就扩容。
- 之后就是通过System.arraycopy方法将插入的数据复制到elementData数组中。
4.1.2 System.arraycopy
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length)
- 从指定的源数组复制数组,其实就是将一个指定的数组中的数据复制到另一个数组中去
- 参数
- src 源数组,即被指定的数组
- srcPos 开始的位置,即被指定数组的数据从那个位置开始被复制
- dest 目标数组,即被复制的数据最后被存放的数组
- destPos 开始的位置,即被复制的数据最后被存放到数组具体位置
- length 被复制的数组的元素长度。
4.2 remove
- 当我们调用remove(int index)方法时,首先要先检查下标是否越界,然后操作数加一,取出将要被溢出的值。numMoved 为要移动的元素的位数,如果numMoved==0,则说明被移除的元素是最后一位,该元素后面的所有元素不需要移动,直接将最后一位置为null,后面的事就交给GC来处理。如果numMoved>0,那么就将被移除元素后面所有的元素向前移动numMoved个位置,然后再将最后numMoved个位置的值设为null。
- 当我们调用remove(Object o)方法时,会根据被移除的对象是否为null进行不同的处理,其实都差不多。先根据被删除对象找到该对象的位置,之后就跟remove(int index)方法一样了。
4.3 get
- get(int index)方法最简单,当代用get方法时,先检查index下标是否越界,如果越界就抛异常。否则,就直接根据index下标从数组elementData中,取出该位置的值,并返回。
五. 感谢
- 本篇笔记是我看了微信公众号好好学java中的文章写出来的,内容的当然没有大佬写的那么详细和有条理,所以大家如果有兴趣可以去订阅一下。