核心区别总结(一张图看懂)
| 特性 | List (列表) |
Set (集) |
|---|---|---|
| 核心特点 | 有序、可重复 | 无序、不可重复 |
| 允许重复元素 | 是 | 否 |
| 元素顺序 | 有序,按插入顺序或排序规则存放 | 通常无序(HashSet),或按排序规则存放(TreeSet) |
| 是否允许 null 元素 | 是(通常允许多个) | 是(但通常只允许一个,TreeSet 不允许) |
| 常用实现类 | ArrayList, LinkedList, Vector |
HashSet, LinkedHashSet, TreeSet |
| 底层结构 | 动态数组 (ArrayList) / 双向链表 (LinkedList) |
哈希表 (HashSet) / 哈希表+链表 (LinkedHashSet) / 红黑树 (TreeSet) |
| 查询性能 | ArrayList 快 (O(1)),LinkedList 慢 (O(n)) |
HashSet/LinkedHashSet 极快 (O(1)),TreeSet 较快 (O(log n)) |
| 插入性能 | ArrayList 尾部快 (O(1)),中间/头部慢 (O(n))LinkedList 头部/尾部快 (O(1)),中间慢 (O(n)) |
HashSet/LinkedHashSet 极快 (O(1))TreeSet 较快 (O(log n)) |
| 适用场景 | 需要按索引访问、需要保留插入顺序、允许重复元素的场景 | 需要去重、不关心顺序(或需要特定排序)的场景 |
详细解释与对比
核心设计理念
-
List (列表):
(图片来源网络,侵删)- “袋子”:你可以把
List想象成一个有序的袋子,你可以往里面放任何东西,可以放多个相同的东西,并且可以指定一个位置(索引)来取出某个特定的东西。 - 核心是“位置”和“顺序”,它的每个元素都有一个精确的索引,从 0 开始。
- “袋子”:你可以把
-
Set (集):
- “集合论中的集合”:
Set严格遵循数学中集合的定义,即不允许有重复的元素,它像一个特殊的盒子,你放一个东西进去,如果已经有一样的东西了,它就不会再放进去。 - 核心是“唯一性”,它不关心元素的插入顺序(除非是
LinkedHashSet),只关心元素是否唯一。
- “集合论中的集合”:
元素的唯一性与顺序
这是两者最根本的区别,我们来通过代码示例看看:
List 示例:
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Alice"); // 允许重复
names.add(1, "Charlie"); // 在索引1的位置插入
System.out.println(names);
// 输出: [Alice, Charlie, Bob, Alice]
// 可以看到,有两个 "Alice","Charlie" 被插入到了指定位置。
Set 示例:

(图片来源网络,侵删)
import java.util.HashSet;
import java.util.Set;
Set<String> names = new HashSet<>();
names.add("Alice");
names.add("Bob");
names.add("Alice"); // 重复添加,这个操作无效
System.out.println(names);
// 输出可能是: [Bob, Alice] 或 [Alice, Bob] (顺序不确定)
// 可以看到,只有一个 "Alice",且输出顺序不一定是插入顺序。
常用实现类详解
List 和 Set 都有多个实现类,了解它们的区别对于选择正确的工具至关重要。
List 的实现类
| 实现类 | 底层结构 | 特点 | 线程安全 | 适用场景 |
|---|---|---|---|---|
ArrayList |
动态数组 | 查询快(随机访问 O(1)),增删慢(非尾部 O(n)) | 否 | 最常用,需要频繁查询、不介意偶尔增删开销的场景。 |
LinkedList |
双向链表 | 增删快(头部/尾部 O(1)),查询慢(O(n)) | 否 | 需要频繁在头部或中间插入/删除元素的场景。 |
Vector |
动态数组 | 功能和 ArrayList 一样,但线程安全 |
是 | 已经过时,性能差,现代开发中使用 CopyOnWriteArrayList 或 Collections.synchronizedList() 替代。 |
Set 的实现类
| 实现类 | 底层结构 | 特点 | 线程安全 | 适用场景 |
|---|---|---|---|---|
HashSet |
哈希表 | 不保证顺序,存取速度快(O(1)),不允许重复 | 否 | 最常用,只需要保证元素唯一性,不关心顺序的场景。 |
LinkedHashSet |
哈希表 + 链表 | 保证插入顺序,存取速度快(O(1)),不允许重复 | 否 | 需要去重,同时保留插入顺序的场景。 |
TreeSet |
红黑树 | 元素自然排序(或指定 Comparator),存取速度较快(O(log n)),不允许重复 | 否 | 需要对元素进行排序的场景。 |
性能对比
-
List(ArrayList):get(int index): 极快,直接通过索引计算内存地址,时间复杂度为 O(1)。add(E e)(尾部): 快,平均时间复杂度为 O(1),可能需要扩容。add(int index, E e): 慢,需要移动 index 位置之后的所有元素,时间复杂度为 O(n)。remove(int index): 慢,同上,需要移动元素,时间复杂度为 O(n)。
-
Set(HashSet):add(E e),remove(Object o),contains(Object o): 极快,通过哈希码直接定位元素,平均时间复杂度为 O(1)。- 注意:虽然
HashSet的增删查很快,但它不能通过索引访问,如果要遍历查找某个元素,性能可能不如ArrayList。
-
Set(TreeSet):
(图片来源网络,侵删)- 所有操作(增、删、查)的时间复杂度都是 O(log n),因为底层是树结构,这比
HashSet的 O(1) 慢,但保证了有序性。
- 所有操作(增、删、查)的时间复杂度都是 O(log n),因为底层是树结构,这比
null 值的处理
List: 通常可以包含多个null值。List<String> list = new ArrayList<>(); list.add(null); list.add(null); System.out.println(list); // [null, null]
Set:HashSet: 可以包含一个null值。Set<String> set = new HashSet<>(); set.add(null); System.out.println(set); // [null]
TreeSet: 不允许包含null值,如果尝试添加,会抛出NullPointerException。Set<String> set = new TreeSet<>(); set.add(null); // 抛出 NullPointerException
如何选择?(决策指南)
问自己以下几个问题来决定使用 List 还是 Set:
-
我的数据需要重复吗?
- 是 -> 必须使用
List。 - 否 -> 继续往下问。
- 是 -> 必须使用
-
我需要保留数据的插入顺序吗?
- 是 -> 使用
LinkedHashSet。 - 否 -> 继续往下问。
- 是 -> 使用
-
我需要对数据进行排序吗?
- 是 -> 使用
TreeSet。 - 否 -> 使用
HashSet(这是最常用的Set)。
- 是 -> 使用
-
我需要根据索引(位置)来访问、插入或删除元素吗?
- 是 -> 必须使用
List,这是List的核心功能,Set完全不支持。
- 是 -> 必须使用
总结流程图:
需要存储一组对象?
|
+-- 数据允许重复? ---是---> 使用 List
| |
| +-- 需要随机访问/索引操作? ---> ArrayList
| |
| +-- 需要频繁在头/尾/中间增删? ---> LinkedList
|
+-- 数据不允许重复? ---否--->
|
+-- 需要按特定规则排序? ---> TreeSet
|
+-- 不需要排序? ----------+
|
+-- 需要保留插入顺序? ---> LinkedHashSet
|
+-- 不关心顺序? ---------> HashSet 