学习笔记——ArrayList总结
ArrayList可以说是一种很常用很常用的数据结构了。
ArrayList底层是通过数组实现的。可以看下源码(基于JDK1.8)
首先从构造函数说起,总共有三种构造函数:
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
其中最常用的就是无参构造函数。
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
无参构造函数创建的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这里之所以要区分出EMPTY_ELEMENTDATA,主要是因为当DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容时,
会优先按默认大小(DEFAULT_CAPACITY)进行扩容。具体可以看这个方法:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
这样就将原本初始化时就要做的内存分配推迟到了真正使用时,算是一点优化吧。
ArrayList的存储
transient Object[] elementData;
在ArrayList中elementData是实际用来存放数据的数组。
默认大小是10,当数组满后,按1.5倍进行扩容。
代码如下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
但这样很容易造成内存的浪费,特别是当ArrayList比较大的时候,比如当前数组大小是20000,已填满,当再添加一个新元素时,就需要扩容成30000。但实际真实只使用了20001,9999就是浪费的。
对此ArrayList提供了trimToSize方法。此外,ArrayList之所以将elementData定义成transient,自己实现序列化和反序列化的方法也是为了序列化的时候,只序列化数组中实际使用的部分。减少了序列化的时间,和序列化后的文件大小。
可变长
其实数组压根就是不可变的,初始化长度是多少就是多少,ArrayList之所以可变长,都是通过创建一个新数组,将原数组的数据复制过去实现的。这是有性能开销的。ArrayList中的remove,指定位置add方法,甚至trimToSize等很多方法都是依靠这个数组复制的方式实现的。需要注意,当可以预估到需要ArrayList中元素比较多的时候,最好是在初始化的时候直接指定ArrayList的大小。否则会导致频繁的扩容,影响性能。
随机访问
ArrayList实现了RandomAccess接口。这是个标记接口。说明当前这个实现类是支持快速随机访问的。接口说明中给出了一个例子,按索引循环会比使用迭代器更快。
和Vector区别
vector可以看成是线程安全的ArrayList,内部代码实现大部分都是一样的,只不过vector的方法都加上了synchronized锁来保证线程安全。
此外vector默认按2倍扩容,也支持初始化的时候自定义每次扩容的大小。
源码如下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
其中capacityIncrement就是自定义的扩容大小。