核心区别在于是否允许重复元素以及是否保证元素的顺序。
List(列表): 有序、可重复,就像一个有编号的队伍,每个位置(索引)都可以站一个人,同一个人可以站多次。Set(集合): 无序(或特定顺序)、不可重复,就像一个舞会,每个人只能参加一次,但入场顺序不重要(或者按特定规则,如字母顺序)。
下面我们从多个维度进行详细对比。
核心区别一览表
| 特性 | List |
Set |
|---|---|---|
| 是否有序 | 有序 (按插入顺序或特定排序) | 通常无序 (HashSet), 但也可以有序 (LinkedHashSet, TreeSet) |
| 是否允许重复元素 | 允许 | 不允许 |
| 是否允许 null 元素 | 允许 (允许多个 null) | 取决于实现 - HashSet: 允许一个 - TreeSet: 不允许 - LinkedHashSet: 允许一个 |
| 主要特点 | 通过索引(整数)来访问元素,类似数组。 | 强调元素的唯一性,快速判断元素是否存在。 |
| 常用实现类 | - ArrayList (数组实现,查询快) - LinkedList (链表实现,增删快) |
- HashSet (哈希表实现,最快) - LinkedHashSet (哈希表+链表,保持插入顺序) - TreeSet (红黑树实现,自然排序或自定义排序) |
| 遍历方式 | for 循环 + 索引 2. 增强for循环 3. 迭代器 |
增强for循环 2. 迭代器 (不推荐使用索引遍历,因为无序) |
| 性能 (增删查) | - ArrayList: 查询(O(1))快,增删(O(n))慢 - LinkedList: 查询(O(n))慢,增删(O(1))快 |
- HashSet: 增删查平均都是 O(1),非常快 - TreeSet: 增删查都是 O(log n) |
| 继承接口 | Collection -> List |
Collection -> Set |
详细解析
List 接口及其实现类
List 是一个元素有序、可以包含重复元素的集合。
核心特点:
- 有序性:
List会记住元素的插入顺序,当你遍历List时,元素会按照你添加它们的顺序出现。 - 索引访问:
List允许你通过一个整数索引(从 0 开始)来访问、添加或删除元素,就像操作数组一样。 - 可重复性: 你可以多次将同一个对象(包括
null)添加到List中。
常用实现类:
-
ArrayList- 数据结构: 基于动态数组。
- 优点:
- 随机访问快: 通过索引获取元素的时间复杂度是 O(1)。
- 缓存友好: 因为是连续内存空间,CPU 缓存命中率更高。
- 缺点:
- 增删慢: 在中间或头部插入/删除元素需要移动大量元素,时间复杂度为 O(n)。
- 容量问题: 可能需要扩容,这是一个相对耗时的操作。
- 使用场景: 当你需要进行大量的随机访问,而很少在中间插入或删除元素时,这是最常用的
List实现类。
-
LinkedList- 数据结构: 基于双向链表。
- 优点:
- 增删快: 在已知位置增删元素,只需要修改前后节点的指针,时间复杂度为 O(1)。
- 无容量限制: 不需要像
ArrayList那样考虑扩容。
- 缺点:
- 随机访问慢: 查找元素必须从头或尾开始遍历,时间复杂度为 O(n)。
- 内存占用稍大: 每个节点都需要额外存储前后节点的指针。
- 使用场景: 当你需要在列表的头部或频繁进行增删操作时。
Set 接口及其实现类
Set 是一个不允许包含重复元素的集合。
核心特点:
- 唯一性: 这是
Set最核心的特征,当你尝试添加一个已经存在的元素时,add()方法会简单地返回false,而不会抛出异常或重复添加。 - 无序性:
Set通常不保证元素的存储和遍历顺序。HashSet就是最典型的例子,它的顺序看起来是随机的。
常用实现类:
-
HashSet- 数据结构: 基于
HashMap(实际上是哈希表)。 - 特点:
- 性能最高: 增、删、查操作的平均时间复杂度都是 O(1),非常快。
- 无序: 不保证元素的任何顺序。
- 允许一个 null: 只能有一个
null元素。
- 使用场景: 当你需要一个高性能的、不关心顺序的、且元素唯一的集合时,这是最常用的
Set实现类。
- 数据结构: 基于
-
LinkedHashSet- 数据结构: 基于
LinkedHashMap(哈希表 + 双向链表)。 - 特点:
- 有序性: 它是
HashSet的一个子类,通过维护一个双向链表来记录元素的插入顺序,它的遍历顺序就是元素的插入顺序。 - 性能略低: 相比
HashSet,因为需要维护链表,所以性能稍有下降,但仍然是 O(1)。 - 允许一个 null。
- 有序性: 它是
- 使用场景: 当你需要一个元素唯一的集合,并且需要保持元素的插入顺序时。
- 数据结构: 基于
-
TreeSet- 数据结构: 基于
TreeMap(红黑树)。 - 特点:
- 有序性: 它会根据元素的“自然顺序”(
Comparable接口)或你提供的Comparator排序规则对元素进行排序,遍历时会得到排序后的结果。 - 性能稳定: 增、删、查操作的时间复杂度是 O(log n)。
- 不允许 null: 因为
null无法与其他对象进行比较,会抛出NullPointerException。
- 有序性: 它会根据元素的“自然顺序”(
- 使用场景: 当你需要一个元素唯一的、并且需要按特定顺序(如字母、数字)存储和遍历的集合时。
- 数据结构: 基于
如何选择?(选择指南)
| 场景 | 应该选择 | 理由 |
|---|---|---|
| 需要存储一组对象,并且允许重复,且需要保持插入顺序 | ArrayList |
ArrayList 是最通用的选择,索引访问方便。 |
| 需要存储一组对象,允许重复,且需要频繁在头部/中间增删 | LinkedList |
LinkedList 的增删性能更好。 |
| 需要存储一组唯一的对象,不关心顺序,追求最高性能 | HashSet |
HashSet 是 Set 的首选,性能最优。 |
| 需要存储一组唯一的对象,并且需要记住插入的顺序 | LinkedHashSet |
在保证唯一性的同时,维护了插入顺序。 |
| 需要存储一组唯一的对象,并且需要它们按特定规则(如字母顺序)排序 | TreeSet |
提供了强大的排序功能。 |
| 需要模拟“队列” (FIFO - 先进先出) | LinkedList 或 ArrayDeque |
LinkedList 实现了 Queue 接口。 |
| 需要模拟“栈” (LIFO - 后进先出) | LinkedList 或 ArrayDeque |
LinkedList 和 ArrayDeque 都可以方便地实现栈操作。 |
代码示例
List 示例
import java.util.ArrayList;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
// 添加元素 (允许重复)
names.add("Alice");
names.add("Bob");
names.add("Alice"); // 重复添加
names.add(null); // 允许 null
System.out.println("List 内容: " + names); // 输出: [Alice, Bob, Alice, null]
System.out.println("List 大小: " + names.size()); // 输出: 4
// 通过索引访问
System.out.println("索引 1 的元素: " + names.get(1)); // 输出: Bob
// 遍历 (按索引顺序)
System.out.println("遍历 List:");
for (int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
}
}
Set 示例
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetExample {
public static void main(String[] args) {
// HashSet: 无序,不重复
Set<String> hashSet = new HashSet<>();
hashSet.add("Banana");
hashSet.add("Apple");
hashSet.add("Orange");
hashSet.add("Apple"); // 重复添加,无效
hashSet.add(null); // 允许一个 null
System.out.println("HashSet 内容 (顺序可能不固定): " + hashSet);
// LinkedHashSet: 有序 (插入顺序),不重复
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Banana");
linkedHashSet.add("Apple");
linkedHashSet.add("Orange");
linkedHashSet.add("Apple"); // 重复添加,无效
System.out.println("LinkedHashSet 内容 (插入顺序): " + linkedHashSet); // 输出: [Banana, Apple, Orange]
// TreeSet: 有序 (自然排序),不重复
// 注意: TreeSet 不允许 null
Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Orange");
System.out.println("TreeSet 内容 (自然排序): " + treeSet); // 输出: [Apple, Banana, Orange]
}
}
记住这个核心原则,就能轻松区分和使用 List 和 Set:
- 要顺序、要重复 ->
List - 不关心顺序 ->
HashSet - 要插入顺序 ->
LinkedHashSet - 要排序 ->
TreeSet
