面试官:HashMap 说一下?谢飞机:哦,那个...装东西的!——互联网大厂 Java 面试现形记

第一轮:基础拷问

面试官:你好,谢飞机同学,请介绍一下你自己,以及你对 Java 集合框架的理解。

谢飞机:您好面试官!我叫谢飞机,爱好是打游戏和看技术博客。Java 集合嘛,就是一堆装数据的工具箱,有 List、Set、Map 这些。我最常用的就是 ArrayList 和 HashMap!

面试官:嗯,不错。那你说说 ArrayList 的底层实现原理是什么?

谢飞机:这个我知道!ArrayList 底层就是一个数组!它会自动扩容,当元素放不下的时候,就会创建一个更大的新数组,然后把老数据搬过去。默认初始容量好像是10。

面试官:很好!回答得很清晰。那 HashMap 呢?它的底层数据结构是什么?

谢飞机:HashMap 啊,哦,那个...也是个装东西的!(挠头)呃...好像是用数组加链表做的,后来又加了红黑树。具体怎么加的...我有点忘了。

面试官:(皱眉)那你能说说什么时候会从链表转成红黑树吗?

谢飞机:嗯...当链表很长的时候?好像是8个?对,就是8个!超过8个就变树!

面试官:...好的。那你知道为什么 HashMap 的数组长度必须是2的幂次方吗?

谢飞机:啊?必须是2的幂吗?我不知道诶,我平时直接 new 就行了。(尴尬地笑)


第二轮:并发与JVM

面试官:我们来聊聊多线程。synchronized 和 ReentrantLock 有什么区别?

谢飞机:synchronized 是关键字,ReentrantLock 是个类。synchronized 用起来简单,ReentrantLock 功能更多,比如可以尝试加锁、可以中断。但是...具体怎么用我有点模糊了。

面试官:那线程池呢?核心参数有哪些?如果任务提交速度远大于处理速度会发生什么?

谢飞机:核心参数...有核心线程数、最大线程数、还有个队列!对,阻塞队列!如果任务太多,队列满了,就会...就会用拒绝策略!拒绝策略有好几种,比如直接抛异常,或者让调用者自己执行。

面试官:嗯,这部分还行。那 JVM 内存模型呢?能画一下吗?

谢飞机:JVM内存...有堆、栈、方法区。堆是放对象的,栈是放局部变量的。方法区放类信息。新生代、老年代我也知道!但是画图...我不会画。(手足无措)

面试官:那常见的垃圾回收器有哪些?G1 和 CMS 的区别是什么?

谢飞机:垃圾回收器...有Serial、Parallel、CMS、G1...区别的话,CMS是并发的,G1可以把堆分成很多小块...然后优先回收垃圾多的块。但是具体怎么工作的,我说不太清楚。


第三轮:框架与中间件

面试官:Spring Boot 的自动装配原理是什么?

谢飞机:哦,这个!是靠 @EnableAutoConfiguration 注解,然后 Spring Boot 会去 META-INF/spring.factories 文件里找配置类,然后根据条件注解 @ConditionalOn... 来决定要不要加载某个 Bean。但是...那些条件注解具体怎么判断的,我就不太清楚了。

面试官:Redis 用过吧?缓存穿透、缓存击穿、缓存雪崩分别是什么?怎么解决?

谢飞机:缓存穿透是查一个不存在的数据,一直打到数据库。可以用布隆过滤器!缓存击穿是一个热点key过期了,瞬间大量请求打过来。可以用互斥锁!缓存雪崩是很多key一起过期,可以用随机过期时间!但是...布隆过滤器怎么实现的,互斥锁具体怎么加,我代码没写过。

面试官:最后一个问题,MySQL 的索引失效场景有哪些?

谢飞机:索引失效...左模糊查询会失效,函数操作会失效,类型转换也会失效。还有...还有...(努力回想)好像 OR 条件用得不对也会失效。具体的例子我想不起来了。

面试官:(叹气)好的,谢飞机同学,今天面试就到这里。你的基础知识还算扎实,但在深度和细节上还需要加强。回去等通知吧。

谢飞机:好的好的,谢谢面试官!我会继续努力的!


技术点详解

1. ArrayList 与 HashMap

  • ArrayList: 底层基于动态数组实现。初始容量为10,当元素数量超过当前容量时,会进行扩容,新容量通常是原容量的1.5倍(oldCapacity + (oldCapacity >> 1))。由于是数组,其随机访问(get/set)性能极佳(O(1)),但插入和删除(尤其是中间位置)性能较差(O(n)),因为需要移动大量元素。
  • HashMap: JDK 1.8 后,底层采用 数组 + 链表/红黑树 的混合结构。
    • 数组长度为2的幂:这是为了在计算元素存储位置(index = hash & (length - 1))时,能通过高效的位运算替代取模运算(hash % length),同时保证哈希值分布均匀。
    • 链表转红黑树:当某个桶(bucket)中的链表长度达到 8,并且数组长度达到 64 时,链表会转换为红黑树,以将查找时间复杂度从 O(n) 降低到 O(log n),防止哈希冲突严重时性能急剧下降。
    • 红黑树转链表:当红黑树中的节点数少于 6 时,会重新转换回链表,因为维护红黑树的开销较大,在节点较少时链表更高效。

2. 多线程、线程池与JVM

  • synchronized vs ReentrantLock:
    • synchronized 是 JVM 内置的关键字,使用简单,由 JVM 自动管理锁的获取和释放(包括异常情况),是非公平锁。
    • ReentrantLock 是 JDK 提供的 API 级别的锁,功能更强大,支持公平/非公平锁、可中断等待、超时获取锁、绑定多个条件变量等。需要手动 lock() 和 unlock(),容易因疏忽导致死锁。
  • 线程池核心参数:
    • corePoolSize: 核心线程数,即使空闲也不会被回收(除非设置了 allowCoreThreadTimeOut)。
    • maximumPoolSize: 最大线程数。
    • workQueue: 任务队列,用于存放等待执行的任务。
    • keepAliveTime: 非核心线程的空闲存活时间。
    • RejectedExecutionHandler: 拒绝策略,当队列满且线程数达到最大时触发。常见策略有 AbortPolicy(抛异常)、CallerRunsPolicy(调用者线程执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  • JVM内存模型与GC:
    • 内存区域: 主要分为线程共享的 堆(Heap,存放对象实例)和 方法区(Method Area,存放类信息、常量、静态变量等),以及线程私有的 虚拟机栈(VM Stack,存放局部变量、方法调用)、本地方法栈(Native Method Stack)和 程序计数器(Program Counter Register)。
    • G1 vs CMS:
      • CMS(Concurrent Mark Sweep): 以获取最短回收停顿时间为目标,主要面向老年代。采用“标记-清除”算法,会产生内存碎片。并发阶段会占用CPU资源,可能导致应用程序变慢。
      • G1(Garbage-First): 面向服务端应用的垃圾收集器,能预测停顿时间。将堆划分为多个大小相等的独立区域(Region),不再区分新生代和老年代。整体采用“标记-整理”算法,局部(两个Region之间)采用“复制”算法,不会产生内存碎片。

3. Spring Boot、Redis 与 MySQL

  • Spring Boot 自动装配: 核心在于 @EnableAutoConfiguration 注解,它会导入 AutoConfigurationImportSelector。该选择器会加载 META-INF/spring.factories 文件中定义的所有自动配置类。每个自动配置类都有一系列 @Conditional 注解(如 @ConditionalOnClass, @ConditionalOnMissingBean),只有满足所有条件,该配置类才会生效,从而完成 Bean 的自动注册。
  • Redis 缓存问题:
    • 缓存穿透: 查询一个数据库中不存在的数据,导致每次请求都穿透到数据库。解决方案:1. 接口层校验参数合法性;2. 对查询结果为空的 key 也进行缓存(设置较短过期时间);3. 使用布隆过滤器(Bloom Filter)快速判断 key 是否可能存在。
    • 缓存击穿: 一个热点 key 在缓存中过期的瞬间,大量并发请求同时打到数据库。解决方案:1. 设置热点数据永不过期;2. 使用互斥锁(如 Redis 的 SETNX),只让一个线程去数据库加载数据并回填缓存,其他线程等待。
    • 缓存雪崩: 大量 key 在同一时间过期,或 Redis 宕机,导致所有请求都打到数据库。解决方案:1. 给 key 的过期时间加上随机值,避免集体失效;2. 使用高可用架构(如 Redis Sentinel, Redis Cluster);3. 服务降级和熔断。
  • MySQL 索引失效场景:
    • 对字段进行函数或表达式操作: 如 WHERE YEAR(create_time) = 2023。
    • 隐式类型转换: 如字符串字段用数字查询 WHERE phone = 13800138000。
    • 左模糊查询: WHERE name LIKE '%john'。
    • 使用不等于 (!= 或 <>) 操作符。
    • OR 条件中部分字段无索引: 如果 OR 连接的条件中有一个字段没有索引,可能导致整个查询不走索引。
    • 联合索引不遵循最左前缀原则: 如联合索引 (a, b, c),查询条件 WHERE b = ? AND c = ? 无法使用该索引。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐