核心区别概览
| 特性 | List |
Set |
|---|---|---|
| 核心特性 | 有序、可重复 | 无序、不可重复 |
| 数据存储 | 允许存储多个 null 元素(ArrayList/LinkedList) |
只允许存储一个 null 元素 |
| 接口方法 | get(int index), add(int index, E e), set(int index, E e) |
不支持按索引访问,没有 get(index) 方法 |
| 遍历方式 | 支持索引遍历 (for-i)、迭代器遍历、增强 for 循环 |
主要使用迭代器遍历、增强 for 循环 |
| 常用实现类 | ArrayList, LinkedList, Vector |
HashSet, LinkedHashSet, TreeSet |
| 性能(查找) | ArrayList 查快(O(1)),LinkedList 查慢(O(n)) |
HashSet 查快(O(1)),TreeSet 查慢(O(log n)) |
| 性能(插入/删除) | ArrayList 中间插/删慢(O(n)),LinkedList 快(O(1)) |
HashSet 快(O(1)),TreeSet 慢(O(log n)) |
| 排序 | 不保证排序,List 本身无排序概念 |
TreeSet 可自然排序或自定义排序 |
| 底层数据结构 | 数组 (ArrayList) 或 链表 (LinkedList) |
哈希表 (HashSet) 或 红黑树 (TreeSet) |
List 接口详解
List 是一个有序的集合,有时也被称为“序列”,它精确地控制每个元素插入的位置,并可以通过整数索引(位置)来访问元素。

核心特性
- 有序性: 元素的插入顺序会被保留,你第一次添加元素的顺序,就是它们在集合中的存储顺序。
- 可重复性: 允许存储多个
null值,也允许存储多个内容相同的对象(只要它们的equals()方法返回true)。 - 支持索引: 可以像数组一样,通过索引(从 0 开始)来快速访问、修改或插入元素。
常用实现类
a. ArrayList
- 数据结构: 基于动态数组实现。
- 特点:
- 查询快: 由于是数组,通过索引访问元素的时间复杂度是 O(1)。
- 增删慢: 在中间或头部插入/删除元素时,需要移动大量元素,时间复杂度为 O(n)。
- 非线程安全: 在多线程环境下不安全。
- 性能高: 是最常用、性能最好的
List实现类之一。
- 适用场景: 频繁查询,较少增删的场景,存储一个学生列表,根据学号(索引)查找学生。
b. LinkedList
- 数据结构: 基于双向链表实现。
- 特点:
- 查询慢: 查找元素需要从头或尾开始遍历,时间复杂度为 O(n)。
- 增删快: 在已知位置进行插入或删除时,只需要修改前后节点的指针即可,时间复杂度为 O(1)。
- 额外功能: 作为
List和Deque(双端队列)的实现,它支持在队列两端进行高效的添加、删除操作(addFirst(),addLast(),removeFirst(),removeLast())。 - 非线程安全。
- 适用场景: 频繁增删,较少查询的场景,实现一个任务队列,任务会不断地被添加到队尾,并从队首取出。
c. Vector
- 数据结构: 基于动态数组实现(与
ArrayList类似)。 - 特点:
- 线程安全: 它的所有公共方法都使用了
synchronized关键字进行同步,因此是线程安全的。 - 性能低: 由于同步开销,性能远不如
ArrayList。 - 已过时: 在现代 Java 开发中,通常使用
Collections.synchronizedList(new ArrayList<>())或CopyOnWriteArrayList来替代Vector。
- 线程安全: 它的所有公共方法都使用了
- 适用场景: 几乎不推荐使用,除非有非常古老的代码需要维护。
示例代码
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
// ArrayList 示例
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add(1, "Grape"); // 在索引1处插入
System.out.println("ArrayList: " + fruits); // 输出: [Apple, Grape, Banana, Orange]
System.out.println("索引为1的元素: " + fruits.get(1)); // 输出: Grape
// LinkedList 示例
List<Integer> numbers = new LinkedList<>();
numbers.add(10);
numbers.add(20);
numbers.addFirst(5); // 在头部添加
numbers.addLast(30); // 在尾部添加
System.out.println("LinkedList: " + numbers); // 输出: [5, 10, 20, 30]
}
}
Set 接口详解
Set 是一个不包含重复元素的集合,它模拟了数学上的集合概念。
核心特性
- 无序性 (对于
HashSet):HashSet不保证元素的存储顺序,更不保证元素的顺序会随着时间的推移保持不变。 - 不可重复性: 如果试图将一个已经存在于
Set中的元素再次添加,add()方法会返回false,且Set的内容不会改变,这是通过equals()和hashCode()方法来判定的。 - 无索引:
Set没有提供get(index)方法,无法通过索引来访问元素。
常用实现类
a. HashSet
- 数据结构: 基于哈希表(实际上是
HashMap)实现。 - 特点:
- 性能极高: 添加、删除、查找操作的平均时间复杂度都是 O(1)。
- 不保证顺序: 元素的存储顺序是不可预测的。
- 允许一个
null元素。
- 适用场景: 需要快速去重,且不关心元素顺序的场景,从一堆数据中提取出唯一的用户ID。
b. LinkedHashSet
- 数据结构: 基于哈希表和链表实现。
- 特点:
- 保证插入顺序: 它是
HashSet的一个子类,通过维护一个双向链表来记录元素的插入顺序。 - 性能稍低: 由于需要维护链表,性能比
HashSet略慢,但仍然是 O(1) 的时间复杂度。 - 允许一个
null元素。
- 保证插入顺序: 它是
- 适用场景: 需要去重,同时需要保持元素插入顺序的场景,记录用户的访问记录,既要去重,又要按时间顺序展示。
c. TreeSet
- 数据结构: 基于红黑树(一种自平衡二叉查找树)实现。
- 特点:
- 有序性: 它会将元素按照一定的顺序进行排序,默认情况下,元素必须实现
Comparable接口,或者你必须在创建TreeSet时提供一个Comparator。 - 性能较好: 添加、删除、查找操作的时间复杂度为 O(log n)。
- 不允许
null元素: 因为null无法与任何对象进行比较,会抛出NullPointerException。
- 有序性: 它会将元素按照一定的顺序进行排序,默认情况下,元素必须实现
- 适用场景: 需要对元素进行排序的场景,存储一组商品并按价格从低到高排序。
示例代码
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("Dog");
hashSet.add("Cat");
hashSet.add("Bird");
hashSet.add("Dog"); // 重复元素,添加失败
System.out.println("HashSet (无序): " + hashSet); // 输出顺序不确定
// LinkedHashSet 示例 - 保证插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Dog");
linkedHashSet.add("Cat");
linkedHashSet.add("Bird");
linkedHashSet.add("Dog");
System.out.println("LinkedHashSet (有序): " + linkedHashSet); // 输出: [Dog, Cat, Bird]
// TreeSet 示例 - 自然排序
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
treeSet.add(5);
System.out.println("TreeSet (自然排序): " + treeSet); // 输出: [5, 10, 20, 30]
}
}
如何选择 List 和 Set?
这是一个非常常见的面试题和开发决策点,选择哪个完全取决于你的业务需求。
| 你的需求 | 应该选择 | 原因 |
|---|---|---|
| 需要存储一组对象,并且允许重复 | List |
Set 的核心就是不允许重复。 |
| 需要保持元素的插入顺序 | List 或 LinkedHashSet |
List 天然有序。LinkedHashSet 也能保证插入顺序,但不能重复。 |
| 需要通过索引(位置)来访问元素 | List |
Set 没有索引概念。 |
| 需要确保集合中没有重复元素 | Set |
这是 Set 的主要用途,可以高效地自动去重。 |
| 需要将元素排序后存储 | TreeSet |
TreeSet 是 Set 中唯一能自动排序的实现。 |
| 需要高性能的查找、插入、删除,且不关心顺序 | HashSet |
HashSet 提供了近乎 O(1) 的最佳性能。 |
| 需要高性能的去重,同时又要保持插入顺序 | LinkedHashSet |
在 HashSet 的基础上增加了维护顺序的功能,性能依然很好。 |
List是一个“列表”:它关心的是位置和顺序,元素可以重复,想象成一个购物清单,你可以买两斤苹果,清单上会有两个“苹果”。Set是一个“集合”:它关心的是成员关系和唯一性,元素不能重复,想象成一个钱包里的硬币,你不可能有两枚完全相同的1元硬币(面值相同即可视为重复)。
理解它们的核心区别——有序可重复 vs. 无序不可重复——是掌握 Java 集合框架的关键一步。

