Skip to content

Java 集合框架 - 概览

本文介绍 Java 集合框架的各类接口和常用子类。

Java 集合框架

容器,就是可以容纳其他 Java 对象的对象。Java Collections Framework(JCF) 为 Java 开发者提供了通用的容器,其始于 JDK 1.2,优点是:

  • 降低编程难度
  • 提高程序性能
  • 提高 API 间的互操作性
  • 降低学习难度
  • 降低设计和实现相关 API 的难度
  • 增加程序的重用性

Java 容器里只能放对象,对于基本类型 (int, long, float, double 等),需要将其包装成对象类型后 (Integer, Long, Float, Double 等) 才能放到容器里。很多时候拆包装和解包装能够自动完成。这虽然会导致额外的性能和空间开销,但简化了设计和编程。

Java 容器主要包括 CollectionMap 两种,Collection 存储着对象的集合,而 Map 存储着键值对 (两个对象) 的映射表:

  1. Collection:主要由 ListSetQueue 组成。
    • List 代表有序、可重复的集合,典型代表就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList
    • Set 代表无序、不可重复的集合,典型代表就是 HashSetTreeSet
    • Queue 代表队列,典型代表就是双端队列 ArrayDeque,以及优先级队列 PriorityQue
  2. Map:代表键值对的集合,典型代表就是 HashMap

Java 集合框架

Collection 接口

Collection 是所有序列集合共有的根接口。集合表示一组对象,称为其元素。一些集合允许重复元素,而另一些则不允许。有些是有序的,有些是无序的。JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如 Set 和 List)的实现。此接口通常用于传递集合并在需要最大通用性的地方操作它们。

Collection接口继承了 Iterable接口,实现 Collection 就意味着需要提供 iterator() 方法。

java.util.AbstractCollection 类提供了 Collection 类的默认实现,使得你可以创建 AbstractCollection 的子类型,而其中没有不必要的代码重复。

Java 访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。

List 接口

java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象称为 List 集合。

在 List 集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。

另外,List 集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致

  1. ArrayList

ArrayList 底层是由数组实现的,支持随机存取,也就是可以通过下标直接存取元素;

如果内部数组的容量不足时会自动扩容,因此当元素非常庞大的时候,效率会比较低。

所以 ArrayList 的特点是元素增删慢,查找快。

  1. LinkedList

LinkedList 是由双向链表实现的,不支持随机存取,只能从一端开始遍历,直到找到需要的元素后返回;

任意位置插入和删除元素都很方便,因为只需要改变前一个节点和后一个节点的引用即可,不像 ArrayList 那样需要复制和移动数组元素;

因为每个元素都存储了前一个和后一个节点的引用,所以相对来说,占用的内存空间会比 ArrayList 多一些。

  1. Vector

和 ArrayList 类似,但它是线程安全的,一些方法都加了 synchronized关键字,执行效率会比较低,很少使用。

  1. Stack

Stack 继承了 Vector,在其基础上实现了栈先进先出的功能(push、pop、peek 等方法),方法上同样添加了 synchronized 关键字,官方推荐使用双端队列 ArrayDeque

Queue 接口

Queue 接口继承了 Collection,被设计用于处理之前临时保存在某处的元素。

除了基本的 Collection 操作之外,队列还提供了额外的插入、提取和检查操作。每一种操作都有两种形式:如果操作失败,则抛出一个异常;如果操作失败,则返回一个特殊值(null 或 false,取决于是什么操作)。

  1. ArrayDeque

ArrayDeque 是一个基于数组实现的双端队列,它使用一个头指针 head 指向队首的第一个有效的元素和一个尾指针 tail 指向队尾第一个可以插入元素的空位构成一个循环数组。

  1. LinkedList

LinkedList 也实现了 Deque 接口,由于其内部双向链表的特性,也可以作为链表实现的双向队列使用。

  1. PriorityQueue

PriorityQueue 是一种优先级队列,它的出队顺序与元素的优先级有关,执行 remove 或者 poll 方法,返回的总是优先级最高的元素。

要想有优先级,元素就需要实现 Comparable 接口或者 Comparator 接口

Set 接口

Set 的特点是存取无序,不可以存放重复的元素,不可以用下标对元素进行操作,和 List 有很多不同。

Set 的子类大多使用 Map 的子类的 key 存储数据,利用了 Map 键不允许重复、无序的特性。

  1. HashSet

HashSet 内部是由 HashMap 实现的,只不过值由一个固定的 Object 对象填充,而键用于操作。

  1. LinkedHashSet

LinkedHashSet 继承自 HashSet,其实是由 LinkedHashMap 实现的,LinkedHashSet 的构造方法调用了 HashSet 的一个特殊的构造方法:

java
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
   map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
   map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
  1. TreeSet

TreeSet 内部使用 TreeMap 实现,同样值由固定的 Object 对象填充,键用于操作。

Map 接口

Map 保存的是键值对,键要求保持唯一性,值可以重复。

  1. HashMap

HashMap 实现了 Map 接口,根据键的 HashCode 值来存储数据,具有很快的访问速度,最多允许一个 null 键。

HashMap 不论是在学习还是工作当中,使用频率都是相当高的。随着 JDK 版本的不断更新,HashMap 的底层也优化了很多次,JDK 8 的时候引入了红黑树

一旦 HashMap 发生哈希冲突,就把相同键位的地方改成链表,如果链表的长度超过 8,就该用红黑树。

  1. LinkedHashMap

LinkedHashMap 是 HashMap 的子类,内部使用链表来记录插入/访问元素的顺序。

LinkedHashMap 可以看作是 HashMap + LinkedList 的合体,它使用了 哈希表来存储数据,又用了双向链表来维持顺序。

  1. TreeMap

HashMap 是无序的,所以遍历的时候元素的顺序也是不可测的。TreeMap 是有序的,它在内部会对键进行排序,所以遍历的时候就可以得到预期的顺序。

为了保证顺序,TreeMap 的键必须要实现 Comparable 接口或者 Comparator 接口。

参考资料