Java 中的集合类有哪些?如何分类的?

#Java#面试八股

Java 中的集合类有哪些?如何分类的?

结论先行:集合框架分成两大阵营——Collection(单个元素的集合)与 Map(键值对映射),Map 不继承 Collection。Collection 下再按语义分三支:List 有序可重复、Set 不重复、Queue / Deque 按队列规则进出。按线程安全又能分成基础实现、java.util.concurrent 并发容器、以及 List.of 这类不可变集合。

1. 一张图记住体系

Iterable
└── Collection
    ├── List(有序、可重复、有下标)
    │   ├── ArrayList        数组,随机访问快
    │   ├── LinkedList       双向链表,兼 Deque
    │   ├── Vector           遗留类,方法级同步
    │   └── CopyOnWriteArrayList   写时复制,读多写少
    ├── Set(不重复)
    │   ├── HashSet          哈希,无序
    │   ├── LinkedHashSet    插入顺序
    │   ├── TreeSet          排序(红黑树)
    │   ├── CopyOnWriteArraySet
    │   └── ConcurrentSkipListSet
    └── Queue / Deque(队列)
        ├── ArrayDeque       双端队列,可当栈
        ├── PriorityQueue    小顶堆
        ├── ArrayBlockingQueue / LinkedBlockingQueue
        ├── SynchronousQueue / DelayQueue
        └── ConcurrentLinkedQueue   无锁并发队列

Map(独立接口,不继承 Collection)
├── HashMap            数组 + 链表 + 红黑树
├── LinkedHashMap      额外维护插入/访问顺序
├── TreeMap            红黑树,按 key 排序
├── Hashtable          遗留类,整表锁
├── ConcurrentHashMap  CAS + 桶级锁
├── ConcurrentSkipListMap  跳表,有序并发
├── WeakHashMap        弱引用 key,可被 GC
├── IdentityHashMap    用 == 比较 key
└── EnumMap            枚举 key,数组实现

2. 三个子接口的语义边界

接口有序性允许重复允许 null代表实现并发替代
List有,按插入位置是是ArrayListCopyOnWriteArrayList
Set取决于实现否HashSet 允许 1 个HashSetConcurrentHashMap.newKeySet()
Queue有,按入队顺序是LinkedList 允许ArrayDequeLinkedBlockingQueue
Deque有,两端可操作是ArrayDeque 不允许ArrayDequeLinkedBlockingDeque
Map取决于实现key 不重复HashMap 允许HashMapConcurrentHashMap

3. Map 的选型看“顺序”和“并发”

实现顺序底层适用场景
HashMap无序数组 + 链表 + 红黑树通用键值存储
LinkedHashMap插入顺序或访问顺序HashMap + 双向链表LRU 缓存、保持输出顺序
TreeMapkey 排序红黑树范围查询、排行榜
ConcurrentHashMap无序CAS + 桶级锁并发读写
ConcurrentSkipListMapkey 排序跳表高并发有序场景
WeakHashMap无序哈希 + 弱引用缓存、监听器注册表

4. 代码:按场景选容器

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class CollectionPick {
    public static void main(String[] args) {
        // 去重且保留首次出现顺序
        Set<String> seen = new LinkedHashSet<>();
        seen.add("b");
        seen.add("a");
        seen.add("b");
        System.out.println(seen); // 输出:[b, a]

        // 需要按 key 排序或范围查询
        Map<Integer, String> sorted = new TreeMap<>();
        sorted.put(3, "c");
        sorted.put(1, "a");
        System.out.println(sorted.firstKey()); // 输出:1

        // 栈语义用 ArrayDeque,而不是遗留的 Stack
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(1);
        stack.push(2);
        System.out.println(stack.pop()); // 输出:2

        // 不可变集合:共享安全,修改直接抛异常
        List<String> ro = List.of("a", "b");
        System.out.println(ro.size()); // 输出:2
    }
}

5. 遗留类与常见坑

  • Vector、Hashtable、Stack 都是 JDK 1.0 的遗留实现。Stack 继承 Vector,每个方法都带 synchronized,已被 ArrayDeque 取代。
  • Arrays.asList(array) 返回定长列表,底层就是原数组,add / remove 会抛 UnsupportedOperationException。
  • List.of 返回真正的不可变集合,不接受 null,重复元素直接抛 IllegalArgumentException。
  • Collections.unmodifiableList 是视图,原列表改动仍会反映出来。
  • 并发容器各有适用面:CopyOnWriteArrayList 适合读多写少,ConcurrentLinkedQueue 适合无界高并发队列,BlockingQueue 家族用于生产者-消费者。

常见追问

  • 为什么 Map 不继承 Collection? 语义不同:Collection 是元素容器,Map 是键到值的映射。若强行继承,add 与 put 的签名和语义会冲突,keySet、values 这些视图也难自洽。
  • Collection 和 Collections 有什么区别? 前者是接口,后者是工具类,提供排序、同步包装、不可变包装等静态方法。
  • RandomAccess 有什么用? 空标记接口,告诉通用算法“下标访问是 O(1)”,Collections.binarySearch 据此选择遍历方式。
  • 为什么 ConcurrentHashMap 不允许 null? 并发下无法区分“没有这个 key”和“值就是 null”。
  • 如何快速选容器? 先问三个问题:要不要 key、要不要去重、要不要顺序;再看是否并发。
  • HashMap 和 Hashtable 的初始容量为什么不同? 前者容量取 2 的幂用位运算定位,后者取质数用取模散列。

小结

集合框架的记忆主线是“两个顶层接口 + 三种语义 + 一条并发支线”:Collection 管单元素,Map 管键值对;List 管顺序、Set 管唯一、Queue 管进出规则;并发场景从 java.util.concurrent 里挑对应实现。选型时先确定语义,再看顺序与并发要求,最后才比较常数性能。

笔记加载中…