ArrayList 动态数组,可动态扩展,是数组实现的list。
源码分析
继承实现
1 | public class ArrayList<E> extends AbstractList<E> |
- ArrayList实现了List,提供了list的的添加、删除、遍历等操作。
- ArrayList实现了RandomAccess,提供了随机访问的能力。
- ArrayList实现了Cloneable,可以被克隆。
- ArrayList实现了Serializable,可以被序列化。
属性
1 | /** |
构造方法
构造方法一
1 | /*传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0则异常*/ |
构造方法二
1 | /*默认构造方法,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空数组,会在添加第一个元素的时候扩容为默认大小,即10*/ |
构造方法三
1 | /*传入集合的构造方法,用拷贝,把传入集合的元素拷贝进elementData数组,如果元素为0,则初始化EMPTY_ELEMENTDATA空数组*/ |
注意,c.toArray();
返回的可能不是Object[]类型
add系列方法
add(E e)方法
新增单个元素,时间复杂度O(1)
- 检查是否需要扩容;
- 如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA则初始化容量大小为DEFAULT_CAPACITY;
- 新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了这么多容量发现比需要的容量还小,则以需要的容量为准;
- 创建新容量的数组并把老数组拷贝到新数组;
1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
add(int index, E element) 方法
添加元素到指定位置,时间复杂度O(n)
步骤
- 检查index是否越界
- 检查是否需要扩容
- 把传入索引位置后的元素往后移
- 在插入索引位置放置插入的元素
- 大小+1
1 | private void rangeCheckForAdd(int index) { |
addAll(Collection<? extends E> c)
两个集合的并集
步骤
- 拷贝c中的元素到数组a中
- 检查是否需要扩容
- 把数据a中的元素拷贝到elementData的尾部
1 | public boolean addAll(int index, Collection<? extends E> c) { |
get remove等方法
get(int index)方法
时间复杂度O(1)
- 检查索引是否越界,越上界抛出IndexOutOfBoundsException异常,越下界抛出的是ArrayIndexOutOfBoundsException异常。
- 返回索引位置处的元素
1 | public E get(int index) { |
remove(int index)方法
删除指定索引位置的元素,时间复杂度O(n)
- 检查索引是否越界
- 获取指定索引位置的元素
- 如果删除的不是最后一位,则其他元素前移一位
- 将最后一位设为null,方便gc回收
- 返回删除的元素
1 | public E remove(int index) { |
retainAll(Collection c)方法
两个集合的交集
- 遍历elementData数组
- 如果元素在c中,则把这个元素添加到elementData数组的w位置并将w位置往后移一位
- 遍历完之后,w之前的元素都是两者共有的,w之后(包含)的元素不是两者共有的
- 将w之后(包含)的元素置为null,方便GC回收
1 | public boolean retainAll(Collection<?> c) { |
removeAll(Collection<?> c)
1 | public boolean removeAll(Collection<?> c) { |
因此
- ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
- ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
- ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;
注意
elementData设置成了transient,则ArrayList怎么序列化元素?
查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。
一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。
在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。
elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。
1 | private void writeObject(java.io.ObjectOutputStream s) |