概述
之前介绍了 Collection 的整体架构。本文将通过Collection的具体类来加深理解。首先我们来看List,而ArrayList是List中最常用的,所以我们从他下手。主要分为以下部分:
- 第一部分 简介
- 第二部分 数据结构分析
- 第三部分 源码分析
- 第四部分 遍历方式
- 第五部分 toArray()异常
ArrayList简介
ArrayList的定义如下:
ArrayList是一个数组列表,相当于动态数组。与Java中的数组相比,它的容量能够动态增长。它继承于AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable这几个接口。
ArrayList继承了AbstractList,实现了List接口。它是一个数组列表,提供了相关的添加、修改、删除、遍历等功能。
ArrayList实现了RandomAccess接口,即提供了随机访问的功能。RandomAccess是Java中用来被List实现,为List提供快速访问功能的。除了各种List外,Stack
、Vector
也实现了RandomAccess接口,这其实也说明了Stack和Vector也是List。在ArrayList中,我们可以通过索引来快速地访问元素,这就是快速随机访问,也可以通过迭代器来访问,稍后我们会比较通过”快速随机访问“和“迭代器访问”的效率。
ArrayList实现了Cloneable,这就表明ArrayList覆盖了clone()
方法,可以被克隆。
ArrayList实现了java.io.Serializable,这就表明ArrayList支持被序列化,可以通过序列化被传输。
和Vector
不同,ArrayList访问不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中使用Vector或者CopyOnWriteArrayList
。
ArrayList的构造方法
|
|
ArrayList的API
|
|
数据结构分析
ArrayList的继承关系
|
|
ArrayList与Collection的关系
ArrayList包含2个重要数据:elementData和size。
- elementData 是”Object[]类型的数组”,它是ArrayList实际存储元素的地方。实际上,elementData是一个动态数组,默认的容量是10,我们可以通过ArrayList(int initialCapacity)来设定它的容量(不小于10)。容量的大小会根据数组列表的长度增长而增长,具体的增长可以查看ensureCapacity()方法。
- size 是动态数组的实际大小。
ArrayList源码分析(基于JDK 1.8.0_40)
源码分析
|
|
总结
- ArrayList实际是通过一个数组去保存元素的;如果使用默认的构造器,则ArrayList的默认容量是10;
- 当ArrayList容量不足以容纳全部元素的时候,会重新扩容:新的容量 = 原始容量 + (原始容量 / 2)。(JDK1.6版本是:新的容量 = (原始容量 * 3) / 2 + 1)。
- ArrayList的克隆函数,即是将所有全部元素克隆到一个数组中。
- ArrayList实现java.io.Serializable的方式。当写入到输出流的时候,先写入容量,再一次写入每个元素;当读取输入流的时候,先读取容量,再依次读取每个元素。
ArrayList的遍历
第一种:通过迭代器遍历
|
|
第二种:随机访问,通过索引值遍历
|
|
第三种:通过forEach遍历
|
|
下面通过实例来测试这3种遍历方式的耗时:
执行结果:
结论:
遍历ArrayList的时候,效率的排序依次是:随机访问遍历 > forEach遍历 > 迭代器遍历
toArray()异常
Arraylist有2个toArray()方法:
调用toArray()
的时候,可能会抛出ClassCastException
异常,例如下面这种情况:
这个时候就会抛出:
因为toArray()返回的是Object[]
,而Java 不支持向下转型,把Object[]转换为Integer[]就会出现异常。但是Object[]里面每个单独的Object是可以强转成Integer的,因为Object指向的实际类型就是Integer,所以强转没有问题。而Object[]本身就只是Object类型的数组,强转成Integer[]就会出错。
可以用toArray(T[] a)改进,方式如下: