ArrayList
实现于 List
、RandomAccess
接口。可以插入空数据,也支持随机访问。
ArrayList
相当于动态数据,其中最重要的两个属性分别是: elementData
数组,以及 size
大小。
成员变量
1 | /** 默认的初始化容量 */ |
构造函数
1 | public ArrayList(int initialCapacity) { |
c.toArray might (incorrectly) not return Object[] (see 6260652)
查看 ArrayList(Collection<? extends E> c)
构造函数时,有个注释 c.toArray might (incorrectly) not return Object[] (see 6260652),具体什么意思呢?先看个示例:
1 | List<Object> list = new ArrayList<Object>(Arrays.asList("foo", "bar")); |
如果 ArrayList 的构造函数中没有类型检查的代码 elementData.getClass() != Object[].class
,会导致其elementData 的实际类型是String[],而不是 Object[],所以当你将其中一个元素更换为 Object 元素时会报错,你可以试下如下代码,肯定会报 ArrayStoreException 的错误。
1 | Object[] arr = new String[]{"a","b"}; |
主要问题出在 Arrays.asList
上面,Arrays.asList
返回的 ArrayList 实际上是内部类 ArrayList ,并不是我们经常使用的 ArrayList
。
1 | private static class ArrayList<E> extends AbstractList<E> |
内部类 ArrayList 的 toArray()
使用的是 clone
方法,而我们经常使用的 ArrayList
的 toArray()
使用的是 Arrays.copyOf()
方法,具体差别如下:
1 | Object[] arr = new ArrayList<Object>(Arrays.asList("foo", "bar")).toArray(); |
关于 Arrays.asList
的一个坑 使用Java时的一些坑
add
在调用 add()
方法的时候:
1 | public boolean add(E e) { |
- 首先进行扩容校验。
- 将插入的值放到尾部,并将 size + 1 。
如果是调用 add(index,e)
在指定位置添加的话:
1 | public void add(int index, E element) { |
- 也是首先扩容校验。
- 接着对数据进行复制,目的是把 index 位置空出来放本次插入的数据,并将后面的数据向后移动一个位置。
其实扩容最终调用的代码:
1 | private void grow(int minCapacity) { |
也是一个数组复制的过程。
由此可见 ArrayList
的主要消耗是数组扩容以及在指定位置添加数据,在日常使用时最好是指定大小,尽量减少扩容。更要减少在指定位置插入数据的操作。
序列化
由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 transient
修饰,可以防止被自动序列化。
1 | transient Object[] elementData; |
因此 ArrayList 自定义了序列化与反序列化:
1 | private void writeObject(java.io.ObjectOutputStream s) |
从实现中可以看出 ArrayList 只序列化了被使用的数据。
ArrayList VS Vector
Vector
也是实现于 List
接口,底层数据结构和 ArrayList
类似,也是一个动态数组存放数据。不过是在 add()
方法的时候使用 synchronized
进行同步写数据,但是开销较大,所以 Vector
是一个同步容器并不是一个并发容器。
以下是 add()
方法:
1 | public synchronized boolean add(E e) { |
以及指定位置插入数据:
1 | public void add(int index, E element) { |