2024年秋招,百度Java工程师第二批笔试,这个话题最近在各大技术社区和校招群里被反复讨论。作为经历过不止一轮大厂笔试、也带过很多应届生走完整套校招流程的Java工程师,我整理了这份关于百度笔试的详细复盘。这篇内容会围绕大家最关心的几个问题展开:笔试题型是什么样的、Java基础到底在考什么、编程题怎么写才能拿分,以及那些让无数人当场心态爆炸的环境问题该怎么解决。不管你是刚开始准备校招的应届生,还是打算为下一轮招聘提前准备的实习生,这篇内容应该都能给你一些真正有用的参考。
先说明一点,我下面写的不是标准答案,而是结合我自己考试经历和帮人复盘总结出来的方法论。每个人的基础不同、优势不同,备考策略也要跟着调整。但有些东西是通用的:Java的高频考点、算法题的常见套路、在线笔试的避坑方式,这些你花时间准备就一定有回报。
1. 笔试整体情况与备战思路
1.1 笔试形式和题型分布
百度校招的技术笔试,第一批和第二批在形式上是差不多的。正常情况下,试卷分两块:一块是选择题,包含单选和多选;另一块是编程题,两到三道不等。整体时长在90分钟到120分钟,具体看当年的安排。在线笔试系统会在考试前发送邮件通知,里面会写清楚支持的语言和版本,这个细节我建议一定提前看清楚。
选择题考察的面很杂,但Java基础一定占大头。集合框架、并发、JVM内存模型、String相关、异常处理,这些几乎每年都会出现。计算机网络会考TCP三次握手、HTTP状态码、DNS解析过程,操作系统则是进程调度、死锁、虚拟内存这些经典题。数据库部分重点关注索引、事务隔离级别、SQL语句优化,这几块在选择题里出现频率很高。
编程题一般是两到三道,难度呈梯度上升。第一道往往是简单题,双指针、模拟、字符串处理,只要思路清晰就能拿下。第二道是中等难度,可能涉及贪心、二分、动态规划。第三道属于拉开差距的题目,时间不够做不出来非常正常,不用自我怀疑。
笔试不是一个要求满分的考试,它的核心逻辑是筛选。你要做的就是在有限时间内尽可能多拿分,把能稳拿的分全部拿到手,再去冲难题。很多人栽在时间分配上,简单题磨蹭太久,难题也没做出来,两头空。
1.2 备战资料怎么选
这个话题我可以直接给你一套组合拳,是我自己验证过效率比较高的方式。
第一,Java基础知识点要系统过一遍,这就是大家常说的"八股文"。网上随手一搜能出来一堆整理好的文档,知识点确实全,但量也大。我的建议是把它当目录用,而不是背诵材料。每个知识点问自己三个问题:这是什么?为什么这样设计?在什么场景下用?能答出来,说明真理解了;答不出来,这个点就是你的薄弱项,重点补。
第二,算法刷题要重质量而不是数量。校招时间紧,优先刷力扣Hot 100和剑指Offer。刷题的时候别光看题解,卡壳十分钟还想不到思路,再看题解,然后手写一遍。隔天再独立写一遍,能写出来才算真正吸收。这样一轮下来,比漫无目的地刷三百题有效得多。
第三,准备一个自己的Java项目。哪怕是课设级别的也完全可以。笔试大题偶尔会有场景设计题,比如设计一个秒杀系统、实现一个限流器、写一个短链接服务。有项目经验的人写这种题,思路和纯背题的人是不一样的。
2. Java基础核心考点拆解
2.1 面向对象三特性的考察方式
面向对象编程(OOP)的封装、继承、多态,是Java笔试里最基础也最高频的考点。选择题经常考"下列哪个体现多态"、"重载和重写的区别"这类问题,看着简单,但很多人丢分。
封装强调的是对象的属性和行为绑定在一起,外部只能通过公开接口访问,不能直接操作内部状态。考察方式一般是给一段代码,让你判断哪个类设计符合封装原则。核心判断标准就一条:成员变量有没有用private修饰,外部是否只能通过getter/setter方法访问。
继承考得最多的是构造器和代码块的执行顺序。父类静态代码块、子类静态代码块、父类实例代码块、父类构造器、子类实例代码块、子类构造器,这个顺序几乎年年考。记忆口诀是:静态先行,父类先于子类。再往深了考,会涉及super关键字、方法重写时访问权限不能变窄、抛出的异常不能变宽、返回值类型可以变窄。这些细节在选择题里非常容易设陷阱。
多态是重中之重。向上转型、向下转型、instanceof判断、动态绑定和静态绑定,都是高频题。做题的时候记住一句话:编译看左边,运行看右边。变量能调什么方法取决于声明类型,方法执行看的是对象实际类型(非private、非static方法)。重载属于静态绑定,编译期确定;重写属于动态绑定,运行期确定。这个区分是选择题的常客。
2.2 集合框架高频考点:HashMap、ArrayList与并发容器
集合框架在笔试里出现频率高到离谱,尤其是HashMap。我甚至见过一张卷子里选择题和编程题都用到了HashMap。
HashMap的核心考点,把这些吃透基本就稳了:
- 底层数据结构:JDK 1.8之后是数组+链表+红黑树。链表长度大于等于8且有元素个数超过64时转红黑树,长度降到6时退化为链表。两个阈值不一样,是为了防频繁转换。
- put流程:根据key的hash值计算索引,该位置为空直接放入;非空则遍历链表比较key,存在就覆盖,不存在就尾插。如果头节点是红黑树节点,走树化插入逻辑。
- 扩容机制:默认容量16,负载因子0.75,扩容为原来的两倍,元素要重新计算位置。
- 为什么用红黑树:链表太长查询退化为O(n),红黑树最坏也是O(log n)。
ArrayList和LinkedList的对比也是选择题常客。ArrayList底层是数组,查询快,增删慢,扩容时做整数组拷贝。LinkedList底层是双向链表,中间增删快,查询慢。刷题时很多人喜欢用LinkedList做队列、做栈,但笔试里要注意,如果你需要随机访问,ArrayList明显更合适。
ConcurrentHashMap在并发容器这一块必考。JDK 1.8之后放弃了分段锁,改用CAS配合synchronized锁住链表头节点或树根节点,读操作不加锁,因为Node里的val和next用volatile修饰了。问它为什么比HashTable快,答案就是锁粒度更细,而且读没有锁。
还有一道经典题:HashMap为什么线程不安全?并发put可能会覆盖数据,JDK 1.7头插法在并发扩容时还可能形成环形链表导致死循环。所以并发场景不要直接用HashMap,选ConcurrentHashMap。
2.3 并发编程与JVM必背知识点
并发这块,笔试常考的包括synchronized与ReentrantLock的区别、volatile关键字、CAS与原子类、线程池参数。
synchronized是JVM层面的锁,通过monitor实现,代码块结束或异常时自动释放;ReentrantLock是JDK提供的锁,需要手动lock和unlock,建议配合try-finally用。ReentrantLock支持公平锁、可中断锁、多条件队列,功能更强。JDK 1.5之后synchronized做了锁升级优化(偏向锁、轻量级锁、重量级锁),性能差距已经大大缩小。
volatile的核心作用有两个:保证可见性、禁止指令重排,但无法保证原子性。笔试最爱问的就是volatile能不能保证原子性,答案是不能。volatile int count,count++这种读改写操作不是原子操作,并发下依然会丢数据。
线程池参数必须背熟:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲线程存活时间)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。执行流程是:提交任务后,先判断核心线程数是否已满,没满就创建核心线程执行;满了就放入队列;队列满了,创建非核心线程;线程数到了最大值,执行拒绝策略。
JVM的考点集中在内存区域和垃圾回收。内存区域常考堆、栈、方法区(JDK 8之后是元空间)、程序计数器、本地方法栈,各自存什么内容、哪些线程共享哪些线程私有。堆是所有线程共享的,栈是每个线程私有的。垃圾回收里常考Minor GC和Full GC的触发条件,可达性分析算法,以及常见垃圾回收器(CMS、G1)的适用场景和特点。还有一个经典题:哪些对象能当GC Roots?虚拟机栈引用的对象、方法区静态属性引用的对象、常量池引用的对象、本地方法栈JNI引用的对象。
2.4 Java新特性与常见语法细节
这几年笔试选择题里,Java 8的Lambda表达式和Stream API出现频率明显上升,说明出题人也在跟着技术潮流走。
Lambda表达式的本质就是一个匿名函数,使用前提是函数式接口,也就是只有一个抽象方法的接口,比如Runnable、Comparator、Consumer。笔试里也出现过方法引用的题,比如System.out::println、String::length,本质是Lambda的简写形式。方法引用分四种:静态方法引用、特定对象的实例方法引用、任意对象的实例方法引用、构造器引用。能识别出来就行。
枚举类型是另一个高频考点。enum本质上是一个继承自java.lang.Enum的类,枚举常量是这个类的静态final实例。它可以定义字段、方法、构造函数,可以implements接口,switch语句里可以直接用枚举。有一个常见判断题:枚举能不能继承其他类?答案是绝对不能,因为Java单继承,而且它已经隐式继承了Enum类。
运算符和表达式,看着基础但容易丢分。三个重点要记住:第一,短路与、短路或的执行规则,左边能决定结果时右边不会执行;第二,三目运算符的类型自动提升问题,int和double混用时会提升为double;第三,位运算,左移一位相当于乘2,右移一位相当于除2,笔试可能会用位运算实现乘除法来考你。
数组越界异常(ArrayIndexOutOfBoundsException)也是笔试编程题里的常见报错,后面我会专门讲怎么避免。至于更常见的Java环境变量配置、JDK安装等基础,如果你笔试前还没配好环境,建议先解决这个前置问题再谈其他。
3. 编程题实战:从审题到AC的完整流程
3.1 笔试算法题的常见题型与套路
百度笔试编程题的高频题型,我总结下来有几类。
第一类是模拟题。给你一套规则,让你按规则模拟整个过程。这种题思路不难,但实现细节多,分支多,容易漏。我的习惯是先别急着写代码,在纸上把流程捋一遍,拆成几个小函数,再动手写。边写边想逻辑很容易漏掉边界分支,最后debug时间比写代码时间还长,不值当。
第二类是双指针和滑动窗口。有序数组的两数之和、最长无重复子串、最小覆盖子串都是这一类的经典题。看到"连续子数组"、"区间"、"窗口"这些关键词,第一反应就应该是滑动窗口。这类题实现起来不复杂,关键在判断场景。
第三类是动态规划。背包问题、最长递增子序列、打家劫舍系列。动态规划的核心是状态定义和状态转移方程,笔试里我建议先把状态定义写清楚,再推转移方程。很多人一上来就写转移方程,写一半发现状态定义就是错的,全部推翻重来,心态很容易崩。
第四类是图论基础题。拓扑排序、最短路径(Dijkstra)、并查集。这类题一般放第三道,属于压轴题,时间不够可以先跳过,不影响前面得分。
有人会问,排序算法在编程题里还会直接考吗?说实话,直接让你写排序的题越来越少,但排序思想会嵌入到更复杂的题里。并且选择题里经常会出现"下面哪一趟排序后的数组可能是快速排序的结果"这类题,所以对排序过程的状态变化要有清晰的认知。搜索热搜词里冒泡排序和快速排序Java实现的搜索量一直不低,说明这是很多人的薄弱点,值得专门补一下。
3.2 排序算法的Java实现思路
冒泡排序,代码虽然简单,但边界条件很容易写错。正确的写法和一个关键优化点是这样的:
public void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { boolean swapped = false; for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; } } if (!swapped) { break; } } }那个swapped标志位特别重要。如果某一趟循环结束后没有任何交换发生,说明数组已经有序,直接提前结束。这个是冒泡排序最经典的优化,也是选择题考点:加了标志位之后,最好情况下的时间复杂度是O(n),没加则永远是O(n^2)。
快速排序是笔试手撕频率最高的排序,没有之一。核心思想是分治:选一个基准值,把数组分成左边小于等于基准、右边大于等于基准的两部分,然后递归处理左右两边。我比较推荐Lomuto分区方案,代码写起来不容易出错:
public 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 int partition(int[] arr, int left, int right) { int pivot = arr[right]; int i = left; for (int j = left; j < right; j++) { if (arr[j] < pivot) { swap(arr, i, j); i++; } } swap(arr, i, right); return i; }快速排序的几个关键点要记住:
- 最坏时间复杂度是O(n^2),发生在数组已经有序,每次选到最大或最小元素作为pivot时
- 优化方式是随机选择pivot或三数取中
- 快速排序是不稳定排序
笔试里如果题目不要求手写排序,直接用Arrays.sort就可以了。底层是DualPivotQuicksort,性能和稳定性都经过大量优化。笔试不是炫技的地方,能用现成API解决的事情不要自己造轮子。
3.3 边界条件与代码规范:如何一次写对
编程题最容易失分的不是思路,而是一些特别小的地方。我总结几个高频出错点。
数组下标越界最常见。凡是代码里出现arr[i + 1]、arr[i - 1]、双指针移动,都要先想想i和j是不是在有效范围内。循环条件建议写 i < n - 1 而不是 i <= n - 2,前者更直观,不容易出错。
空指针是第二杀手。题目如果没说输入一定非空,方法开头一定要判空。还有一个隐藏很深的空指针来源:拆箱。int和Integer混合使用,Integer为null时自动拆箱抛出NullPointerException,这个选择题考过多次,很多人挂在上面。
整型溢出也容易被忽略。题目没给数据范围时,计算涉及乘法或累加就用long,别用int。还有一个Integer比较的陷阱:-128到127范围内,Integer用==比较没问题,因为这个范围内的值会被缓存;超出这个范围,==比较的是对象引用地址,结果可能为false。判等要用equals。这个坑在笔试题里非常容易出成选择题。
输入输出的效率也要重视。在线笔试里Java读取数据最快的是BufferedReader,其次是Scanner。数据量到一定程度,Scanner会有明显延迟,极端情况会TLE。平时练习就直接用BufferedReader加StringTokenizer,形成习惯:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int n = Integer.parseInt(st.nextToken());4. 笔试现场的环境雷区与排查实录
4.1 JDK版本不匹配:源发行版17需要目标发行版17
这个报错是2024年以来很多同学的噩梦,原文长这样:
java: 警告: 源发行版 17 需要目标发行版 17出现这个问题的根本原因是IDE里的项目编译级别和JDK版本不一致。比如说你本地装的是JDK 8,但项目编译级别被设成了17,编译器就会报这个警告,并且拒绝使用当前的JDK来编译。
笔试现场遇到这个问题会非常急人。在线笔试系统一般有自带的代码编辑器,不需要本地编译,但如果你习惯在本地IDE里先调试好再粘贴到系统里,这个问题就会卡住你。排查顺序我建议是这样:
第一步,确认本地JDK版本。命令行敲 java -version,看清装的是哪个版本。如果装的是JDK 17,默认编译级别就是17,问题不大。
第二步,检查IDE的Project Structure设置。在IntelliJ IDEA中是File -> Project Structure -> Project,把SDK和Language Level设成一致版本。
第三步,检查Maven或Gradle的编译配置。pom.xml里maven-compiler-plugin的source和target要和JDK版本一致。最省心的写法是直接用properties统一指定:
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>还有一个细节容易被忽略:如果笔试平台只支持Java 8,你本地却用了Java 11+的语法(比如var、Switch表达式、文本块),粘贴过去就会编译失败。提前看考试说明确认支持哪个JDK版本,本地环境切换到对应版本去练题,这是最稳妥的做法。
4.2 Lombok与编译器报错
另一个高频报错长这样:
java: You aren't using a compiler supported by lombok, so lombok will not work.这个报错一般出现在本地IDE编译阶段。Lombok是通过注解处理器在编译期生成代码的,比如@Data自动生成getter/setter。如果Lombok版本和JDK版本不兼容,它就无法注入编译器,就会报这个错。
解决办法有几种:
- 升级Lombok到最新版本,旧版本对JDK 17以上支持不全
- 检查IDE里是否安装了Lombok插件,IDEA没有插件时注解不会生效
- 最直接的方案:笔试环境如果默认不支持Lombok,就不要在笔试编程题里用@Data、@Builder这些注解
我个人的习惯是:笔试编程题全程手写普通类,不用Lombok。在线笔试的评测系统可能没配注解处理器,一个@Data注解可能导致整个类编译失败,直接零分。手写getter/setter也就几行代码,换来的是确定性,很值。
4.3 内存溢出与输入输出陷阱
本地跑得好好的,粘贴到在线笔试系统就报内存溢出,这个问题很多人都遇到过。报错一般是:
Exception in thread "main" java.lang.OutOfMemoryError: Insufficient memory原因通常是两个:代码里有无限递归或死循环;或者创建了超大数组、不必要的大对象。
排查思路很简单:先看递归有没有终止条件。快速排序如果基准选得不好,在特定数据下递归深度会非常大,触发栈溢出。再看数组大小,如果题目数据范围是10^5,你开个10^9的数组,那必然撑爆内存。
在线笔试系统一般都会限制内存,通常是256M到512M。写代码时要有复杂度意识,能用数组就别用HashMap,能用int就别用Integer。还有一个容易被忽略的点:Scanner读大数据量输入时也会消耗额外内存和时间。数据量大时,换成BufferedReader,流畅很多。
另外,如果你在本地用VS Code运行Java时遇到中文乱码,这在笔试环境不会出现,但会影响本地调试心情。解决方案是把编码统一成UTF-8,控制台编码和文件编码保持一致,VS Code的settings.json里加上:
"java.debug.settings.consoleEncoding": "UTF-8", "java.jdt.ls.vmargs": "-Dfile.encoding=UTF-8"5. 秋招笔试的独门经验与心态调整
5.1 时间分配:先易后难,学会放弃
笔试的编程题难度是渐进的,第二题比第一题难,第三题最难。很多人的问题不是不会做难题,而是把时间全部砸在难题上,简单题反而没时间写。
我的策略是这样的:拿到试卷先快速浏览所有题目,用一两分钟判断每道题的难度。然后按顺序写,但每题给自己设一个时间上限。简单题最多25分钟,中等题最多40分钟,难题看完如果10分钟没有思路,直接跳过。编程题是按测试用例给分的,只过部分用例也能拿部分分数,完全不写一定是零分。
选择题也一样,不要恋战。有些选择题特别绕,考的是冷门知识点,凭第一感觉选了就过,后面有时间再回来检查。任何一道题卡太久,都是对其他题目的不尊重。
5.2 笔试结束后的复盘
笔试结束不等于流程走完。如果你有记题的习惯,笔试结束后第一时间把题目复现出来,这非常重要。
为什么说重要?百度第二批笔试和后面批次的题目,有很大概率出现相似题型。你认真复盘过一道题,下次遇到类似题型就能直接联想到思路,节省大量思考时间。很多同学笔试完就把题目抛到脑后,下一场又是从头开始,这样每次都在重复劳动。
复盘的核心不只是把代码抄一遍,而是分析三点:当时哪里卡住了、正确的做法是什么、如果重写一次要避免什么。把这些记下来,形成自己的题库,这才是越考越顺的核心逻辑。
5.3 心态调整:笔试不是终点
最后说点实在话。秋招笔试只是整个校招流程中的一环,它更像一个初筛器,把明显不达标的筛掉,而不是把所有考生都难倒。
百度这样的公司,每年校招收到的简历数以万计,笔试通过率本身就不高。一次笔试没过不说明你不行,我的建议是别把全部希望押在一场笔试上,多投多面,增加机会。
保持稳定的复习节奏,比临时抱佛脚重要得多。每天固定刷几道题,过几个知识点,周末做一次全真模拟,这种细水长流的节奏,到了笔试当天状态是稳的。临时狂刷三天,大脑一片混乱,反而容易在考场上发挥失常。
我个人的体会是,笔试最大的门槛不是知识量,而是熟练度。八股文背熟了、算法题刷顺了、输入输出写熟了,笔试的通过率自然会大幅提升。把平时练习当成真正的笔试来对待,真正笔试的时候才能像平时练习一样轻松。
这份复盘是基于我自己的实战经验和帮人备考过程中积累的总结,希望对准备百度Java工程师笔试的同学有帮助。每个人的基础和薄弱点都不一样,别照搬任何一份备考计划,一定要结合自己的情况去调整。最后祝大家都能拿到心仪的offer。