杰瑞科技汇

List和Set的核心区别是什么?

核心区别在于是否允许重复元素以及是否保证元素的顺序

  • 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 HashSetSet 的首选,性能最优。
需要存储一组唯一的对象,并且需要记住插入的顺序 LinkedHashSet 在保证唯一性的同时,维护了插入顺序。
需要存储一组唯一的对象,并且需要它们按特定规则(如字母顺序)排序 TreeSet 提供了强大的排序功能。
需要模拟“队列” (FIFO - 先进先出) LinkedListArrayDeque LinkedList 实现了 Queue 接口。
需要模拟“栈” (LIFO - 后进先出) LinkedListArrayDeque LinkedListArrayDeque 都可以方便地实现栈操作。

代码示例

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]
    }
}

记住这个核心原则,就能轻松区分和使用 ListSet

  • 要顺序、要重复 -> List
  • 不关心顺序 -> HashSet
  • 要插入顺序 -> LinkedHashSet
  • 要排序 -> TreeSet
分享:
扫描分享到社交APP
上一篇
下一篇