Java 集合框架全景:一张图记住所有接口

Java 集合框架是最常用也最容易用错的部分。它的核心是两个顶层接口:Collection 和 Map。Collection 下面分 List(有序可重复)、Set(无序不重复)、Queue(队列)三个子接口;Map 是键值对结构,独立于 Collection 体系。

记住这个分类就不会选错:需要顺序存东西用 List、需要去重用 Set、需要按键查值用 Map、需要排队用 Queue。每一类下面又有多个实现类,区别在于底层数据结构和性能特性。

选集合先想清楚三个问题:要不要顺序?要不要去重?要不要按键查值?
本文核心知识点
  • ArrayList基于动态数组,随机访问快,中间插入慢。
  • HashMap数组加链表加红黑树,JDK 8 后链表长度超 8 转红黑树。
  • ConcurrentHashMap线程安全的 Map,分段锁优化,并发场景首选。
  • 选型普通场景用 ArrayList 和 HashMap,并发场景换 ConcurrentHashMap。
Java 集合框架示意:蓝色渐变背景左侧堆叠方块代表列表,中间树状网络节点代表映射,右侧漏斗过滤容器代表队列
集合框架三大类:列表(堆叠方块)、映射(树状节点)、队列(漏斗过滤),各自适合不同的业务场景。

List 家族:ArrayList vs LinkedList

List 有两个主要实现:ArrayList 和 LinkedList。面试常问区别,实际开发中 95% 的情况都用 ArrayList。

特性ArrayListLinkedList
底层结构动态数组双向链表
随机访问 get(i)O(1) 快O(n) 慢
尾部添加均摊 O(1)O(1)
中间插入删除O(n) 要搬移元素O(1) 改指针
内存占用连续数组,紧凑每个节点多两个指针
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
names.add(1, "王五");  // 在索引 1 插入

// 遍历
for (String name : names) {
    System.out.println(name);
}

// Java 8 流式操作
names.stream()
     .filter(n -> n.startsWith("张"))
     .forEach(System.out::println);

什么时候用 LinkedList?当你需要频繁在头部插入删除(比如做队列),或者数据量很大且频繁中间插入。但实际项目中这种场景极少,大多数时候用 ArrayList 就够了。

Set 家族:HashSet 与 TreeSet 的区别

Set 最核心的特性是不允许重复元素。最常用的是 HashSet,底层就是一个 HashMap(value 是固定占位对象)。需要排序时用 TreeSet,底层是红黑树。

Set<String> tags = new HashSet<>();
tags.add("Java");
tags.add("Python");
tags.add("Java");  // 重复,不会加入

System.out.println(tags.size());  // 2

// 去重场景:列表转 Set 去重
List<String> raw = List.of("a", "b", "a", "c", "b");
Set<String> unique = new HashSet<>(raw);
System.out.println(unique);  // [a, b, c]

// TreeSet 自动排序
Set<Integer> nums = new TreeSet<>();
nums.add(30);
nums.add(10);
nums.add(20);
System.out.println(nums);  // [10, 20, 30]

判断重复的依据是 equals 和 hashCode。自定义对象放进 HashSet 时,必须同时重写 equals 和 hashCode,否则会出现"看起来一样但被当成不同对象"的 bug。

Map 家族:HashMap 的底层原理

HashMap 是 Java 里用得最多的集合。理解它的底层原理能帮你写出更高效的代码。JDK 8 的 HashMap 底层是数组加链表加红黑树:

  • put 一个键值对时,先对 key 算 hash,映射到数组下标。
  • 那个位置如果是空的,直接放进去。
  • 如果已经有元素(哈希冲突),就把新元素接到链表尾部。
  • 当链表长度超过 8 且数组容量大于 64 时,链表转成红黑树,查找从 O(n) 降到 O(log n)。
Map<String, Integer> scores = new HashMap<>();
scores.put("张三", 90);
scores.put("李四", 85);
scores.putIfAbsent("王五", 100);  // 不存在才放

// 遍历键值对
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// Java 8 新增方法
scores.getOrDefault("赵六", 0);       // 不存在返回默认值
scores.replace("张三", 95);            // 替换
scores.computeIfAbsent("钱七", k -> k.length());  // 不存在时计算值

性能要点:HashMap 默认初始容量是 16,负载因子 0.75。如果预知数据量大,创建时指定初始容量避免频繁扩容:new HashMap<>(128)。