Skip to content

集合框架与数据结构总览 ​

学习目标 ​

  • 区分 List、Set、Queue/Deque、Map 的语义和常见实现。
  • 根据索引、去重、顺序、优先级和并发边界选择集合。
  • 通过细粒度页面快速查找 API,而不是在一个大表格里翻找。

核心知识点 ​

专业术语 ​

  • List:有序、可重复、支持位置访问的序列。
  • Set:强调元素唯一性,不提供索引语义。
  • Queue/Deque:表达先进先出、双端进出或栈操作。
  • Map:键到值的映射,键唯一。
  • 不可变集合:创建后不能改变容器结构的集合结果。

白话解释与边界 ​

因为按索引和顺序通常选 ArrayList;需要去重选 HashSet 或 LinkedHashSet;需要排序和范围查询选 TreeSet;需要任务先进先出或栈选 ArrayDeque;需要键值查找选 HashMap。平均 O(1) 不是顺序、线程安全或内存占用的保证,选择实现要先看业务语义。

详细页面:

常用用法 ​

List:保存有序且可重复的元素 ​

需要按位置访问元素并保留重复值时使用 List。

java
// 语义:需要按位置访问元素并保留重复值时使用 List。
import java.util.ArrayList;
import java.util.List;

List<String> values = new ArrayList<>(List.of("a", "a"));
System.out.println(values);
// 输出:[a, a]

Set:保存不重复的元素 ​

需要按相等性去重时使用 Set,是否保留顺序由具体实现决定。

java
// 语义:需要按相等性去重时使用 Set,是否保留顺序由具体实现决定。
import java.util.LinkedHashSet;
import java.util.Set;

Set<String> values = new LinkedHashSet<>(List.of("a", "b", "a"));
System.out.println(values);
// 输出:[a, b]

Queue:按先进先出处理任务 ​

需要按加入顺序消费任务时使用 Queue。

java
// 语义:需要按加入顺序消费任务时使用 Queue。
import java.util.ArrayDeque;
import java.util.Queue;

Queue<String> queue = new ArrayDeque<>();
queue.offer("task");
System.out.println(queue.poll());
// 输出:task

Deque:从两端操作元素 ​

需要双端队列或栈语义时使用 Deque。

java
// 语义:需要双端队列或栈语义时使用 Deque。
import java.util.ArrayDeque;
import java.util.Deque;

Deque<String> stack = new ArrayDeque<>();
stack.push("page");
// 关键变化:stack.push("page");;调用 push,实参为 "page";将 "page" 压入 stack 栈顶;栈状态更新。
System.out.println(stack.pop());
// 输出:page

Map:键值映射入口 ​

需要按唯一键保存和读取值时使用 Map。

java
// 语义:需要按唯一键保存和读取值时使用 Map。
import java.util.LinkedHashMap;
import java.util.Map;

public class MapOverviewDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new LinkedHashMap<>();
        map.put("java", 95);
        System.out.println(map.getOrDefault("sql", 0));
        // 输出:0
    }
}

Map 的键唯一,复合更新优先 merge 或 computeIfAbsent;完整 API 见 Map 常用 API。

按语义选择集合实现 ​

先选择接口表达语义,再选择实现;因为声明变量使用 List、Set、Queue、Map 等接口便于替换实现。

java
// 语义:先选择接口表达语义,再选择实现。
// 具体调用:分别构造空的 ArrayList、HashSet、ArrayDeque 和 HashMap,再读取每个实例的类名。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class CollectionChoiceDemo {
    public static void main(String[] args) {
        System.out.println(new ArrayList<>().getClass().getSimpleName());
        // 输出:ArrayList
        System.out.println(new HashSet<>().getClass().getSimpleName());
        // 输出:HashSet
        System.out.println(new ArrayDeque<>().getClass().getSimpleName());
        // 输出:ArrayDeque
        System.out.println(new HashMap<>().getClass().getSimpleName());
        // 输出:HashMap
    }
}

使用接口类型声明变量 ​

接口变量隐藏具体实现细节;只有确实需要实现特有 API 时才声明具体类型。

java
// 语义:接口变量隐藏具体实现细节。
import java.util.ArrayList;
import java.util.List;

public class CollectionInterfaceDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        System.out.println(names);
        // 输出:[Alice]
    }
}

不常用但需要知道 ​

LinkedList:节点结构不是默认更快 ​

LinkedList 在已知节点位置操作可能方便,但按索引定位仍需遍历,节点对象和缓存局部性也有额外成本;没有明确理由时优先 ArrayList。

java
import java.util.LinkedList;
import java.util.List;

public class LinkedListBoundaryDemo {
    public static void main(String[] args) {
        List<String> list = new LinkedList<>(List.of("a", "b"));
        list.add("c");
        System.out.println(list);
        // 输出:[a, b, c]
    }
}

Arrays.asList:固定大小数组视图 ​

它允许 set 但不能增删;要可变大小列表请复制到 ArrayList。

java
import java.util.Arrays;
import java.util.List;

public class FixedListBoundaryDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b");
        list.set(0, "A");
        // 关键变化:list.set(0, "A");;把 0, "A" 写入 list 的指定位置,返回旧元素;列表状态更新。
        System.out.println(list);
        // 输出:[A, b]
    }
}

Collections.synchronizedList:同步包装 ​

同步包装只保证单次操作,遍历与多步组合仍需要外部同步;并发场景应根据读写模式选择专门集合。

java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SynchronizedListBoundaryDemo {
    public static void main(String[] args) {
        List<String> list =
            Collections.synchronizedList(new ArrayList<>());
            // 关键变化:Collections.synchronizedList(new ArrayList<>());;调用 synchronizedList,实参为 new ArrayList<>();返回对底层 List 访问加同步包装的 List。
        list.add("java");
        synchronized (list) {
            System.out.println(list.size());
            // 输出:1
        }
    }
}

简单案例 ​

java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

public class CollectionsOverviewDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Alice"));
        Queue<String> queue = new ArrayDeque<>();
        queue.offer("task");
        System.out.println(names + " / " + queue.poll());
        // 输出:[Alice] / task
    }
}

同一个业务可以同时需要 List 的顺序和 Queue 的 FIFO 语义;不要为了少写类型就混用集合。

易混点 ​

  • List 允许重复,Set 去重,Queue/Deque 表达进出顺序,Map 通过 key 查 value。
  • ArrayList 与 LinkedList 的性能不能只看 Big-O,还要看定位方式、内存和缓存局部性。
  • Arrays.asList 是固定大小视图,List.of 是不可变集合,ArrayList 才是常见可变列表。
  • 同步包装与不可变集合解决不同问题,任何一个都不会自动形成业务事务。

课后小问 ​

  1. 为什么“需要去重”时不应该继续使用 List.contains 循环? 答案:Set 直接表达唯一性,通常能用哈希或排序结构更清晰地处理重复。 解析:List.contains 循环可能是 O(n²),还要手动维护去重逻辑;若需要保序使用 LinkedHashSet。

  2. 为什么集合 API 页面要拆成多个细页? 答案:List、Set、Queue、Map 的方法和边界不同,按对象拆页才能通过标题快速定位。 解析:总览页负责选择,细页按常用与不常用分组,并在每个 API 标题下直接给 Java 案例。

本节小结 ​

  • 集合选择先看语义,再看顺序、复杂度、内存和并发需求。
  • ArrayList、HashSet、ArrayDeque、HashMap 是常见默认实现。
  • 具体 API 和边界已分别拆到 List、Set、Queue、Map 与排序页面。
  • 同步包装、固定大小视图和不可变集合不能混为一谈。

快速回顾 ​

  • 能按索引、唯一性、进出顺序和键值查找选择接口。
  • 能区分四类集合的核心语义。
  • 能解释 ArrayList、LinkedList、HashSet、ArrayDeque 的边界。
  • 能从总览跳到对应 API 细页。