news 2026/6/11 10:53:41

Java进阶必修课:List、Set、Map选型+底层原理+避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java进阶必修课:List、Set、Map选型+底层原理+避坑指南

写 Java 的人,基本绕不开集合。平时开发里,List、Set、Map 天天都在用,很多人也觉得自己已经挺熟了。可真到了面试,或者项目里碰到性能、并发、源码细节这些问题时,才发现自己对集合的理解其实并不扎实。

比如 ArrayList 和 LinkedList 到底该怎么选,HashMap 为什么查询快,HashSet 去重靠的是什么,subList() 为什么一不小心就埋坑。这些问题单看都不难,但一旦连起来问,很多人就容易乱。

这篇文章不准备照着教材把集合类挨个讲一遍,而是站在开发和面试两个角度,把 Java 集合里最常用、最容易问、也最容易踩坑的内容捋顺。你看完之后,至少能把选型思路、核心原理和高频问题弄明白。

1. Java 集合别死记,先抓住这两条主线

Java 集合框架看起来一大堆类,实际上先抓住两条主线就够了:

  • Collection:单列集合,一个一个存元素
  • Map:双列集合,以 key-value 方式存数据

而 Collection 下面,又可以继续拆成三类:

  • List:有序,可重复
  • Set:不可重复
  • Queue:队列结构

用一张简单的结构图来看会更直观:

Collection ├─ List │ ├─ ArrayList │ ├─ LinkedList │ └─ Vector ├─ Set │ ├─ HashSet │ ├─ LinkedHashSet │ └─ TreeSet └─ Queue ├─ LinkedList └─ ArrayDeque Map ├─ HashMap ├─ LinkedHashMap ├─ Hashtable ├─ TreeMap └─ ConcurrentHashMap

这张图不用全背,但你至少要意识到一件事:工程里最常打交道的集合,并没有那么多。

真正的主角,其实长期都是下面这几个:

  • ArrayList
  • HashSet
  • HashMap
  • TreeMap
  • ConcurrentHashMap

把这几个吃透,集合这章基本就站住了。

2. List、Set、Map 到底怎么选,别再凭感觉了

很多初学者写代码时选集合,基本靠手感。想到列表就 List,想到键值对就 Map,剩下的能跑就行。短期看没问题,长期看会吃亏。

2.1 需要顺序、允许重复,用 List

只要你的数据需要“按顺序放着”,并且允许重复,优先考虑 List。

典型场景:

  • 数据库查出来的一批用户
  • 页面上的商品列表
  • 按时间顺序记录的操作日志

而在 List 里,最常用的两个实现类是:

  • ArrayList
  • LinkedList

但这里有个很重要的结论:大多数业务场景里,默认优先 ArrayList。

2.2 需要去重,用 Set

如果你的核心需求不是“存一批数据”,而是“确保数据不重复”,那就该想到 Set。

比如:

  • 一批手机号去重
  • 用户标签去重
  • 判断某个元素是否已经存在

常见实现类:

  • HashSet:最常用,去重快
  • LinkedHashSet:去重的同时保留插入顺序
  • TreeSet:自动排序去重

2.3 需要映射关系,用 Map

只要数据天然是“一个 key 对应一个 value”,就优先用 Map。

典型场景:

  • 用户 ID 对应用户对象
  • 配置项名称对应配置值
  • 单词对应出现次数

常见实现类:

  • HashMap
  • LinkedHashMap
  • TreeMap
  • ConcurrentHashMap

如果你现在只能记一条经验,那就记这一句:

默认列表选 ArrayList,默认映射选 HashMap,默认去重选 HashSet。

3. 为什么大家都在用 ArrayList,而不是 LinkedList

这是集合里最典型的“看上去会,实际上容易答偏”的问题。

很多人第一反应是:链表插入删除快,所以 LinkedList 应该更适合业务开发。这个说法只说对了一半。

3.1 ArrayList 的底层是动态数组

ArrayList 底层是动态数组,所以它的特点非常鲜明:

  • 支持随机访问,get(index) 很快
  • 尾部追加元素效率高
  • 中间插入、删除需要移动元素
  • 容量不够时会扩容

示例:

List<String> list = new ArrayList<>(); list.add("Java"); list.add("Spring"); list.add("MySQL"); System.out.println(list.get(1)); // Spring

3.2 LinkedList 的底层是双向链表

LinkedList 的优势在于:

  • 头尾插入删除方便
  • 不需要像数组那样整体搬迁元素

但它的问题也很明显:

  • 随机访问慢
  • 查找某个位置时需要遍历
  • CPU 缓存友好性通常不如数组

所以真实项目里,LinkedList 并没有很多人想象中那么常用。

更准确的理解应该是:

  • 普通业务列表:优先 ArrayList
  • 频繁头尾操作:再考虑 LinkedList 或 ArrayDeque

这也是为什么很多人写了几年 Java,项目里看到的 ArrayList 数量远远多于 LinkedList。

4. Set 为什么能去重,核心就在 equals 和 hashCode

集合里有一个点,面试一定会问,项目里也一定会碰到,那就是:Set 为什么能去重?

以 HashSet 为例,它底层其实是基于 HashMap 实现的。换句话说,HashSet 的去重能力,本质上来自 HashMap 的 key 不可重复。

当我们往 HashSet 里放对象时,通常会经历两个关键判断:

  1. 先比较 hashCode()
  2. 如果哈希值相同,再比较 equals()

所以一定要记住这个结论:

重写了 equals(),就必须同时重写 hashCode()。

来看一个例子:

class User { private String name; public User(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof User)) { return false; } User user = (User) o; return Objects.equals(name, user.name); } @Override public int hashCode() { return Objects.hash(name); } }

如果你只重写 equals() 不重写 hashCode(),那么逻辑相等的两个对象,可能会因为哈希值不同而落到不同位置,最终导致 HashSet 去重失败。

这个坑,真不是只存在于面试题里。业务代码里自定义对象放入 Set、作为 Map 的 key 时,经常就会踩到。

5. HashMap 为什么这么重要,甚至可以说是集合的核心

如果说 Java 集合里有一个类是“你绕不过去的最终 boss”,那基本就是 HashMap。

5.1 HashMap 的底层结构,到底是什么

JDK 8 里的 HashMap,底层结构可以概括成一句话:

数组 + 链表 + 红黑树

它的工作过程大致是这样的:

  1. 先通过 hash 计算桶位置
  2. 如果桶里没有元素,直接放进去
  3. 如果有冲突,就先挂到链表上
  4. 如果链表太长,再转成红黑树

为什么要这么设计?

因为理想情况下,哈希定位能让查找接近 O(1);但一旦冲突多了,链表会让性能变差,于是 JDK 8 用红黑树来兜底。

5.2 HashMap 为什么查找快

说白了,HashMap 快,不是因为它“遍历得快”,而是因为它大多数时候根本不用全量遍历。

它先用 hash 把查找范围压缩到某个桶,再在这个桶里继续判断。只要哈希分布比较均匀,效率就会很高。

这也是为什么 HashMap 在业务开发中几乎随处可见。

5.3 关于 HashMap,你至少还要知道这几件事

1)它允许 null

HashMap:

  • 允许 null key
  • 允许 null value

但 ConcurrentHashMap 不允许,这是面试里的高频对比点。

2)已知数据量时,尽量初始化容量

如果你大概知道要放多少元素,创建时最好顺手指定容量,避免频繁扩容。

Map<String, Integer> map = new HashMap<>(16);

在工程规范里,这也是一个很实用的优化习惯。

3)遍历时优先 entrySet

遍历 Map 时,优先用 entrySet(),不要总是先拿 keySet() 再去 get()。

for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); }

6. TreeMap 和 TreeSet 不是冷门,它们只是有明确适用场景

很多人学集合时会把 TreeMap、TreeSet 当成“知道有这个东西就行”,其实没那么简单。

它们底层基于红黑树,最大的特点就是:

  • 自动排序
  • 支持有序遍历
  • 适合范围查询

比如这些场景就很合适:

  • 按分数排序
  • 按日期维护数据
  • 需要找“大于某个值的最小 key”

示例:

Map<Integer, String> treeMap = new TreeMap<>(); treeMap.put(3, "C"); treeMap.put(1, "A"); treeMap.put(2, "B"); System.out.println(treeMap); // {1=A, 2=B, 3=C}

注意,TreeMap 和 TreeSet 要么依赖元素实现 Comparable,要么创建时显式传入 Comparator。

7. 并发环境下,别再默认用 HashMap 了

单线程环境用 HashMap 很正常,但只要你进入并发读写场景,就不能继续想当然了。

这时候更合适的选择通常是 ConcurrentHashMap。

Map<String, Integer> counterMap = new ConcurrentHashMap<>(); counterMap.put("Java", 1); counterMap.put("Spring", 2);

它值得记住的点有三个:

  • 线程安全
  • 并发性能明显优于老的 Hashtable
  • 不允许 null key 和 null value

很多老八股还在围着 Hashtable 讲线程安全,但真实开发里,优先考虑的基本都是 ConcurrentHashMap。

8. 这些集合坑,项目里真的太常见了

如果说源码考点更多是为面试准备,那下面这些坑就真的是为项目避雷准备的。

8.1 Arrays.asList() 不是普通可变 List

很多人会这样写:

List<String> list = Arrays.asList("A", "B", "C"); list.add("D");

然后程序直接报错。

原因是 Arrays.asList() 返回的是固定长度列表,不能随便增删。

如果你需要的是可变 List,应该这样写:

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); list.add("D");

8.2 subList() 返回的是视图,不是副本

这个坑非常经典。

List<String> sub = oldList.subList(0, 2);

很多人下意识会觉得 sub 是一个新的列表,但其实它只是原列表的一个视图。原集合结构一变,子集合就可能跟着出问题,甚至抛出 ConcurrentModificationException。

如果你想真正拷贝一份,正确做法是:

List<String> newList = new ArrayList<>(oldList.subList(0, 2));

8.3 foreach 里不要直接删元素

错误写法:

for (String item : list) { if ("A".equals(item)) { list.remove(item); } }

这类代码很容易触发 ConcurrentModificationException。

更稳妥的方式是 Iterator:

Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("A".equals(item)) { iterator.remove(); } }

8.4 Collectors.toMap() 很容易因为重复 key 报错

下面这段写法看起来很正常,但一旦 key 重复就会抛异常:

Map<String, Integer> map = list.stream() .collect(Collectors.toMap(User::getName, User::getAge));

更稳的写法,是显式指定 merge 函数:

Map<String, Integer> map = list.stream() .collect(Collectors.toMap( User::getName, User::getAge, (oldValue, newValue) -> newValue ));

另外,规范里还特别提醒过:如果 value 为 null,这里也可能出问题。

8.5 Collections.emptyList() 不能改

下面这种写法一样会出现问题:

List<String> list = Collections.emptyList(); list.add("Java");

因为它返回的是不可变空集合。

如果你的语义只是“这里暂时没有数据”,它很好用;但如果你后面还要继续往里加元素,就不要这么写。

9. 一张表,帮你把集合选型记清楚

场景推荐集合
普通有序列表ArrayList
频繁头尾操作LinkedList / ArrayDeque
去重HashSet
保留插入顺序的去重LinkedHashSet
自动排序去重TreeSet
普通键值存储HashMap
保持插入顺序的映射LinkedHashMap
自动按 key 排序TreeMap
并发键值存储ConcurrentHashMap

如果你想把这一章真正学扎实,我建议至少把下面这套默认心智模型建立起来:

  • 默认列表:ArrayList
  • 默认去重:HashSet
  • 默认映射:HashMap
  • 并发映射:ConcurrentHashMap
  • 需要排序:TreeMap / TreeSet
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/11 10:52:31

中小企业财务合规避坑指南:5个常见误区与AI辅助解决方案

一、为什么财务合规是中小企业的生死线&#xff1f;在金税四期全面上线、税务监管从"以票管税"向"以数治税"转型的大背景下&#xff0c;财务合规已不再是可有可无的"加分项"&#xff0c;而是企业存续的"及格线"。根据税务总局公开数据…

作者头像 李华
网站建设 2026/6/11 10:51:58

从视觉识别到精准控制:基于STM32与K210的云台追踪系统设计

1. 项目背景与核心思路 第一次接触视觉追踪系统时&#xff0c;我被摄像头自动锁定移动物体的效果深深吸引。这种技术在工业分拣、智能安防等领域有广泛应用&#xff0c;但很多人不知道用百元级开发板就能实现。这次我们用K210做"眼睛"&#xff0c;STM32当"大脑&…

作者头像 李华
网站建设 2026/6/11 10:51:51

AI 生成 PR 正在刷爆开源项目:GitHub 贡献信号为什么失灵了?

AI 让低质量开源 PR 被批量制造&#xff0c;真正问题是招聘激励污染了贡献信号。 原文链接&#xff1a;AI 小老六 开源项目收到越来越多莫名其妙的 PR&#xff0c;不一定是因为突然多了很多热心贡献者。很多时候&#xff0c;那只是一个求职信号正在被批量制造。 软件行业长期鼓…

作者头像 李华
网站建设 2026/6/11 10:51:12

okbiye AI PPT:毕业论文答辩演示文稿的智能减负新方案

okbiye-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPTAI PPT制作 - Okbiye智能写作https://www.okbiye.com/ppt 完成毕业论文的撰写、降重与格式校准之后&#xff0c;答辩 PPT 制作又成了横在毕业生面前的一道难关。不少学子花费数日打磨论文正文&#xff…

作者头像 李华
网站建设 2026/6/11 10:50:55

Seraphine:3步快速掌握英雄联盟智能辅助工具

Seraphine&#xff1a;3步快速掌握英雄联盟智能辅助工具 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine Seraphine是一款专为英雄联盟玩家设计的智能辅助工具&#xff0c;通过LCU API与游戏客户端深度集成&am…

作者头像 李华
网站建设 2026/6/11 10:50:53

GitHub汉化插件终极指南:3分钟让GitHub界面说中文

GitHub汉化插件终极指南&#xff1a;3分钟让GitHub界面说中文 【免费下载链接】github-chinese GitHub 汉化插件&#xff0c;GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 还在为GitHub全英文界面…

作者头像 李华