杰瑞科技汇

java list 和 set

核心区别概览

特性 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 是一个有序的集合,有时也被称为“序列”,它精确地控制每个元素插入的位置,并可以通过整数索引(位置)来访问元素。

java list 和 set-图1
(图片来源网络,侵删)

核心特性

  • 有序性: 元素的插入顺序会被保留,你第一次添加元素的顺序,就是它们在集合中的存储顺序。
  • 可重复性: 允许存储多个 null 值,也允许存储多个内容相同的对象(只要它们的 equals() 方法返回 true)。
  • 支持索引: 可以像数组一样,通过索引(从 0 开始)来快速访问、修改或插入元素。

常用实现类

a. ArrayList

  • 数据结构: 基于动态数组实现。
  • 特点:
    • 查询快: 由于是数组,通过索引访问元素的时间复杂度是 O(1)。
    • 增删慢: 在中间或头部插入/删除元素时,需要移动大量元素,时间复杂度为 O(n)。
    • 非线程安全: 在多线程环境下不安全。
    • 性能高: 是最常用、性能最好的 List 实现类之一。
  • 适用场景: 频繁查询,较少增删的场景,存储一个学生列表,根据学号(索引)查找学生。

b. LinkedList

  • 数据结构: 基于双向链表实现。
  • 特点:
    • 查询慢: 查找元素需要从头或尾开始遍历,时间复杂度为 O(n)。
    • 增删快: 在已知位置进行插入或删除时,只需要修改前后节点的指针即可,时间复杂度为 O(1)。
    • 额外功能: 作为 ListDeque(双端队列)的实现,它支持在队列两端进行高效的添加、删除操作(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]
    }
}

如何选择 ListSet

这是一个非常常见的面试题和开发决策点,选择哪个完全取决于你的业务需求。

你的需求 应该选择 原因
需要存储一组对象,并且允许重复 List Set 的核心就是不允许重复。
需要保持元素的插入顺序 ListLinkedHashSet List 天然有序。LinkedHashSet 也能保证插入顺序,但不能重复。
需要通过索引(位置)来访问元素 List Set 没有索引概念。
需要确保集合中没有重复元素 Set 这是 Set 的主要用途,可以高效地自动去重。
需要将元素排序后存储 TreeSet TreeSetSet 中唯一能自动排序的实现。
需要高性能的查找、插入、删除,且不关心顺序 HashSet HashSet 提供了近乎 O(1) 的最佳性能。
需要高性能的去重,同时又要保持插入顺序 LinkedHashSet HashSet 的基础上增加了维护顺序的功能,性能依然很好。
  • List 是一个“列表”:它关心的是位置顺序,元素可以重复,想象成一个购物清单,你可以买两斤苹果,清单上会有两个“苹果”。
  • Set 是一个“集合”:它关心的是成员关系唯一性,元素不能重复,想象成一个钱包里的硬币,你不可能有两枚完全相同的1元硬币(面值相同即可视为重复)。

理解它们的核心区别——有序可重复 vs. 无序不可重复——是掌握 Java 集合框架的关键一步。

java list 和 set-图2
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇