面灵AI→

北京景行锐创(西安 base)后端一面:纯八股,从 HashMap 问到深分页

时间
2026-10
来源
牛客网

《面试题目》

开场

  1. 确认当前的学业与空闲时间情况。
  2. 询问项目的性质(课程/实习/自研)。

Java 并发与集合

  1. HashMap 与 ConcurrentHashMap 的区别是什么?
  2. 既然 ConcurrentHashMap 是线程安全的,为什么大部分代码仍优先使用非线程安全的 HashMap?

Linux

  1. 针对 CentOS 机器卡顿的问题,如何查看占用内存排行前五的进程?

Git

  1. 本地开发分支与主分支合并发生冲突时,如何解决?

MySQL

  1. MySQL 底层索引采用的数据结构有什么特点?
  2. 当用户翻至第 90 万页(深分页)时出现查询极慢,可能是什么原因?如何优化?

Redis

  1. Redis 提供了哪些常用数据结构?分别适用于什么业务场景?
  2. Redis 有哪些高级数据结构?请举例说明其应用场景和优势。

JVM

  1. JVM 内存主要划分为哪些核心区域?
  2. 日常 JVM 调优中涉及哪些参数配置?

《参考解析》

HashMap vs ConcurrentHashMap。 HashMap 非线程安全、允许一个 null key 与多个 null value、用「数组 + 链表 + 红黑树」实现,扩容是整体 rehash;ConcurrentHashMap 线程安全、不允许 null 键值、JDK 8 起用 CAS + synchronized 锁单个桶、支持多线程协助扩容,size 用分段计数。为什么还常用 HashMap:① 绝大多数场景是方法内的局部变量,天然不共享,加并发开销没意义;② ConcurrentHashMap 的每次读写都有内存屏障与 CAS/synchronized 成本,单线程下慢;③ 它禁止 null,语义上更严格,迁移会改变现有行为。选型原则是「共享可变状态才需要并发容器」,而不是「线程安全的总比不安全的好」。

Linux 查内存前五。 ps aux --sort=-%mem | head -6(含表头所以取 6 行),或 ps -eo pid,ppid,%mem,rss,comm --sort=-rss | head -6 按实际物理内存排序;也可以用 top -b -n1 -o %MEM | head。要能补一句「%MEM 是占物理内存百分比,RSS 是实际驻留集;如果怀疑是缓存导致的内存高,看 free -h 的 buff/cache,必要时 echo 3 > /proc/sys/vm/drop_caches 清缓存再观察(生产慎用)」。

Git 冲突解决。 git fetch 拿到最新 → 在本地分支 git rebase origin/main(或 git merge origin/main)→ 冲突文件里按 <<<<<<</=======/>>>>>>> 标记手工合并,保留正确语义而不是简单选一边 → git add 后 git rebase --continue(merge 则 git commit)→ 跑测试 → 推送。要点:冲突解决后必须重新构建与测试;rebase 会改写历史,已经推送并被他人使用的分支不要 rebase;拿不准时先 git stash 或建临时分支备份。

深分页为什么慢。 limit 900000, 20 的语义是「扫描并丢弃前 90 万行」,B+ 树要沿着叶子链表走 90 万次,还要回表取整行,代价随偏移线性增长。三类优化:① 游标/延迟关联——记住上一页最后一条的排序键(where id > last_id order by id limit 20),或用 select id from t order by id limit 900000,20 先在覆盖索引里拿到 20 个 id,再 join 回表取字段(延迟关联,避免 90 万次回表);② 业务层限制——限制最大页数、改成「加载更多」的游标分页;③ 索引与查询改造——确保 order by 的列有合适索引、避免 order by 非索引列导致 filesort。第 90 万页这种量级,正确答案通常是「产品上不该允许跳这么深」,技术上用游标。

Redis 数据结构与场景。 五种基础:String(缓存、计数器、分布式锁、bitmap 签到)、Hash(对象字段,如用户属性,可单独更新字段)、List(消息队列、时间线、栈/队列)、Set(去重、共同好友、抽奖)、Zset(排行榜、延迟队列、按分数范围查询)。高级结构:HyperLogLog(基数统计,UV 估算,误差约 0.81%,内存固定 12KB)、Bitmap(位图,布隆过滤器/签到/在线状态)、GEO(地理位置,附近的人)、Stream(持久化消息队列,支持消费组与 ACK)、以及 RedisJSON/RediSearch 这类模块。回答时把「结构 → 底层编码 → 适用场景」串起来,比如 Zset 小数据量用 ziplist、大了转 skiplist + dict。

JVM 内存区域。 线程私有:程序计数器、虚拟机栈(栈帧:局部变量表、操作数栈、动态链接、返回地址)、本地方法栈;线程共享:堆(新生代 Eden/Survivor、老年代)、方法区(JDK 8 起是元空间,用本地内存)。另有直接内存(NIO 的 DirectByteBuffer)。要能补一句「字符串常量池在 JDK 7 之后移到了堆里」「元空间不设上限时可能吃光物理内存,要设 MaxMetaspaceSize」。

JVM 调优参数。 堆:-Xms/-Xmx(设成相等避免动态扩缩)、-Xmn 或 -XX:NewRatio(新生代比例)、-XX:SurvivorRatio;元空间:-XX:MetaspaceSize/-XX:MaxMetaspaceSize;栈:-Xss;GC:-XX:+UseG1GC(或 ZGC -XX:+UseZGC)、-XX:MaxGCPauseMillis、-XX:ParallelGCThreads;诊断:-XX:+HeapDumpOnOutOfMemoryError、-XX:HeapDumpPath、-Xlog:gc*(JDK 9+ 统一日志);容器里还要注意 -XX:MaxRAMPercentage 让 JVM 按容器限额算堆。调优的顺序永远是「先量(GC 日志、heap dump、火焰图)→ 定位(内存泄漏 / 分配过快 / GC 参数不当)→ 小步改一个参数 → 对比」,而不是背参数表。