面灵AI→

美团后端开发一面:手撕四题与 HashMap 线程安全

轮次
一面
结果
已挂
时间
2026-09
来源
牛客网

《面试题目》

  1. 手撕:n 的阶乘
  2. 手撕:合并有序数组
  3. 手撕 SQL:查到某门课的平均值,按平均值和课程号排序
  4. 设计模式有哪些,用过哪些?
  5. HashMap 为什么线程不安全,怎么安全?
  6. 头插法、尾插法有什么区别?
  7. Java 里有哪些容器?
  8. 聊项目,难点,怎么解决?
  9. 聊实习,难点,怎么解决?

《参考解析》

HashMap 为什么线程不安全,要能说到具体机制。 主要问题有三类:① 并发 put 丢数据——两个线程同时对同一个空桶写,后写的会覆盖先写的;JDK 1.7 的头插法还可能在并发扩容时形成环形链表,get 时死循环把 CPU 打满(这是最著名的那个 bug);② size 不准——size 不是原子的,并发自增会丢计数;③ 扩容期间的可见性问题——一个线程在迁移数据、另一个线程读,可能读到旧表或 null。1.8 改成了尾插(环形链表问题消除),但丢数据和 size 不准依然存在。要线程安全有几条路:Collections.synchronizedMap()(全表一把锁,简单但并发度低)、ConcurrentHashMap(首选,1.8 用 CAS + 桶级 synchronized,读操作无锁)、或者干脆每个线程用自己的 map 再合并(无共享则无竞争)。

头插法与尾插法的区别。 这是 JDK 1.7 与 1.8 在 HashMap 冲突链表上的实现差异:头插是把新节点插到链表头部(代码最简单,新插入的元素查询更快一点),尾插是插到尾部。真正的差别在扩容迁移时:头插会反转链表顺序,并发扩容时两个线程互相把对方的节点搬到自己正在处理的链表里,就可能形成环;尾插保持原有顺序,环不会形成。所以「头插法/尾插法」这道题的本质是在问「你知不知道 1.7 那个死循环 bug 的成因」。补充一句:即使 1.8 修了环,HashMap 依然不能在并发场景用,因为丢数据的问题没解决。

Java 容器体系按两条线记。 第一条是 Collection 与 Map 两大分支:Collection 下面是 List(ArrayList 数组、随机访问 O(1);LinkedList 双向链表、头尾增删 O(1))、Set(HashSet 基于 HashMap、LinkedHashSet 保插入序、TreeSet 有序)、Queue(ArrayDeque、PriorityQueue、各种阻塞队列)。Map 下面是 HashMap(无序、允许 null 值)、LinkedHashMap(保插入序或访问序,可做 LRU)、TreeMap(红黑树、按 key 有序)、Hashtable/ConcurrentHashMap(线程安全,后者是现代选择)。第二条线是线程安全版本:CopyOnWriteArrayList(读多写少,写时复制整份数组)、ConcurrentHashMap、BlockingQueue 家族。答题时把「选型依据」一起带上(要不要顺序、要不要并发、读写比例)比单纯罗列容器名有价值。

「n 的阶乘」这类手撕的关键不是算法而是边界。 阶乘增长极快,20! 就已经超出 long 的范围,所以必须问清楚数据范围:小范围就用 long 循环累乘(注意 0! = 1 的约定);大范围就要用大数实现(Java 的 BigInteger,或自己用数组模拟乘法)。面试官出这道题通常就是想看你有没有主动确认范围、有没有意识到溢出——直接写个 int 循环交上去,即使结果对小 n 正确也会被扣分。

合并有序数组要注意原地场景。 如果允许额外空间,双指针从前往后归并最直观,O(m+n)。如果是 LeetCode 88 那种「nums1 后面有足够空间、要求原地合并」的变体,就要从后往前填(先比较两数组末尾的较大者放到 nums1 末尾),否则从前往后会覆盖还没处理的元素。这类题的得分点在于:主动说明两种写法的适用条件、处理其中一个数组先耗尽的剩余部分、以及明确空间复杂度。

SQL 求平均分那题的写法。 基本形态是 SELECT course_id, AVG(score) AS avg_score FROM score_table GROUP BY course_id ORDER BY avg_score, course_id;——考点有三个:分组键和聚合函数要对应(非聚合列必须出现在 GROUP BY 里,否则在严格模式下报错);AVG 会自动忽略 NULL,如果业务上「缺考计 0 分」就要写 AVG(COALESCE(score, 0)),这个区别面试官很喜欢追;排序要按题目要求给出明确的次序(先平均值、再课程号),且要注意 NULL 在排序里的位置。如果题目要求「查到某门课」那就是加 WHERE course_id = ?,此时不需要 GROUP BY。

「聊项目/实习的难点」是这场面试真正的分水岭。 面经里作者觉得氛围轻松却挂得很快,这类情况通常不是技术题答错,而是难点讲得没有纵深。有效的讲法是固定四段:当时的具体约束是什么 → 有哪几个可选方案、你为什么选了这个 → 实现中最大的坑是什么、现象和定位过程 → 结果如何、事后看还能怎么改。特别要避开「难点是需求变更频繁」这类外部归因,面试官想听的是你在技术上的判断和取舍;能把一次真实的 debug 讲清楚(现象 → 缩小范围 → 根因 → 验证),比罗列十个功能更有说服力。