1. List 接口及其实现类
根据
Vector
类,Stack
类,ArrayList
类和LinkedList
类,
这四个实现类所拥有的存储特点方法进行总结, 得出共同特点:- 允许集合内部存在重复的元素
- 会记录添加元素的先后顺序
因此自下而上来说可以根据这些实现类总结出一个接口, 就是
List
接口.List
接口是有序的collection
(也称为序列). 它的特点是:- 列表中允许存在重复的元素
- 用户可以对列表中每个元素的插入位置进行精确地控制
- 用户可以根据元素的整数索引(在列表中的位置)访问并搜索元素
此接口是
Java Collections Framework
的成员.
1.1. Vector 类
Vector
类可以实现可增长的对象数组, 它包含可以使用整数索引进行访问的元素.它的大小可以根据需要增大或缩小, 以适应创建
Vector
后进行添加或移除项的操作.该类中的方法是支持同步的, 该类底层实际上是
Object
数组.
1.1.1. Vector 类的存储原理
通过查看源码可以知,
Vector
类中定义了一个Object
类型的数组成员变量.
定义的代码为:protected Object[] elementData;
因此表面上是将数据存到
Vector
对象中, 实际是存在Object
数组中的.同时数组元素类型是
Object
类型, 因此集合中只能存储任意类型的对象.
在低版本 JAVA 中集合框架是不支持存储基本数据类型的.
但 JAVA5 后会自动装箱拆箱, 因此写入基本类型时不会出错.集合中存储的对象, 都是对象的引用, 而非对象本身.
1.2. Stack 类
1.2.1. Stack 类的简介
Stack
类代表先进先出(LIFO)栈的对象. 它继承自Vector
类, 允许一个向量被视为栈.要实现栈这种数据结构, 底层可使用数组或链表进行操作,
Stack
类继承父类Vector
, 因为Vector
类定义了Object
数组成员的, 因此Stack
类将该数组成员继承了,
所以Stack
类底层是用数组来实现栈的.对于栈结构实现的集合框架官方建议:
Deque
接口及其实现提供更完整和一致的 LIFO 堆栈操作集, 这些接口应优于此类.
例如下面利用其实现类来进行定义的一个栈:Deque<Integer> stack = new ArrayDeque<Integer>();
1.2.2. Stack 常用的方法
Stack
类在Vector
类的基础上增加了 5 个操作:- 常规的入栈出栈,
push()
和pop()
操作; - 查看栈顶元素的
peek()
方法; - 以测试堆栈是否为空的
empty()
方法; - 以及返回一个对象在此堆栈上的基于 1 的位置的
search()
方法.
- 常规的入栈出栈,
1.3. ArrayList 类
1.3.1. ArrayList 类的简介
List
接口的大小可变数组的实现. 实现了所有可选列表操作, 并允许存放所有元素.除了实现
List
接口外, 该类还提供了一些方法来操纵内部的存储列表的数组大小.
(这个类是大致相当于 Vector, 不同之处在于它是不同步的).ArrayList
类是 JAVA 集合框架出现后用来取缔Vector
类的.- 二者的底层原理是基于数组的算法, 几乎是一模一样的.
ArrayList
类和Vector
类的最大区别是在于有没有用同步修饰ArrayList
中的所有方法没有使用synchronized
修饰;Vector
类中的所有方法都使用了synchronized
修饰.
因此
ArrayList
类的性能比Vector
类更高, 但线程安全程度相对要低.但可通过集合工具类使得
ArrayList
类线程安全, 因此彻底取缔Vector
类.- 工具类调用实例如下所示:
List list = Collections.synchronizedList(new ArrayList(...));
- 工具类调用实例如下所示:
1.3.2. ArrayList 类的源码分析
从底层源码来互相对比
Vector
类和ArrayList
类, 可以发现它们的设计差异很大.这种设计差异源自于 JAVA7 之后对
ArrayList
类的设计重新进行优化, 使其性能更高.
在 JAVA6 及其之前,Vector
类和ArrayList
类的源码除了同步修饰外是一样的.最直观的优化在于初始化
Object
数组上.Vector
类默认初始化数组长度为 10, 不论增加数据与否都占用了内存空间;ArrayList
类则默认初始化长度是 0 的空数组, 得到的是一个不占空间的空集;
只有在第一次调用add()
方法的时候才会再次对数组赋予空间进行存储.
ArrayList
类优化后节省了空间, 以及可通过返回new ArrayList()
来返回空集[]
.
1.4. LinkedList 类
1.4.1. LinkedList 类的简介
List
接口的链接列表实现. 实现所有可选的列表操作,并且允许所有元素, 包括null
.除了实现
List
接口外,LinkedList
类还为在列表的开头及结尾对元素的操作,
包括get
,remove
和insert
元素提供了统一的命名方法.LinkedList
类还实现了单向和双向队列的接口, 也提供了栈和链表的操作方法.
因此LinkedList
类可用于实现栈和双向链表, 单向/双向队列这几种数据结构.但由于是多种数据结构的实现类, 导致继承和实现了多种方法, 但实际上很多方法
其功能都是重复的, 只是名字不同而已.同时该类也是不同步的, 也需要调用集合工具类的方法来提供一个线程安全的链表.
List list = Collections.synchronizedList(new LinkedList(...));
1.4.2. LinkedList 类的方法解析
不论是链表还是队列, 都特别擅长操作头部和尾部的节点.
因此在LinkedList
类中大多数方法命名都为xxFirst/xxLast
.值得注意的是链表中并没有索引的概念, 但
LinkedList
类仍能使用E get(int index)
方法来进行索引查询, 这是因为从 JAVA2 开始,LinkedList
类作为List
接口的实现类, 而接口中提供了该方法,
所以LinkedList
类内部也只能提供一个变量来模拟当做索引使用.
因此使用LinkedList
类时, 尽量不要去使用索引查询, 性能非常低.LinkedList
类性能高的地方在于删除和保存操作, 而非去查询检索.具体操作方式可以直接去查询 API 获取.
2. List 的实现类性能和特点分析
- 这里只讨论
Vector
类,ArrayList
类和LinkedList
类.
2.1. 实现类之间的区别
共同点已经在上面所总结, 现在这里总结这四种实现类之间的不同点:
Vector
类: 底层采用数组结构算法, 方法都是同步的ArrayList
类: 底层采用数组结构算法, 方法不是同步的LinkedeList
类: 底层采用双向链表结构算法, 方法不是同步的
2.2. 数组结构和双向链表结构的算法性能
List
的实现类的底层有数组结构和双向链表两种, 应用着不同的算法.- 数组结构算法: 查询和更改性能高, 插入和删除性能低
- 链表结构算法: 插入和删除性能高, 查询和更改性能低
因此在使用的时候根据不同的应用场景选择不同的实现类,
但Vector
类已被ArrayList
类取代, 不再适用.