前几天有学弟给我发来一份百度2023校招Java研发工程师笔试卷(第三批),说自己在牛客上刷到,做起来有点没底。我翻了一遍,发现这份卷子虽然叫“第三批”,但考点覆盖得很全,Java基础、集合、并发、JVM、Spring、MySQL、Redis、算法题基本都占了。对于准备大厂Java校招的同学来说,这份卷子值得拿来当一次全真模拟;对于工作一两年的Java开发,也可以通过它自查知识漏洞。我结合自己的刷题和面试经验,把这份卷子的考点、答题思路和避坑点完整地捋一遍,希望能帮到正在备战的你。
1. 拿到试卷先别慌:百度校招Java笔试的题型与考点分布
1.1 为什么是“第三批”:批次背后的出题逻辑
很多人看到“第三批”三个字,下意识会觉得是不是“剩下来的题”“难度垫底的批次”?并不是。校招笔试分批次,核心原因是候选人数太多、笔试场次需要错峰安排,题库也会滚动更新。第三批的题目通常会吸收前两批暴露出的问题,比如区分度不够、题目存在歧义、考点重复率过高等,然后换一批考法再上。
这意味着什么?如果你只刷了前两批的回忆帖,直接上第三批,大概率会碰壁。因为同一批热门考点会换着花样考,比如这批考了HashMap的扩容过程,下一批可能考ConcurrentHashMap的size()统计机制,再下一批又可能考红黑树和链表的转换条件。知识点还是那些,但切入角度一直在变。
所以我的建议是:不要试图去“押题”,而是把考点当成一个坐标网格来复习。看到一道题,先判断它在哪个模块,再回忆这个模块的完整知识树。这份卷子之所以值得复盘,就是因为它用的考点组合能帮你快速发现自己的薄弱网格。
1.2 题型结构与时间分配建议
从往年百度校招Java笔试的常见结构来看,这套卷子一般由单选题、多选题和编程题三部分组成。选择题覆盖的范围很广,从Java语法、集合、并发、JVM到Spring、MySQL、Redis、操作系统和网络都有可能涉及;多选通常放在中间位置,用来拉区分度;编程题一般有两道,一道偏数据结构,一道偏逻辑或场景设计。
这里有一个非常关键的时间分配策略:选择题总时长如果控制在50分钟以内,后面编程题会从容很多。我见过很多同学在选择题上死磕一道多选,结果编程题只剩15分钟,最后连用例都没跑通,非常可惜。多选的规则通常是“少选给部分分、错选不得分”,所以拿不准的选项宁可少选,也别凭感觉乱勾。
编程题方面,我的建议是按分值分配时间,先做自己最有把握的一道,拿到保底分,再回头啃难题。不要看到第一道题题干很长就被劝退,很多时候长题干只是在包装故事,核心算法往往很简单。先把输入输出格式看清楚,再动笔写代码,能省掉大量改格式的时间。
1.3 高频考点一览:先建立全局认知
这里我结合这份卷子和近两年大厂Java校招笔试的通用趋势,整理了一张考点分布表。它不是绝对权威,但可以作为你复习优先级的一个参考。
| 模块 | 典型考点 | 出现频率 | 复习优先级 |
|---|---|---|---|
| Java基础 | String、包装类、异常、equals/hashCode | 极高 | 必须滚瓜烂熟 |
| Java集合 | HashMap、ArrayList、ConcurrentHashMap | 极高 | 必须滚瓜烂熟 |
| 并发编程 | JMM、volatile、synchronized、线程池 | 高 | 重点突破 |
| JVM | 内存区域、垃圾回收、类加载 | 高 | 重点突破 |
| Spring | IoC、AOP、Bean生命周期、循环依赖 | 中高 | 系统掌握 |
| MySQL | 索引结构、事务、MVCC、SQL优化 | 中高 | 系统掌握 |
| Redis | 缓存穿透/击穿/雪崩、分布式锁 | 中 | 掌握常见场景 |
| 算法 | LRU、TopK、排序、双指针、DP | 极高 | 每天手写保持手感 |
如果你现在离笔试还有一个月以上,这张表里所有模块都要过一遍;如果只剩一周,我建议优先保Java基础、集合、并发、算法,因为这些是性价比最高的板块。框架和数据库的题通常考得比较经典,把核心概念梳理清楚也能拿到大部分分。
2. Java基础八股:这些点几乎每批都会出现
2.1 String、常量池与equals/hashCode的“连环问”
Java基础板块里,String几乎是必考。我记得卷子里有一道印象很深的题,问的是“String str = new String("abc") 一共创建了几个对象”。很多人脱口而出两个,但严格来说,如果常量池里已经有“abc”,那就只创建一个堆对象;如果没有,才创建常量池对象和堆对象两个。这种题不看你背没背过结论,而是看你有没有真正理解String的存储机制。
String不可变带来的好处也很值得展开:一是字符串常量池可以被安全复用;二是不可变对象天然线程安全;三是hashCode可以缓存,不需要每次重新计算;四是作为HashMap的key时,不会因为key内容变化导致定位错乱。笔试如果出多选题,往往会把这些好处拆成选项,少选一个都拿不到满分。
equals和hashCode的契约关系同样高频。核心规则是:两个对象equals返回true时,hashCode必须相等;hashCode相等,equals不一定为true。为什么?因为HashMap先通过hashCode定位桶,再用equals比较具体元素,如果两个相等的对象hashCode不同,它们在HashMap里就永远不可能被找到;如果hashCode相同但equals不同,只是形成链表或红黑树,不影响正确性。
2.2 HashMap、ArrayList的底层实现与扩容细节
集合类里,HashMap是当之无愧的“题王”。默认容量16、负载因子0.75、每次扩容翻倍,这些是基础;真正拉开差距的是扩容过程。JDK 7里的扩容采用头插法,并发情况下可能形成环形链表,导致死循环;JDK 8改成尾插法,规避了这个问题,但HashMap依然不是线程安全的,并发写还是可能丢数据。
关于红黑树,我建议至少记住两个触发条件:链表长度大于等于8且数组长度大于等于64时,链表转红黑树;红黑树节点数小于等于6时,退化为链表。至于为什么阈值是8,官方注释里给了泊松分布的概率推算,在负载因子0.75下,链表长度达到8的概率已经极低,这个解释写进面试答案里非常加分。
ArrayList的考点相对简单,初始容量10,扩容时按1.5倍增长,也就是 oldCapacity + (oldCapacity >> 1)。它和LinkedList的对比也是热门选择题:随机访问选ArrayList,频繁插入删除选LinkedList,但要注意LinkedList的插入删除优势也不是绝对的,因为定位到具体节点本身也是O(n)的,笔试出这种题特别爱埋“陷阱”。
2.3 泛型、Stream和Lambda:新题型的“送命题”
校招笔试和社招有一个明显区别:校招爱考Java 8的新特性,比如Lambda、Stream、Optional。看起来很简单,但错误率很高。核心原因是很多人只写过CRUD代码,对函数式编程的几个关键机制毫无感知。
我先说泛型。Java的泛型是类型擦除的,编译之后泛型信息会被擦除,运行时拿不到List 里的String类型。这意味着你不能通过getClass()区分ArrayList 和ArrayList 。泛型方法、泛型通配符? extends T和? super T的读写限制,也是选择题的高频点:extends只能读不能写,super只能写不能读,理解这个原则比死记强得多。
Stream的考点主要集中在惰性求值和短路。比如filter、map这类中间操作不会立即执行,只有遇到collect、forEach这类终止操作才会真正遍历。还有limit、findFirst这类短路操作,可以提前结束流处理,性能上很有讲究。笔试题有时候会问“下面代码会输出什么”,如果你不清楚惰性求值,很容易算出错误结果。
Lambda表达式则要理解“变量捕获”规则。Lambda里访问外部局部变量时,这个变量必须是effectively final,也就是初始化后不能再被修改。这背后是Java对函数式接口实现对象的约束,也是选择题里常见的错误选项来源。复习时建议亲手写几个例子,把编译错误触发一遍,印象会深很多。
3. 并发与JVM:拉开差距的核心板块
3.1 JMM、volatile和synchronized锁升级
并发板块是很多人的噩梦,但它恰恰是百度这类大厂笔试的区分度所在。JMM(Java内存模型)最核心的就是三条:原子性、可见性、有序性。volatile能保证可见性和有序性,但保证不了原子性,所以volatile变量做count++依然是线程不安全的。这个点几乎每年都考,选项里经常会混入“volatile保证原子性”这种错误描述。
volatile的底层原理也值得多写几句。它通过内存屏障实现:写操作之后插入StoreStore屏障和StoreLoad屏障,读操作之前插入LoadLoad屏障和LoadStore屏障,防止指令重排。在单例模式的双重检查锁(DCL)里,单例对象必须用volatile修饰,否则可能出现“半初始化对象被其他线程读到”的问题。这个案例笔试面试都爱考,最好能手绘出对象创建三步:分配内存、初始化、引用赋值,以及重排序可能导致的后果。
synchronized的锁升级过程也是必背:无锁→偏向锁→轻量级锁→重量级锁。偏向锁是同一个线程反复进入同步块时消除CAS开销;锁竞争加剧时升级为轻量级锁,通过自旋等待;自旋超过阈值后膨胀为重量级锁,阻塞其他线程。JDK 15之后偏向锁被默认禁用,但校招笔试如果没特别说明,考的还是经典升级链路,你按经典版本答基本没问题。
3.2 JVM内存区域与OOM排查思路
JVM的内存区域划分是送分题,但也最容易丢分。堆、虚拟机栈、本地方法栈、方法区(元空间)、程序计数器,各管什么要能说清楚。选择题常挖的坑是:线程私有的区域有哪些?答案是虚拟机栈、本地方法栈、程序计数器;堆和方法区是线程共享的。如果同时问“哪个区域不会抛出OutOfMemoryError”,答案是程序计数器,因为它的内存空间很小且固定。
垃圾回收部分,常见的收集器要能区分:CMS主打低停顿,G1主打可预测停顿,ZGC主打超低停顿且适用于超大堆。G1把堆划分为Region,通过维护优先列表来优先回收垃圾最多的Region,这是它和传统分代收集器最大的区别。笔试问“G1为什么能做到可预测停顿”,核心答案就是Region划分和停顿预测模型。
OOM的排查思路也很可能出现在案例题里。堆内存溢出看日志里的“Java heap space”,通常用jmap导出堆转储,再用MAT分析大对象;栈溢出看“StackOverflowError”,一般是递归过深;元空间溢出则常和动态生成类有关。这里我多说一句,实际开发中很多人会碰到“java: outofmemoryerror: insufficient memory”这种本地IDE报错,它本质是启动参数内存给小了,和线上OOM是两回事,排查时要先分清环境。
3.3 线程池参数计算与拒绝策略
线程池几乎是并发必考。核心参数七个:corePoolSize、maximumPoolSize、workQueue、threadFactory、handler、keepAliveTime、unit。笔试喜欢问提交任务的执行顺序:先判断核心线程是否满,不满就直接创建线程执行;满了进入阻塞队列;队列也满了,再判断是否达到最大线程数;还没达到就创建临时线程;达到最大线程数就执行拒绝策略。
核心线程数的计算没有标准答案,但业界有两套常见经验:CPU密集任务设置成 N+1,IO密集任务设置成 2N,其中N是CPU核数。笔试如果给场景题,要注意看任务类型。如果是计算型任务,线程数不应该超过CPU核数太多,否则频繁上下文切换反而更慢;如果是IO型任务,线程在等待IO时可以切给其他任务,线程数可以大一些。
拒绝策略四种要能区分:AbortPolicy直接抛异常、CallerRunsPolicy让提交任务的线程自己跑、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队列里最老的任务。最常考的是CallerRunsPolicy,因为它能起到背压的效果,而不是简单丢弃任务。线程池的坑还有一个:通过Executors.newFixedThreadPool创建的线程池用的是无界LinkedBlockingQueue,任务堆积可能造成OOM,实际开发中更推荐手动new ThreadPoolExecutor。
4. 框架和数据库:笔试不是只靠背概念
4.1 Spring IoC、AOP与Bean循环依赖
Spring在笔试里的权重不低,但考得很“经典”。IoC的核心是容器管理对象生命周期和依赖关系,AOP的核心是动态代理。这里有个常见选择题陷阱:Spring AOP默认对接口使用JDK动态代理,对类使用CGLIB代理;Spring Boot 2.x之后,spring.aop.proxy-target-class默认为true,所以即使有接口,也优先使用CGLIB。如果你还在背“有接口就JDK代理”,遇到新版本题目就会选错。
Bean生命周期这道大题,最好能背出一条完整链路:实例化→属性填充→Aware接口回调→BeanPostProcessor前置处理→initMethod→BeanPostProcessor后置处理→使用→销毁。笔试一般不会让你写全,但会抽其中一两个节点出选择题,比如“Aware接口在哪个阶段被调用”“BeanPostProcessor的postProcessBeforeInitialization和初始化的顺序”。
循环依赖也是一个固定考点。Spring通过三级缓存解决单例Bean的构造器循环依赖:一级缓存存成品Bean,二级缓存存早期暴露的Bean,三级缓存存Bean的ObjectFactory。为什么需要三级缓存?因为如果Bean需要AOP代理,必须提前通过ObjectFactory去生成代理对象,否则拿到的就不是最终代理对象。两级缓存也能解决普通循环依赖,但解决不了代理对象的正确处理,所以必须有三级。
4.2 MySQL索引、事务与MVCC
数据库方面,MySQL是绝对主角。索引必考B+树,它的优势要能说清楚:树高矮、磁盘IO少、叶子节点形成有序链表适合范围查询。聚簇索引的叶子节点存整行数据,二级索引的叶子节点存主键值,所以“回表”就是指通过二级索引查到主键后再去聚簇索引查整行。覆盖索引可以避免回表,这个是SQL优化题的高频答案。
事务隔离级别和MVCC是一对组合拳。四种隔离级别:读未提交、读已提交、可重复读、串行化。MySQL默认是可重复读,但可重复读依然解决不了幻读。MVCC通过undo log版本链和ReadView实现快照读,在可重复读级别下,事务第一次执行select时生成ReadView,之后都用这个快照;在读已提交级别下,每次select都生成新的ReadView。
笔试的SQL优化题,基本都是围绕explain展开。至少要会看type字段:const、eq_ref、ref、range、index、ALL,性能从左到右依次变差。如果看到ALL全表扫描,就要分析是不是没建索引、索引失效,或者数据量太小导致优化器认为全表扫描更快。索引失效的常见场景也爱考,比如对索引列使用函数、隐式类型转换、最左前缀原则被破坏。
4.3 Redis缓存三大问题与分布式锁
Redis相关题目,最经典的就是缓存穿透、缓存击穿、缓存雪崩。穿透是查询一个不存在的key,请求直接打到数据库,解决方法是布隆过滤器或缓存空值;击穿是某个热点key过期瞬间大量请求打到数据库,解决方法是互斥锁或逻辑过期;雪崩是大批key同时过期或Redis宕机,解决方法是过期时间加随机值、集群高可用、限流降级。
分布式锁也是高频场景题。用Redis实现分布式锁,核心命令是SET key value NX EX timeout,NX保证不存在时才设置,EX防止持有锁的进程崩溃导致死锁。但简单的SET NX有几个坑:锁的value如果不用唯一标识,可能误删别人的锁;锁过期时间到了但业务还没执行完,需要续期。Redisson的看门狗机制就是解决续期问题的,底层会启动一个定时任务,在锁快要过期时自动续期。
这套卷子如果出场景题,很可能会问“用Redis实现分布式锁要注意什么”。我的答题框架通常是:加锁要原子性(SET NX EX)、解锁要校验自己身份(value用唯一ID)、锁要支持自动续期、最好支持可重入。把这几条写出来,基本就能把大部分分拿到手。
5. 编程题实战:从审题到AC的完整思路
5.1 经典LRU缓存题:HashMap加双向链表的最优解
编程题里,LRU缓存的出场率非常高。题目一般是这样:实现一个LRUCache,支持get和put操作,get和put的时间复杂度要求O(1),当缓存容量满时,淘汰最久未使用的key。
看到O(1)的get和put,第一反应应该是HashMap,但如果只用HashMap,实现不了“最近最少使用”的淘汰顺序,所以需要额外维护一个按访问时间排序的结构。数组不行,因为插入删除是O(n);链表可以,但单项链表删除节点需要遍历前驱,也是O(n)。所以标准答案是用HashMap加双向链表:HashMap负责O(1)定位,双向链表负责维护访问顺序。
import java.util.HashMap; import java.util.Map; public class LRUCache { private final int capacity; private final Map<Integer, Node> map = new HashMap<>(); private final Node head = new Node(); private final Node tail = new Node(); private static class Node { int key; int value; Node prev; Node next; Node() {} Node(int key, int value) { this.key = key; this.value = value; } } public LRUCache(int capacity) { this.capacity = capacity; head.next = tail; tail.prev = head; } public int get(int key) { Node node = map.get(key); if (node == null) { return -1; } moveToHead(node); return node.value; } public void put(int key, int value) { Node node = map.get(key); if (node != null) { node.value = value; moveToHead(node); } else { Node newNode = new Node(key, value); map.put(key, newNode); addToHead(newNode); if (map.size() > capacity) { Node tailNode = removeTail(); map.remove(tailNode.key); } } } private void addToHead(Node node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } private void removeNode(Node node) { node.prev.next = node.next; node.next.prev = node.prev; } private void moveToHead(Node node) { removeNode(node); addToHead(node); } private Node removeTail() { Node node = tail.prev; removeNode(node); return node; } }这里有两个细节要提醒:一是删除队尾节点时,必须从map里把key也移除,否则会发生“map里还有数据、链表里已经删掉”的不一致;二是所有节点的prev和next指针都要更新完整,漏一条就会导致链表断裂。笔试时如果时间紧张,可以考虑用LinkedHashMap来实现LRU,但建议先手写一遍双向链表,因为面试官经常会追问底层原理。
5.2 TopK问题:堆和快排变体的选型
TopK是另一道高频题。比如“从10万个整数中找出最大的K个数”。最直观的思路是排序后取前K个,时间复杂度O(n log n),但在笔试里大概率不是最优解,会扣分。
求最大K个用最小堆最合适:维护一个大小为K的最小堆,堆顶是当前堆里最小的元素。遍历数据时,如果元素比堆顶大,就替换堆顶并调整堆,最终堆里留下的就是最大的K个数。时间复杂度O(n log K),当K远小于n时非常高效。java里直接用PriorityQueue默认就是最小堆,很方便。
import java.util.PriorityQueue; public class TopK { public int[] findTopK(int[] nums, int k) { if (nums == null || nums.length == 0 || k <= 0) { return new int[0]; } PriorityQueue<Integer> minHeap = new PriorityQueue<>(k); for (int num : nums) { if (minHeap.size() < k) { minHeap.offer(num); } else if (num > minHeap.peek()) { minHeap.poll(); minHeap.offer(num); } } int[] result = new int[minHeap.size()]; int index = 0; for (int num : minHeap) { result[index++] = num; } return result; } }如果数据量巨大,大到内存装不下,堆解法依然适用,因为它只需要在内存里维护K个元素。另一种思路是快排的partition变体,平均时间复杂度O(n),但最坏情况会退化到O(n^2)。笔试里建议优先写堆,因为代码短、不容易出错、复杂度的解释也好说。如果题目额外要求“不能使用现成PriorityQueue”,再手写堆或者实现快排partition。
5.3 冒泡排序和快速排序:手写排序题背后的隐藏考点
排序题在笔试里看起来简单,却是翻车重灾区。热词里冒泡排序、快速排序Java实现被频繁搜索,说明很多人到现在还在背代码模板。笔试如果直接考“手写快排”,至少一半的人能写出来,但追问“快排最坏时间复杂度是多少”“什么情况下会退化”“如何优化”,就会淘汰一大批人。
先给一个标准的快速排序实现,这是基本功,最好能盲写:
public class QuickSort { public void quickSort(int[] nums, int left, int right) { if (left >= right) { return; } int pivot = partition(nums, left, right); quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } private int partition(int[] nums, int left, int right) { int pivotValue = nums[left]; int i = left; int j = right; while (i < j) { while (i < j && nums[j] >= pivotValue) { j--; } nums[i] = nums[j]; while (i < j && nums[i] <= pivotValue) { i++; } nums[j] = nums[i]; } nums[i] = pivotValue; return i; } }快排平均时间复杂度O(n log n),但每次选到最小值或最大值做基准时,会退化成O(n^2),比如对已经有序的数组用最左元素做基准,就会出现这种情况。优化方案有三个层次:一是随机选择基准元素,打破输入分布的影响;二是三数取中,取left、mid、right三个位置的中间值;三是当子数组规模小于一定阈值时,改用插入排序,减少递归调用开销。
冒泡排序的隐藏考点则更多。基础版本是两层循环,复杂度O(n^2);优化版本加一个flag标志位,如果某一轮没有发生交换,说明已经有序,可以提前终止。笔试还可能问“冒泡排序是否稳定”,答案是稳定,因为相等元素不会交换位置。手写排序时,一定要先把“稳定”“原地”“最好复杂度”“最坏复杂度”这几个维度背清楚,因为它们经常出现在选择题的选项组合里。
6. 我踩过的坑:校招笔试避坑清单
6.1 选择题里埋的“文字陷阱”
我当年做校招笔试时,最大的教训就是选择题不能想太快。出题人特别喜欢在选项里做文字游戏,比如“HashMap线程安全是因为它使用了synchronized”“ArrayList默认容量为10,所以第一次add就会扩容到10”“String使用+拼接字符串性能一定比StringBuilder差”。这些说法单独看似乎有点道理,但放到具体上下文里全是坑。
举几个真实出现过的陷阱:一是HashMap默认容量确实是16,但new HashMap(10)并不会立即扩容,而是会计算成16,很多人选“容量为10”就直接错了;二是ArrayList扩容发生在add导致size超过容量时,第11个元素加进去才会触发扩容,不是第一次add就扩容;三是String拼接如果发生在编译期常量折叠场景下,性能并不差,不能一概而论。
多选题的规则更要重视。很多平台是“全部选对才得分,少选得部分分,错选不得分”。所以遇到模棱两可的选项,我的策略是:有60%以上把握的选项才选,完全没把握的宁可不选,先保住部分分而不是赌全对。这听起来保守,但实际收益最高。
6.2 编程题“用例通过却拿不到分”的常见原因
编程题最常见的悲剧,不是做不出来,而是“本地能跑,提交就挂”。我复盘了一下,问题基本集中在四个方面。
第一是边界条件。空数组、单元素数组、元素全相等、最大值和最小值相邻出现,这些情况最容易暴露问题。练题时一定要形成条件反射:写完代码先自测这几个输入,不要只测题目给的示例。
第二是类型溢出。比如求两数之和、求乘积、求斐波那契数列第N项,用int很容易溢出,应该提前判断数据范围,必要时用long。有些题故意把数字设得很大,就是在考验你“数据的边界感知力”。
第三是输入输出的性能。数据量达到十万、百万级别时,Scanner + System.out.println会明显变慢,甚至超时。推荐用BufferedReader和BufferedWriter,或者用StringBuilder拼接后一次性输出。这个优化很多人都知道,但一紧张就忘了。
第四是死循环。while循环的退出条件写错,或者map迭代时修改结构,都会导致超时。调试时如果发现“卡住不动”,优先检查while条件、循环变量是否更新、容器是否被并发修改。
6.3 考试环境与临场心态:别让无关问题影响发挥
笔试环境本身也有一堆细节。首先是JDK版本,本机用的Java 8和线上判题环境用的Java 17,在某些API行为上会有差异,比如String的intern策略、switch表达式、var关键字是否支持。交卷前如果提示“源发行版17需要目标发行版17”这类编译配置问题,不要慌,这通常只是本机IDE的编译级别设置,不是代码逻辑问题。
类名和包名也有讲究。很多在线判题系统要求主类名为Main,并且不能声明package,否则提交后编译提示找不到主类。我见过有同学在自己IDE里建了com.example包,粘贴到系统里忘了删,白丢好多分。另外,lombok在部分判题环境里是不生效的,如果用了@Data、@Slf4j这类注解,提交到不支持lombok的环境就会编译失败。笔试时间紧张,我建议编程题尽量写纯原生Java,不依赖任何第三方库。
最后说心态。笔试遇到不会的题太正常了,我做这套卷子时也有两道选择题拿不准。关键是不要在一道题上耗太久,该跳就跳。编程题哪怕只写出暴力解,也能拿到一部分用例分,比空着强得多。把“拿到所有保底分”当作目标,心态会稳很多。
7. 笔试后的复盘思路与资料选择
7.1 从错题反推知识盲区
笔试结束后,很多人只关心“过了没有”,其实更重要的是做复盘。我的习惯是把错题分三类:纯记忆类、理解类、临场失误类。纯记忆类比如某个API的默认值、某个数据结构的时间复杂度,考前突击背一遍就行;理解类比如synchronized锁升级、MVCC快照读,需要重新看原理;临场失误类比如边界条件没判断、输入输出写错,通过多刷题就能改善。
复盘时不要只看正确答案,还要看错误选项为什么错。尤其是多选题,每个选项都值得展开成一个小专题。我在备考时做过一个表格,左边是错误选项,右边是一句话解释,最后发现很多考点在解释过程中被彻底梳理清楚了。这个方法比闷头刷题效率高很多。
7.2 系统复习比盲目刷题更重要
关于备考资料,我不建议囤太多。Java基础推荐《Java核心技术》和《深入理解Java虚拟机》,并发看《Java并发编程的艺术》,MySQL看《高性能MySQL》里面索引和锁的章节。B站上很多免费视频讲得也不错,但要注意甄别内容的版本新旧,有些教程还在讲JDK 7的HashMap头插法,和现在的主流版本已经有出入。
刷题平台方面,牛客网的校招真题、LeetCode热门100题、力扣的Top面试题都值得刷。每天保持1到2道算法题的节奏,比周末突击10道效果好得多。手写代码一定要自己敲,不要只看题解。我见过太多人“眼会手不会”,一到笔试现场连import都写不全,这只能靠多写来解决。
如果你正在准备大厂Java校招,这份卷子里的考点可以作为一份自检清单。拿出半天时间,把每道题按“能讲清原理、能写代码、能说出坑”三个标准过一遍,过不了的马上补。别等笔试开始了才发现,原来自己对这么多知识点只有“好像知道”的程度。
最后再分享一个我个人的小习惯:每次笔试或模拟完之后,我都会把错题截屏整理到一份文档里,周末统一过一遍。记忆会随时间衰减,但错题本不会。越到后期,这份错题本的价值就越大,很多考前没底的知识点,都是靠着反复看错题才真正记牢的。希望你也能从这套卷子里找到自己的薄弱点,然后一个一个填平,在校招这场持久战里拿到属于自己的那个offer。