按照从构造方法->常用API(增、删、改、查)的顺序来阅读源码,并做简要分析。
一. 定义
1 | public class ArrayList<E> extends AbstractList<E> |
介绍
- 继承了AbstractList,实现了List
它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能 - 实现了RandmoAccess接口
即提供了随机访问功能 - 实现了Cloneable接口
即覆盖了函数clone(),能被克隆 - 实现java.io.Serializable接口
支持序列化,能通过序列化去传输
优势
- 比数组的优势
ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长 - 比Vector的优势
和Vector不同,ArrayList中的操作不是线程安全的,但是开销小,在单线程环境性能优于Vector
二 .属性
底层用数组来保存数据
1 | private transient Object[] elementData; |
- size :当前数组长度
- elementData:存放数组的对象的数组
- modCount:ArrayList集合的修改次数
基于数组实现,保存元素的数组使用 transient
修饰,该关键字声明数组默认不会被序列化。这是 ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
三. 构造函数
1)无参构造函数–ArrayList()
Constructs an empty list with an initial capacity of ten.
1 | public ArrayList() { |
1 | private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; |
Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.
1 | private static final int DEFAULT_CAPACITY = 10; |
此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.(具体过程看下文的add())
2)带容量大小的构造函数–ArrayList(int initialCapacity)
传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常
Constructs an empty list with the specified initial capacity.
1 | public ArrayList(int initialCapacity) { |
3)带Collection对象的构造函数–ArrayList(Collection<? extends E> c)
构造一个包含指定元素的list,这些元素的是按照Collection的迭代器返回的顺序排列的
- 将collection对象转换成数组,然后将数组的地址的赋给elementData
- 更新size的值,同时判断size的大小
2.1 如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
2.2 如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容copy到elementData中Constructs a list containing the elements of the specified collection, in the order they are returned by the collection’s iterator.
1 | public ArrayList(Collection<? extends E> c) { |
1 | private static final Object[] EMPTY_ELEMENTDATA = {}; |
Arrays.copyOf()方法
1 | public static <T> T[] copyOf(T[] original, int newLength) { |
可以看到,他是在方法内部,新建了一个相同类型的数组,然后调用系统方法
1 | System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength)); |
original - 源数组。
0 - 源数组中的起始位置。
copy - 目标数组。
0 - 目标数据中的起始位置。
Math.min(original.length, newLength) - 要复制的数组元素的数量。
四. 增加—add addAll
添加元素时使用 ensureCapacity()
方法来保证容量足够,如果不够时,需要使用 grow()
方法进行扩容,使得新容量为旧容量的 1.5 倍oldCapacity + (oldCapacity >> 1)
扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
添加一个元素(在末尾)–add(E e)
add(E e)
Appends the specified element to the end of this list.
1 | public boolean add(E e) { |
ensureCapacityInternal(int minCapacity)
- 确保添加的元素有地方存储
- 当第一次添加元素的时候this.size+1 的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10
1 | private void ensureCapacityInternal(int minCapacity) { |
calculateCapacity(Object[] elementData, int minCapacity)
1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
1 | private static final int DEFAULT_CAPACITY = 10; |
ensureExplicitCapacity()
经过前面的一顿操作,minCapacity 是
- DEFAULT_CAPACITY(10)
当第一次添加时 - size + 1
当不是第一次添加时
1 | private void ensureExplicitCapacity(int minCapacity) { |
grow()
Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument
1 | private void grow(int minCapacity) { |
在指定位置添加一个元素–add(int index, E element)
Inserts the specified element at the specified position in this list.
- 确保有足够的容量之后
- 使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位
- 将新的数据内容存放到数组的指定位置(index)上
1 | public void add(int index, E element) { |
关于arraycopy()
1 | public static void (Object src,int srcPos, |
参数 | 意义 |
---|---|
src | 源数组 |
srcPos | 源数组要复制的起始位置 |
dest | 目的数组 |
destPos | 目的数组放置的起始位置 |
length | 复制的长度 |
- src和dest都必须是同类型或者可以进行转换类型的数组
- 可以实现自己到自己复制
其中rangeCheckForAdd是为了防止越界:
1 | private void rangeCheckForAdd(int index) { |
增加一个集合–addAll(Collection<? extends E> c)
Appends all of the elements in the specified collection to the end of this list, in the order that they are returned by the specified collection’s Iterator.
1 | public boolean addAll(Collection<? extends E> c) { |
在指定位置,增加一个集合元素–addAll(int index, Collection<? extends E> c)
Inserts all of the elements in the specified collection into this list, starting at the specified position.
1 | public boolean addAll(int index, Collection<? extends E> c) { |
关于增 的总结:
add、addAll。
先判断是否越界,是否需要扩容。
如果扩容, 就复制数组。
然后设置对应下标元素值。
值得注意的是:
1 如果需要扩容的话,默认扩容一半。如果扩容一半不够,就用目标的size作为扩容后的容量。
2 在扩容成功后,会修改modCount
五. 删除—remove
根据索引位置删除元素–remove(int index)
Removes the element at the specified position in this list.
1 | public E remove(int index) { |
根据元素内容删除,只删除匹配的第一个–remove(Object o)
Removes the first occurrence of the specified element from this list, if it is present. If the list does not contain the element, it is unchanged.
循环遍历所有对象,得到对象所在索引位置,然后调用fastRemove方法,执行remove操作
- null值要用==比较
1 | public boolean remove(Object o) { |
fastRemove()
Private remove method that skips bounds checking and does not return the value removed.
定位到需要remove的元素索引,先将index后面的元素往前面移动一位(调用System.arraycooy实现),然后将最后一个元素置空。
1 | private void fastRemove(int index) { |
数组越界检查
1 | private void rangeCheck(int index) { |
1 | private String outOfBoundsMsg(int index) { |
六. 查找—get
查找指定位置上的元素
Returns the element at the specified position in this list.
1 | public E get( int index) { |
七. 更新—set
将指定位置的元素更新为新元素
Replaces the element at the specified position in this list with the specified element.
- 注意返回值是oldValue
1 | public E set(int index, E element) { |
八. 判断
包含–contains(Object o)
调用indexOf方法,遍历数组中的每一个元素作对比,如果找到对于的元素,则返回true,没有找到则返回false
1 | public boolean contains(Object o) { |
1 | public int indexOf(Object o) { |
空–isEmpty()
1 | public boolean isEmpty() { |
九. ArrayList遍历方式
ArrayList支持3种遍历方式
- 通过迭代器遍历。即通过Iterator去遍历。
1 | Integer value = null; |
- 随机访问,通过索引值去遍历。
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
1 | Integer value = null; |
- for循环遍历。如下:
1 | Integer value = null; |
十. ArrayList和 Vector 的区别
- 同步
Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制; - 扩容
Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
Vector 的grow()扩容部分
1 | int newCapacity = oldCapacity + ((capacityIncrement > 0) ? |
十一. 小回顾
- 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间
- add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
- get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
- remove(Object o)需要遍历数组
- remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
- contains(E)需要遍历数组