集合系列(一):集合框架概述
Java 集合是 Java API 用得最频繁的一类,掌握 Java 集合的原理以及继承结构非常有必要。总的来说,Java 容器可以划分为 4 个部分:
- List 集合
- Set 集合
- Queue 集合
- Map 集合
除了上面 4 种集合之外,还有一个专门的工具类:
- 工具类(Iterator 迭代器、Enumeration 枚举类、Arrays 和 Collections)
在开始聊具体的集合体系之前,我想先介绍一下 Collection 框架的基本类结构。因为无论是 List 集合、Set 集合还是 Map 集合都以这个为基础。
- 首先,最顶层的是 Collection 接口。
可以看到 Collection 接口定义了最最基本的集合操作,例如:判断集合大小、判断集合是否为空等。List、Set、Queue 都继承了该接口。
- 接着,AbstractCollection 也继承了 Collection 接口。
从这个类名可以看出,其是一个抽象类。AbstractCollection 对 Collection 接口中一些通用的方法做了实现。例如:判断是否为空的方法、判断是否包含某个元素的方法等。
通过继承 AbstractCollection 接口,可以少写许多不必要的代码,这是代码抽象设计最常用的思想。AbstractCollection 是最为基础的类,其他所有集合的实现都继承了这个抽象类。
List 集合
List 集合存储的是有序的数据集合,其数据结构特点是:读取快,修改慢,适合于读取多、写入修改少的场景。List 集合的类继承结构如下:
我们可以看到除了 Collection 和 AbstractCollection 之外,我们还有 List 接口和 AbstractList 抽象类。其中 List 接口是 List 集合的最上层抽象,其继承了 Collection 接口,表示其实一个集合。而 AbstractList 则是 List 集合的抽象实现,实现了许多公用的操作。
整个 List 集合的实现可以分为红、黄、绿三大块。其中红色部分是 List 集合的列表实现,绿色部分是 List 结合的链表实现,而 黄色部分则是 List 集合列表实现的线程安全版本。
列表实现
ArrayList 类是很常用的 List 实现,其底层是用数组实现的。其读取元素的时间复杂度是 O(1),修改写入元素的时间复杂度是 O(N)。我们将会在下面的章节中详细介绍,这里不做深入。
列表安全实现
Vector 类也是很常用的 List 实现,其数据结构与 ArrayList 非常类似。但其与 ArrayList 的一个最大的不同是:Vector 是线程安全的,而 ArrayList 则不是线程安全的。
Stack 类则是在 Vector 的基础上,又实现了一个双向队列。所以其除了是线程安全的之外,其还是一个先进后出的 List 实现。
最后我们总结一下,List 集合最为关键的几个实现类是:
- ArrayList:列表集合经典实现。
- Vector:列表集合经典实现,线程安全,与 ArrayList 对应。
- Stack:栈结构的经典实现,先进后出的数据结构。继承了 Vector,线程安全。
- LinkedList:链表结构的经典实现。
链表实现
LinkedList 是一个经典的链表实现。LinkedList 继承了 AbstractSequentialList 抽象类。AbstractSequentialList 抽象类从字面上理解是抽象连续列表。这里的重点是
sequential 这个词,表示其数据结构是连续的(链表)。从其源码注释也可以看出这个意思。
This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list).
(意译)如果你想要实现一个连续存储(链表)的 List,那么这个抽象类可以让你减少不少工足量。
其实从命名就可以看出,AbstractSequentialList 其实是连续列表(链表)的一个抽象实现。AbstractSequentialList 抽象类做了许多工作,使得后续的链表实现更加简单。从 AbstractSequentialList 的注释可以看到,如果要实现一个链表,那么只需要实现 listIterator 方法和 size 方法就可以了。
Set 集合
Set 集合中存储的元素是不重复的,但是其存储顺序是无序的。下面是 Set 集合的类继承结构图:
与 List 集合类似,都是一个 Set 接口继承了 Collection 接口。一个 AbstractSet 抽象类实现了 Set 接口、继承了 AbstractCollection 抽象类。这部分完全和 List 相同。
Set 集合的实现可以分为两大块,一块是 Set 集合的有序实现(红色部分),另一块是 Set 集合的哈希实现(黄色部分)。
有序实现(TreeSet)
- SortedSet 接口继承了 Set 接口,TreeSet 实现了 SortedSet。
我们知道 Set 集合中的元素是无序的,而 SortedSet 接口则是定义了有序 Set 集合的接口。而 TreeSet 则是 SortedSet 的具体实现。
哈希实现(HashSet、LinkedHashSet)
HashSet 是 Set 接口的经典哈希实现。但 Set 集合中的元素是无序的,为了维护 Set 集合的插入顺序,人们创造出了 LinkedHashSet。LinkedHashSet 在 HashSet 的基础上是用链表维护元素的插入顺序。
到这里我们总结一下 Set 集合的所有实现:
- TreeSet:Set 集合的有序实现。
- HashSet:Set 集合的哈希实现。
- LinkedHashSet:Set 集合的哈希实现,维护了元素插入顺序。
Queue 集合
队列是一个特殊的线性表,其数据结构特点是先进先出。Queue 类结构体系如下图所示:
首先,Queue 接口继承了 Collection 接口。Queue 接口在拥有基本集合操作的基础上,定义了队列这种数据结构的基本操作。可以看到 offer、poll 等方法都是队列独有的操作。
接着,AbstractQueue 是对 Queue 接口的抽象实现。针对队列这种数据结构,其添加、删除元素的动作都不一样。在 AbstractQueue 抽象类里将队列的基本操作都实现了一遍。例如 AbstractQueue 中的 add 方法就和 AbstractList 中的 add 方法有着不同的实现。
如上图所示,Queue 的类结构整体可以分为黄色、红色两个部分。红色部分是 Queue 接口的有序实现,有 PriorityQueue 这个实现类。黄色部分是 Deque(双向队列)的实现,有 LinkedList 和 ArrayDeque 两个实现类。
有序实现
PriorityQueue 是 AbstractQueue 抽象类的具体实现。
PriorityQueue 表示优先级队列,其按照队列元素的大小进行重新排序。当调用 peek() 或 pool() 方法取出队列中头部的元素时,并不是取出最先进入队列的元素,而是取出队列的最小元素。
双向实现
- 首先,我们会看到 Deque 接口。
Deque(double ended queue)是双向队列的意思,它能在头部或尾部进行元素操作。
- 最后,我们看到 LinkedList 和 ArrayDeque 都是 Deque 接口的具体实现。
LinkedList 我们之前说过了,是一个链表,但它还是一个双向队列。因此 LinkedList 具有 List 和 Queue 的双重特性。ArrayDeque 则是一个双向循环队列,其底层是用数组实现。更多内容,我们将在队列章节讲解。
最后我们总结 Queue 体系的几个常见实现类:
- PriorityQueue:优先级队列
- LinkedList:双向队列实现
- ArrayDeque:双向循环队列实现
Map 集合
Map 集合与 List、Set、Queue 有较大不同,其实类似于 key/value 的数据结构。
- 首先,Map 接口是最顶层的接口。
与 List、Set、Queue 类似,Map 接口定义的是哈希表数据结构的操作。例如我们常用的 put、get、keySet 等。
- 接着,有 AbstractMap 抽象类。
和 List 等类似,AbstractMap 是 Map 接口的抽象实现。如上图所示,Map 集合的整个类结构可以分为红、黄、绿三块。
哈希实现
红色部分可以看成是 Map 的哈希实现。
- AbstractMap 有具体的实现类 HashMap。
HashMap 是 AbstractMap 基于哈希算法的具体实现。
- 接着,LinkedHashMap 和 WeakedHashMap 继承了 HashMap。
LinkedHashMap 是 HashMap 的进一步实现,其用链表保存了插入 HashMap 中的元素顺序。WeakedHashMap 是 HashMap 的进一步实现,与 HashMap不同的是:WeakedHashMap 中的引用是弱引用,如果太久没用,则会被自动回收。
有序实现
黄色部分可以看成是 Map 集合的有序实现。
- 首先,SortedMap 接口继承了 Map 接口。
与 Set 一样,Map 中的元素是没有顺序的,SortedMap 就是有序 Map 的接口定义。
- 接着,NavigableMap 继承了 SortedMap 接口。
NavigableMap 接口定义了一些查找逻辑,方便后续实现。
- 最后,TreeMap 则是 NavigableMap 接口的具体实现。
其实 TreeMap 是基于红黑树的 Map 实现。
看到了这里,Map 整个类结构看完了一半。而另外一半则是以 Dictionary 为主的实现(绿色部分)。但实际上 Dictionary 是老旧的 Map 实现,现在已经废弃了。我们从源码的注释中可以看到相关的提示。
NOTE: This class is obsolete(废弃的). New implementations should implement the Map interface, rather than extending this class.
这个类已经被废弃,新的实现应该实现 Map 接口,而不是扩展这个类。
所以针对于 Dictionary 的实现,我们并不打算深入讲解。
到这里我们总结一下 Map 集合的所有实现类:
- HashMap:Map 集合的经典哈希实现。
- LinkedHashMap:在 HashMap 的基础上,增加了对插入元素的链表维护。
- WeakedHashMap:在 HashMap 的基础上,使强引用变为弱引用。
- TreeMap:Map 集合的有序实现。
工具类
集合的工具类有:Iterator 迭代器、ListIterator 迭代器、Enumeration 枚举类、Arrays 和 Collections 类。
Iterator 迭代器
Iterator 迭代器是一个用来遍历并选择序列中的对象。Java 的 Iterator 只能单向移动。可以看到在 ArrayList、WeakHashMap 等集合类都实现了该接口,从而实现不同类型集合的遍历。
ListIterator 迭代器
ListIterator 继承了 Iterator 接口,所以其有更强大的功能,即它能够实现双向移动。但从其名字也可以看出,其只能适用于 List 集合的遍历。
Enumeration 枚举类
它是 JDK 1.0引入的接口。作用和Iterator一样,也是遍历集合。但是Enumeration的功能要比Iterator少。Enumeration只能在Hashtable, Vector, Stack中使用。这种传统接口已被迭代器取代,虽然 Enumeration 还未被遗弃,但在代码中已经被很少使用了。
官方也在文档中推荐使用 Iterator 接口来替代 Enumeration 接口。
Arrays
Java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的。
Collections
java.util.Collections 是一个包含各种有关集合操作的静态多态方法的工具类,服务于 Java 的 Collection 框架。
总结
我们花费了大量的篇幅讲解了 List 集合、Set 集合、Map 集合、Queue 集合以及 Iterator 等工具类。我们对这集合的类结构进行了详细的解析,从而更加了解他们之间的关系。
有时候我们会想,了解这么多有啥用呢。我有个朋友只用了常见的 ArrayList、HashMap 就可以了啊。对于这个问题,我想分享几个收获。
第一,让你更加熟悉类之间的差异。 如果我们只会用一两个类,那么我们就不知道在什么时候用什么类。例如:什么时候用 HashMap,什么时候用 Hashtable?Iterator 接口有什么作用?JDK源码的命名有什么特点?
第二,方便对源码进行扩展。 当我们深入研究了集合的实现之后,我们知道了原来 List 接口就是 List 这种数据类型的定义,而 AbstractList 是 List 的抽象实现。那么如果我们要实现一个自定义的 List 结构,那么我们就可以直接继承 AbstractList 类,从而达到快速实现的目的。但如果你没有深入研究呢?你或许只能从头写起,这样得浪费多大的精力啊。你学会了这种方式,那么对于你扩展 Spring 源码也是有很好的帮助的。
在接下来的文章里,我们将深入介绍每一个集合的具体实现。