尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java面试八股文系统整理:基础、集合、JVM、并发全覆盖

Java面试八股文系统整理:基础、集合、JVM、并发全覆盖 “Java面试八股文”这几个字我过去几年在面候选人的时候听过无数遍。有人靠它突击进大厂也有人背了一个月却连“为什么”都答不上来。这份整理不是让你打印下来死记硬背的而是想给你一张相对完整的复习地图Java基础、集合、JVM、并发、Spring/MySQL/Redis、手写算法再加上几个很容易在实际面试里翻车的编译和环境问题。如果你正在准备校招、社招或者工作几年想系统查漏补缺这篇内容应该比单纯刷题更有参考价值。我先把话说在前面没有任何一份八股文合集敢说“全网最全”标题多少带点夸张。真正值得参考的是它把高频考点串起来了并且每个点都回答了“面试官为什么爱问这个”。明白这层逻辑你背的每个答案才立得住。1. 为什么我决定整理这份Java八股文以及怎么用才不白背1.1 八股文不是背出来的是理解出来的我在技术面试里见过太多类似场景候选人把HashMap的底层结构背得很顺“数组加链表JDK8之后还会转红黑树”但追问一句“为什么链表转红黑树的阈值是8”就卡住了。背题和理解的差距恰恰就在这个“为什么”上。面试官大多数时候不是要考倒你而是想通过追问判断你是不是真的掌握。八股文只是外层答案内层是源码、数据结构、操作系统和网络的基础知识。你背的是结论但面试官要的是你推导结论的过程。所以我建议这样使用这份整理先看问题试着用自己的话说一遍再看答案对照漏掉了哪些关键点。如果能把每个核心结论都讲出“因为什么所以什么”这场面试的容错率会高很多。1.2 这份整理覆盖了哪些方向复习优先级怎么排下面这张表是我整理复习路径时的依据不是全部知识点但覆盖了Java岗位面试里出现频率最高的几大块。优先级是结合我自己面试别人和经验复盘排的仅供参考。方向核心考点常见追问方向面试优先级Java基础String、equals/hashCode、异常体系创建几个对象、为什么重写规则高集合框架HashMap、ConcurrentHashMap、ArrayList扩容机制、源码细节、线程安全高JVM内存区域、GC、类加载、OOM排查内存泄漏场景、常用收集器原理高并发编程synchronized、volatile、线程池、AQS锁升级、线程池参数、底层实现高框架中间件Spring、MySQL、RedisBean生命周期、索引优化、缓存问题中高算法与工程排序、Lambda/Stream、编译问题手写代码、代码输出结果中如果你只有一到两周准备时间优先把前四块吃透如果时间充裕再把框架、中间件和手写算法补齐。Java后端技术面最看重的前四块内容也是候选人差距最容易拉开的地方。2. Java基础面试题从String到异常藏在八股文底层的原理2.1 String的不可变设计与“创建几个对象”陷阱String是Java里被问得最频繁的类之一。它的不可变性表现在一旦创建字符序列不能改变。JDK8及之前String内部是char[]JDK9开始改成了byte[]并且增加了一个编码标记位因为大多数字符是拉丁字符用一个字节存储能省不少内存。为什么设计成不可变三个核心好处第一字符串常量池可以缓存两个引用指向同一个String对象时不用担心被改坏第二String的hashCode可以安全缓存第一次计算后复用HashMap用String做key才高效第三多个线程可以共享同一个String对象天然线程安全。经典面试题是String s new String(abc)创建了几个对象。答案不固定如果常量池里已经有“abc”那么只创建一个堆对象如果常量池里还没有“abc”JVM会先在常量池中创建“abc”再在堆中创建一个String对象总共两个。如果再考intern()你可以答手动把字符串放入常量池如果池中已有则返回池中对象这样可以减少重复字符串对象。2.2 为什么重写equals就必须重写hashCode这个问题几乎每次面试都会出现。最直观的解释是HashMap和HashSet这类基于哈希的集合先通过hashCode()计算桶的位置再通过equals()比较链表中是否存在相同元素。如果两个业务上相同的对象hashCode()不同它们就会被放到不同桶里get的时候找不到put的时候还能插入两个“逻辑重复”的key。可以这样理解hashCode是房间号equals是房间里的人。别人要找张三先通过房间号找到房间再看看房间里的人是不是张三。如果房号对不上连房间都进不去。实际操作中重写equals时也要把参与比较的字段在hashCode里用相同的规则算一遍。比如用Objects.hash(name, age)生成哈希码保证相等的对象哈希值一定相等。IDE生成的equals和hashCode通常就是规范写法直接使用就可以。2.3 final、finally、finalize不要再答老一套final修饰类表示不能被继承修饰方法表示不能重写修饰变量表示引用一旦赋值不能改变。这里的“不能改变”要理解准确如果变量引用的是一个对象对象内部字段仍然可以修改只是引用不能指向新对象。finally是异常处理结构中的代码块无论try里是否抛出异常finally都会执行常见用途是释放资源。这里有个细节如果finally里有return它会覆盖try或catch中的return。实际写代码时不要在finally里做返回操作否则会导致异常和返回值被吞掉。finalize完全不是同一类问题。它早期用于GC回收前的清理但从Java 9开始被标记为废弃因为它执行时机不确定、性能差还可能引发对象复活问题。现在面试官问这个更多是想听你知不知道它已经被淘汰。可替代方案是用try-with-resources和AutoCloseable这是JDK7引入的常规资源关闭方式。2.4 异常体系受检异常和运行时异常怎么选Java异常体系的根是Throwable下面分Error和Exception。Error表示JVM层面的严重问题比如OutOfMemoryError、StackOverflowError应用程序一般处理不了。Exception又分受检异常和运行时异常。受检异常必须显式捕获或声明抛出例如IOException、SQLException运行时异常继承RuntimeException不需要强制处理例如NullPointerException、IllegalArgumentException。面试里还有一个容易混淆的点ClassNotFoundException和NoClassDefFoundError。前者是代码尝试用Class.forName或ClassLoader.loadClass加载类时类不存在后者是JVM在编译期类存在、运行期类定义却找不到比如依赖没打包完整或静态初始化失败。这两个概念能分清楚说明你对类加载机制有基本认识。实践建议只有一句不要用catch(Exception e) {}把异常吞掉。至少打日志并保留原始异常对象否则线上问题查起来会非常痛苦。3. 集合框架高频考点HashMap、ConcurrentHashMap的源码级理解3.1 HashMap的put流程、扩容和树化阈值HashMap从JDK8开始是“数组 链表 红黑树”的结构。初始容量是16默认负载因子0.75。put一个key时流程大致是先通过hash(key)扰动计算哈希值让高位也参与运算再用(n - 1) hash定位到数组下标。如果这个位置为空直接放进去如果发生哈希冲突就在链表尾部插入节点。链表长度达到8并且数组容量达到64时链表转为红黑树如果数组容量没到64会先扩容。扩容机制也是必问点。默认容量必须是2的幂这样才能用位运算代替取模扩容时元素要么留在原位置要么移动到原位置加旧容量的位置重哈希开销小。每次扩容为原来的两倍加载因子0.75是空间和时间折中的结果。为什么树化阈值是8因为当哈希函数分布足够均匀时用泊松分布计算链表长度达到8的概率非常低设置8是作为极端冲突场景的兜底。容易翻车的还有JDK1.7和JDK8的区别。JDK1.7用头插法并发扩容时可能形成环形链表导致死循环JDK8改为尾插法死循环问题在单链表层面有所缓解但并发下put仍然可能丢数据。结论是并发场景请用ConcurrentHashMap不要自己去做额外同步。3.2 JDK8的ConcurrentHashMap为什么放弃分段锁JDK7的ConcurrentHashMap使用Segment分段锁每个Segment继承ReentrantLock把整个map分成16段线程只锁自己操作的段。这种设计锁粒度比较粗而且Segments数量初始化后不好扩展。JDK8彻底改成了CAS synchronized。插入数据时如果对应桶为空直接用CAS尝试放入新节点避免加锁开销如果桶不为空就锁住桶里的头节点。synchronized在JDK6之后做了锁升级优化低竞争场景下开销很小所以这个方案比分段锁更轻量。面试还会追问为什么size()不是精确值。因为并发情况下没法对每个桶加锁来统计JDK8使用baseCount加CounterCell数组size()返回的是合并后的近似值。另一个细节是mappingCount()返回longsize()返回int数据量很大时用mappingCount()更安全。3.3 ArrayList扩容为什么是1.5倍LinkedList增删真的快吗ArrayList默认初始容量是10达到上限后扩容为原来的1.5倍即oldCapacity (oldCapacity 1)然后调用Arrays.copyOf拷贝到新数组。为什么不直接扩容2倍因为1.5倍相对保守在均摊复杂度O(1)的前提下减少内存浪费。LinkedList基于双向链表实现。很多人背结论“LinkedList增删快ArrayList查询快”但这不够严谨。LinkedList如果已知要删除的节点引用确实是O(1)但如果只知道下标删除前还要从头遍历定位时间复杂度是O(n)。ArrayList随机访问是O(1)尾部和末尾增删在均摊意义下也是O(1)但中间插入和删除因为要移动元素是O(n)。实际业务里如果不涉及频繁的中间插入删除ArrayList通常是默认选择因为连续内存对CPU缓存更友好。还有一个高频坑在循环里用list.remove(i)删除多个元素删除后下标会变化容易漏删。正确做法是使用Iterator的remove方法或者直接用removeIf。4. JVM与内存问题OOM、GC和类加载的面试深挖4.1 运行时数据区哪些线程私有哪些线程共享JVM运行时数据区是JVM面试的地基。线程私有的有三块程序计数器、虚拟机栈、本地方法栈。线程共享的有两块堆、方法区。JDK8之后方法区用元空间实现字符串常量池被移到了堆中。虚拟机栈对应每个Java方法调用方法执行时创建栈帧栈帧里有局部变量表、操作数栈、动态链接、方法出口。递归调用太深会抛StackOverflowError线程请求栈深度超出虚拟机允许范围则抛OutOfMemoryError。堆是所有对象实例和数组的分配区域也是GC的主要战场。方法区存放类元信息、运行时常量池等。JDK8之前方法区叫永久代JDK8换成了元空间。为什么换因为永久代的大小难以确定而且类元数据的回收在Full GC时容易引发性能问题。元空间使用本地内存默认不设上限避免永久代导致的OutOfMemoryError: PermGen space。4.2 “Insufficient memory”类OOM的排查链路实际开发中java.lang.OutOfMemoryError并不少见。很多人一看到OOM就拍脑袋加-Xmx但根源往往不是堆太小而是存在对象无法被回收。比如静态集合持有大量对象、ThreadLocal没有清理、数据库连接池设置过大、ClassLoader泄漏导致类无法卸载。排查OOM时我一般按这个链路走启动参数加上-Xms和-Xmx并且开启GC日志线上保留GC日志很关键。用jstat -gcutil pid观察GC频率用jmap -dump:formatb,fileheap.hprof pid导出堆转储。使用MAT或JProfiler打开堆转储重点看支配树里的大对象以及org.eclipse.mat.parser等分析报告。重点排查这些位置静态Map/List、缓存框架、未释放的IO流、线程池中的任务对象、ThreadLocal值未remove。如果是容器环境还要注意JVM无法感知容器内存限制时可能被cgroup OOM Killer直接杀掉此时系统日志里会有对应记录。一个补充点如果你看到的是OutOfMemoryError: Insufficient memory这种信息可能不是Java堆的问题而是进程启动时无法从操作系统拿到足够内存常见于物理机内存不足、容器内存限制或者同时启动太多JVM实例。处理思路是检查可用内存、减少实例数量或调整占用。4.3 GC算法与常用收集器垃圾回收算法有三个基础款标记-清除、标记-复制、标记-整理。标记-清除会产生大量碎片标记-复制适合新生代因为对象存活率低浪费空间不大HotSpot把新生代分成Eden和两块Survivor默认比例8:1:1标记-整理适合老年代移动对象消除碎片但移动成本高。分代收集把堆分成新生代和老年代。新生代对象朝生夕死回收后存活对象晋升老年代。Minor GC触发条件是Eden区满Full GC触发条件比较复杂包括老年代空间不足、元空间不足、System.gc()被调用、CMS并发清理失败转Serial Old等。面试比较常问G1和ZGC。G1把堆分成大小相同的Region维护一个可预测的停顿时间模型适合大堆和多核环境。ZGC使用着色指针和读屏障暂停时间通常不超过10毫秒适合超大堆低延迟场景。你不需要背完整源码但至少要能说清楚“G1和CMS最大的区别是Region化、可预测停顿、以及不需要老年代和新生代物理隔离”。4.4 类加载过程与双亲委派类加载过程包括加载、验证、准备、解析、初始化五个阶段。加载就是根据全限定名读取字节流生成Class对象准备阶段为静态变量分配内存并设置零值初始化阶段执行静态变量赋值和静态代码块。双亲委派机制是类加载器先让父加载器尝试加载父加载器加载不了才由自己加载。这样做的目的是防止核心类被自定义类篡改也避免同一个类被不同加载器重复加载。Bootstrap ClassLoader加载rt.jar中的核心类Platform/Extension ClassLoader加载扩展库Application ClassLoader加载classpath下的类。面试常追问“什么场景需要打破双亲委派”。最典型的例子是JDBCDriverManager在启动类加载器加载的rt.jar里但具体数据库驱动在classpath下启动类加载器加载不到所以JDBC通过ServiceLoader机制由线程上下文类加载器加载驱动实现。另一个常见场景是Tomcat为了支持多个Web应用之间类隔离会破坏双亲委派。5. 并发编程synchronized、volatile、线程池与AQS5.1 synchronized到底锁的什么锁升级过程synchronized锁的是对象不是代码块。每个Java对象头里都有Mark Word记录了锁状态。JDK6之后锁可以升级基本路径是无锁 - 偏向锁 - 轻量级锁 - 重量级锁。偏向锁的意思是第一个获取锁的线程把锁记录在对象头里后续再进入同步块只要检查偏向线程是不是自己不需要做CAS。一旦发生竞争偏向模式撤销升级为轻量级锁。轻量级锁通过自旋等待持有锁的线程释放自旋失败则膨胀为重量级锁依赖操作系统的Monitor实现涉及用户态和内核态切换开销最大。这里有个容易混淆的点轻量级锁失败不一定立刻升级重量级锁JVM会采用自适应自旋根据历史自旋结果动态调整。另外JDK15起默认禁用了偏向锁因为现代应用线程竞争普遍偏向锁带来的收益已经不明显。你面试时能说出这个版本变化会显得平时跟得比较紧。5.2 volatile的内存语义以及和synchronized的区别volatile有两个核心语义保证可见性、禁止指令重排序。它不保证原子性。JMMJava内存模型规定每个线程有工作内存volatile变量的读写直接操作主内存并且通过内存屏障防止相关指令被重排。最经典的例子是单例模式的DCL写法。new一个对象在字节码层面有多个步骤分配内存、初始化对象、引用指向内存。如果没有volatileJVM可能把“引用指向内存”重排到“初始化对象”之前另一个线程拿到引用后去访问对象会读到半初始化状态。加volatile后禁止这种重排。和synchronized的区别可以这样记volatile是轻量级同步适合一个线程写、多个线程读的状态标志但它不能替代锁因为复合操作如i不是原子的。如果多个线程同时执行自增还是要用AtomicInteger或synchronized。5.3 线程池核心参数和执行流程线程池构造方法有七个核心参数核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。执行流程是当线程数小于核心线程数时创建新线程处理任务超过核心线程数后新任务进入阻塞队列队列满了再创建新线程直到达到最大线程数线程数达到最大值且队列也满触发拒绝策略。为什么用阻塞队列而不是普通队列因为当核心线程都忙时任务是排队等待而不是无限创建线程。阻塞队列自带线程等待和唤醒机制适合生产者-消费者模型。实际开发中不要直接使用Executors创建线程池。newFixedThreadPool和newSingleThreadExecutor的队列是无界的任务堆积过多会OOMnewCachedThreadPool最大线程数是Integer.MAX_VALUE可能创建大量线程耗尽资源。正确的做法是手动new ThreadPoolExecutor把队列长度、线程数、拒绝策略都控制住。默认拒绝策略是AbortPolicy直接抛RejectedExecutionException不想让任务直接失败可以用CallerRunsPolicy让提交任务的线程自己执行。5.4 AQS是什么ReentrantLock怎么用它AQS是AbstractQueuedSynchronizer可以理解为JUC很多工具类的基石。核心是state状态字段加一个FIFO等待队列再配合模板方法。子类只需要实现tryAcquire和tryRelease这类方法AQS负责线程的排队、唤醒和中断处理。ReentrantLock就是基于AQS实现的。它默认是非公平锁线程在获取锁时直接尝试CAS修改state抢不到再进队列。公平锁则先检查等待队列中是否有前驱节点有就排队。非公平锁吞吐量通常更高因为减少线程切换代价是新来的线程可能插队导致等待线程饥饿。AQS的Condition也是高频追问点。Condition内部维护条件队列await方法会释放锁并把线程放入条件队列signal方法把条件队列中的线程移到同步队列。理解了这个ArrayBlockingQueue和CountDownLatch的原理就都能串起来。6. Spring、MySQL、Redis项目经验中最常被追问的三驾马车6.1 Spring Bean生命周期与循环依赖Spring Bean生命周期可以压缩成一句话实例化、属性填充、初始化、使用、销毁。中间穿插各种扩展点比如BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization、InitializingBean的afterPropertiesSet、PostConstruct等。循环依赖是Spring面试最常考的点。Spring用三级缓存解决一级缓存singletonObjects存放完整Bean二级缓存earlySingletonObjects存放早期暴露的原始Bean三级缓存singletonFactories存放ObjectFactory。为什么需要三级而不是两级关键点是有AOP。一个Bean在创建过程中可能需要被代理代理对象必须在属性填充之前暴露给依赖方。三级缓存里存的是ObjectFactory这样可以在对象真正被引用时才决定是否生成代理。如果只有二级缓存提前暴露出来的原始对象后面无法替换成代理对象最终会造成类型不匹配。还要记住构造器注入的循环依赖无法通过三级缓存解决多例Bean也不行。出现循环依赖第一反应应该是重构设计而不是依赖Spring的兜底机制。6.2 MySQL索引为什么是B树以及SQL优化常用手段MySQL InnoDB索引默认是B树。为什么不是B树或哈希哈希适合等值查询但不适合范围查询和排序B树叶子节点和非叶子节点都存数据树相对更高范围查询要做中序遍历B树只有叶子节点存数据内部节点只存键值扇出更高、树更矮而且叶子节点用链表串起来范围查询非常方便。面试题里“回表”和“覆盖索引”也要说清楚。二级索引的叶子节点存的是主键值通过二级索引查数据需要拿到主键再回聚簇索引查一次这就是回表。覆盖索引是指查询的字段已经在索引的叶子节点里不需要回表。联合索引还涉及最左前缀原则比如索引(a,b,c)查询条件只有b和c时无法走索引但a和c可以因为最左列固定住了。SQL优化一般从EXPLAIN开始。重点关注type字段从好到差大致是system const eq_ref ref range index all。看到typeall要注意全表扫描看到ExtraUsing filesort或Using temporary要优化排序和分组。实际优化手段包括加合适的索引、避免select *、少用%xxx%这类无法命中索引的模糊查询、避免在索引列上做函数运算。MySQL的事务隔离级别也常考。默认是可重复读靠MVCC和undo log实现。MVCC的ReadView可见性规则是高频追问点需要理解为什么可重复读下同一个事务多次查询结果一致而读已提交每次查询都会生成新的ReadView。6.3 Redis缓存穿透、击穿、雪崩的解决方案缓存穿透是查询一个一定不存在的数据请求直接打到数据库缓存完全失效。方案有两个方向缓存空值设置较短过期时间或者在缓存前加布隆过滤器快速判断key是否存在。缓存击穿是某个热点key过期瞬间大量请求同时打到数据库。解决办法包括热点数据永不过期用互斥锁让只有一个请求去重建缓存其他请求阻塞等待也可以采用逻辑过期策略在缓存value里额外存过期时间获取时发现逻辑过期就异步重建缓存。缓存雪崩是大量key同时过期或者Redis节点故障导致缓存集体失效。解决方式过期时间增加随机值避免同一时刻大面积过期多级缓存兜底Redis高可用集群必要时在入口做限流和熔断。面试追问“Redis为什么快”时不要只答单线程。更完整的回答是基于内存操作使用IO多路复用模型处理网络请求核心命令执行仍然是单线程数据结构设计高效比如跳表、压缩列表单线程避免了锁竞争和线程切换开销。Redis 6.0引入多线程只负责网络IO读写核心命令执行还是单线程这个细节能体现你关注新版本变化。7. 算法与编码题排序、Lambda、Stream以及翻车率最高的编译问题7.1 手写冒泡排序和快速排序讲到什么程度才算过冒泡排序是最基础的排序。优化后的写法可以加一个swapped标志如果某一轮没有发生交换说明已经有序提前结束。时间复杂度最坏和平均都是O(n^2)空间复杂度O(1)。public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) { break; } } }快速排序是面试手写题中的常客。核心是partition操作选一个基准值把小于等于基准值的放左边大于的放右边再递归排序左右两部分。平均时间复杂度O(nlogn)最坏退化成O(n^2)空间复杂度O(logn)来自递归栈。public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot partition(arr, left, right); quickSort(arr, left, pivot - 1); quickSort(arr, pivot 1, right); } private static int partition(int[] arr, int left, int right) { int pivotValue arr[right]; int i left; for (int j left; j right; j) { if (arr[j] pivotValue) { swap(arr, i, j); i; } } swap(arr, i, right); return i; }手写快排能过但最好还能答出优化方式基准值三数取中、数据规模小时换插入排序、递归到一定程度后改用堆排序防止栈溢出。这会让面试官觉得你不是背代码而是理解排序过程。7.2 Lambda与Stream高频、简洁但容易用错Java 8引入的函数式接口是Lambda的核心。PredicateT做条件判断FunctionT,R做转换ConsumerT做消费SupplierT做生产。Stream就是对集合进行函数式操作的流水线。常见用法包括filter过滤、map转换、collect(Collectors.toList())收集、Collectors.groupingBy分组、reduce聚合。例如ListString names users.stream() .filter(u - u.getAge() 18) .map(User::getName) .collect(Collectors.toList()); MapInteger, ListUser groupByAge users.stream() .collect(Collectors.groupingBy(User::getAge));容易踩的坑有三个第一stream只能消费一次第二次使用会报“stream has already been operated upon or closed”第二parallelStream在共享可变状态时会有并发问题比如往同一个ArrayList里add元素不一定能体现并行优势第三文件IO相关的Stream要放在try-with-resources里否则底层文件句柄可能不释放。7.3 编译报错和环境配置Lombok、source/target 17、JAVA_HOME这部分不是传统八股文但面试过程中特别容易翻车尤其是候选人现场打开代码却编译不过观感非常差。Lombok的报错You arent using a compiler supported by lombok, so lombok will not work本质是Lombok版本和JDK版本不匹配或者IDE使用的编译器不是javac。解决方案是升级Lombok到支持当前JDK的版本同时在IDE里开启Annotation Processing并在Maven/Gradle依赖中把Lombok声明为provided或compileOnly避免它被打进最终产物。“源发行版 17 需要目标发行版 17”这个报错也很常见。原因通常是项目语言级别、Maven编译器插件版本和JDK版本三者不一致。检查思路是pom.xml中maven-compiler-plugin的source、target、release统一成17IDEA的Project Structure - Project SDK和Modules - Language level设置一致IDEA的Settings - Build Tools - Maven - Importing里JDK设置正确。JAVA_HOME配置也是老生常谈。注意要指向JDK安装根目录不要指到jre因为javac在JDK里。Path中增加%JAVA_HOME%\bin然后命令行分别执行java -version和javac -version验证。如果你本机装了多个JDK可以用SDKMAN或用环境变量手动切换面试官问到环境问题时不至于手忙脚乱。8. 复习优先级和时间分配我的个人建议与一次真实复盘如果面试时间只剩一周我的建议是前两天过Java基础、集合、JVM和并发把原理看透并整理成自己的话第三天练手写算法至少把冒泡、快排、二分查找、链表反转写熟第四到第六天集中过Spring、MySQL、Redis每个模块准备一个能落地的项目例子最后一天做错题复盘和模拟口头复述。时间更充裕的话可以每天拆分任务三分之一时间看原理三分之一时间手写代码和画图三分之一时间口头讲给别人听。口头输出非常重要很多知识点你以为会了但一开口就卡壳说明还没有形成稳定的知识链路。我经常给候选人推荐一个复盘方法每道题整理成“一句话结论 原理支撑 项目例子”三件套。比如问到索引一句话结论是B树适合范围查询和排序原理支撑是内部节点只存键、叶子节点链表串联、树高更矮项目例子是你曾经优化过一条慢SQL加联合索引后执行时间从几百毫秒降到几十毫秒。这样面试官听到的不只是背出来的概念而是一套完整思维链路。我之前面试过一个候选人HashMap的扩容机制背得很熟练但问“如果自定义对象作为key需要重写什么”时愣住了。他说自己刷题的时候只记结论没想过为什么hashCode必须跟着equals重写。后来他把这个点补上之后整个集合框架都串起来了。技术面试就是这样一个“为什么”能撬动一整片知识盲区所以复习八股文时永远多问自己一句“为什么是它而不是别人”。
返回列表