2018年秋招,爱奇艺这场Java工程师笔试开到了第三场。时间过去这么久,回头再看这套题的考察范围,依然很值得拿出来仔细拆一遍。它几乎就是一张Java后端校招的“标准地图”:视频平台的后端服务依赖高并发、大数据量,所以笔试不会只考那种背一背就能过的“八股文”,而是会把Java语法细节、集合容器、JVM异常、排序算法和Spring Boot这一整套工程工具链串在一起考。
准备这场笔试,很多人一上来就翻“java面试八股文”,但真正拉开差距的往往不是背诵量,而是有没有把知识点串成体系。比如“java: outofmemoryerror: insufficient memory”这条报错,表面上是JVM内存不够,往里挖就是堆内存、GC策略、对象引用生命周期的问题;再比如“java: 警告: 源发行版 17 需要目标发行版 17”这种编译警告,背后是Maven编译插件和JDK版本不一致。这篇文章不打算给你押题,而是把这类校招高频考察的模块按真实做题逻辑拆开,从Java基础语法、集合容器、异常与JVM,到排序算法、Spring Boot工程能力,每一块都讲清楚“为什么考”和“怎么答才不会翻车”。
1. 这场校招笔试在考察什么:从热搜词反推Java后端能力图谱
1.1 为什么Java面试题高度趋同
先看一个现象:你搜“java面试题”,翻几页就会发现,考点翻来覆去就是那么几类——Java基础、集合框架、多线程、JVM、Spring、MySQL、Redis、算法。这不是出题人偷懒,而是后端Java工程师日常工作的核心能力就这几块。爱奇艺这种体量的视频平台,后端系统要处理海量用户请求、视频元数据存储、推荐策略调度,工程师大概率会接触高并发场景,所以JVM调优、集合选型、线程安全这类问题一定会出现在笔试里。
我会判断一个知识点是否值得花时间,就一个标准:它能不能解释一个真实出现的线上问题。比如“java中数组越界异常”,看起来是最基础的Exception,但实际业务里很多数据分批处理、分页查询的边界Bug,最终根源就是数组或集合越界。“Java环境变量配置”这种问题也是,你在本机装JDK踩过的坑,面试官往往会换个马甲再考一遍,让你现场说环境变量从JAVA_HOME到PATH到底是怎么被加载的。热搜词里的“java环境变量配置详细教程”“drozer+找不到java”“pcl(java版启动器)”看起来是工具问题,本质都是同一个东西:对JDK/JRE运行机制的理解。
1.2 第三场笔试的题型与时间分配推测
校招笔试一般分三块:选择题/填空题、简答题、编程题。选择题主要扫基础知识盲区,比如运算符优先级、枚举的用法、集合扩容机制;简答题会考察一些“需要表达”的内容,比如HashMap底层结构、快排思路、Spring Boot自动配置原理;编程题则是手写算法,冒泡、快排这类排序题出镜率极高。
以“第三场”这个定位来看,它大概率是批量机考中的一场,题目难度不会特别离谱,但覆盖面很广。我之前模拟过不少类似场次,总结出一套时间分配逻辑:
| 题型 | 建议占比 | 策略 |
|---|---|---|
| 选择题 | 30% | 快速判断,不会的适当标记后跳过,不恋战 |
| 简答题 | 25% | 按“结论先行、原因补充、举例说明”的结构作答 |
| 编程题 | 45% | 先写能跑的暴力解,再优化,保证有分拿 |
很多人时间不够用,是因为选择题里纠结太久。实际上选择题大多考的是“认不认识这个坑”,认识就秒选,不认识想十分钟也白搭。编程题反而是拉开分差的地方,所以我会建议把完整的大块时间留给算法题,尤其是排序、字符串处理、链表操作这几类手写题。
2. Java基础语法考点:运算符、枚举、Lambda与异常体系的正确打开方式
2.1 运算符与表达式:看似送分,实则暗藏类型转换坑
Java基础部分最爱考的就是运算符和表达式,因为它能快速检验一个人是否真正写过代码,而不是只看过语法。举个例子:
int a = 5; double b = a / 2; System.out.println(b);很多人第一反应是输出2.5,实际输出是2.0。因为a / 2两个操作数都是int,先做整数除法得到2,再隐式转成double,结果是2.0。这种题在笔试题里属于“送命题”,看着简单但错误率极高。更狠的考法是:
Object result = true ? new Integer(1) : new Double(2.0); System.out.println(result);这个输出是1.0而不是1。三目运算符在做类型判断时,会把int和double统一提升为double,这是语言规范里明确写的,只是平时很少有人注意。笔试考的就是这种“我不会特意去查但代码跑出来跟直觉不符”的细节。复习阶段把《Java语言规范》里关于二进制数值提升、条件表达式的规则看一遍,这类题基本能全对。
2.2 枚举、Lambda与常用类的核心考点
枚举在校招笔试里算高性价比考点。它不复杂,但能延伸出不少问题。比如枚举能否继承类?答案是不能,因为Java中枚举隐式继承java.lang.Enum,但可以实现接口。再比如枚举的构造方法权限,必须是private,这是枚举能在单例场景被推荐的原因。面试官喜欢让你现场实现一个枚举单例,顺便问一句“为什么枚举实现单例不会破坏单例”,因为枚举在序列化和反射机制上天然有保护。
Lambda表达式也是高频考点。它本质是函数式接口的实例,只有像Runnable、Comparator、Function这样的接口才能使用Lambda表达式。有几个细节值得注意:Lambda表达式中访问外部局部变量,这个变量必须是effectively final,也就是经过初始化之后不能再被修改。如果你在循环里用某个可变量去构造Lambda,编译器直接报错。我当时准备时专门整理过一个表:
| 考点 | 常见坑 | 正确认知 |
|---|---|---|
| 枚举继承 | 试图继承父类 | 枚举隐式继承Enum,只能实现接口 |
| 枚举构造器 | 写成public | 枚举构造器固定为private |
| Lambda变量捕获 | 修改外部变量 | 外部变量必须是effectively final |
| 方法引用 | 与Lambda混淆 | 实例方法引用和静态方法引用使用场景不同 |
常用类方面,String、Integer、BigDecimal是三个雷区。String的不可变性、字符串常量池、intern()方法,老生常谈但总有人答不完整。Integer的-128到127缓存范围也经常考,你写Integer a = 100; Integer b = 100;,比较结果是true,但改成128就变成false,很多人会忽略。BigDecimal更坑,构造器传double和传String结果完全不同,new BigDecimal(0.1)会得到一长串近似值,必须用new BigDecimal("0.1")。
2.3 数组越界与异常体系:如何回答“异常”类问题
数组越界异常在笔试里出现频率极高,因为排序、遍历、动态规划这些编程题都涉及下标访问。(ArrayIndexOutOfBoundsException)是个运行时异常,编译期完全检查不出来,只有跑到那行才会炸。线上环境如果出现这种错误,多半是数据分批逻辑错了,或者循环边界用了<=而不是<。准备面试时,可以顺便把整个异常体系梳理一遍:
- 受检异常(checked exception):编译期必须处理的异常,比如IOException、SQLException,常见处理方式有try-catch和throws。
- 非受检异常(unchecked exception):运行时异常,比如NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException,编译期不强制处理。
- Error:严重问题,比如OutOfMemoryError、StackOverflowError,不建议在代码里主动catch。
面试时如果被问到“异常处理的原则”,我通常会这样答:能用非受检异常处理的不要声明成受检异常;自定义异常时要继承合适的异常类;不要在finally块里return,因为会覆盖try或catch中的返回值。这个答法既有代码经验,又有设计思想,比单纯背分类要高级很多。
3. 集合与容器:HashMap、比较器与对象排序的实战细节
3.1 HashMap、ArrayList底层机制与扩容
Java集合框架是校招笔试的重头戏,几乎每场必考。HashMap是当之无愧的C位,需要掌握的底层机制包括:JDK 1.8之后底层由“数组+链表+红黑树”组成;默认初始容量16,负载因子0.75;当链表长度大于等于8且数组长度大于等于64时,链表转红黑树;扩容时新容量为旧容量的两倍,且元素的位置要么在原位置,要么在原位置加旧容量的偏移。
为什么要说“要么在原位置,要么加偏移”?这跟HashMap计算索引的方式有关。正常情况下索引是hash & (n - 1),扩容后n变成了原来的两倍,等价于在二进制高位多了一位,所以旧元素要么不动,要么整体移动旧容量的距离。很多候选人能背出扩容因子是0.75,但说不清为什么是0.75——太高容易发生哈希冲突,太低浪费空间,0.75是时间与空间的折中。
ArrayList的重点是扩容机制。ArrayList底层是Object数组,默认第一次添加元素时容量为10,之后每次扩容到原来的1.5倍。注意它扩容用的是oldCapacity + (oldCapacity >> 1),也就是右移一位的位运算,不是* 1.5。这种细节在源码里很常见,笔试偶尔也考。如果提前知道数据量,最好在构造时指定初始容量,避免频繁扩容带来的数组复制消耗。我在代码review时见过太多因为不指定容量导致性能突刺的情况,面试时主动提这个点会加分。
3.2 Comparator.comparing把指定元素排到第一位
这是一个比较偏实用但很经典的题目:有一个对象列表,我想让某个指定元素排在最前面,其他元素按原来的顺序排列。最简单的实现是:
list.sort(Comparator.comparing(item -> { if (item.getId() == targetId) { return 0; } return 1; }));但这样写有个隐患:Comparator.comparing要求返回的key是可比较的,而且相等返回0意味着排序算法认为它们“相等”,在稳定性上没问题,但如果你直接返回boolean的false/true,那就不行了。比如:
// 错误示例:Boolean的自然顺序是false < true list.sort(Comparator.comparing(item -> item.getId() != targetId));这个写法会把false(也就是目标元素)排前面,但逻辑不够清晰,而且一旦比较逻辑复杂,很容易出错。更可读的写法是:
list.sort(Comparator .comparingInt((Item item) -> item.getId() == targetId ? 0 : 1) .thenComparing(Item::getOrder));先用comparingInt把指定元素映射为0,其他映射为1,再用thenComparing做次级排序。这样既保证了目标元素在最前,又不影响其他元素的相对顺序。面试官考这道题,通常不是要你用最精简的写法,而是看你能不能解释Comparator的返回语义:负数表示小于,0表示相等,正数表示大于。能把这三个值说清楚,比把代码背下来要重要得多。
3.3 equals与hashCode的约定
集合考点里另一个容易踩坑的是equals和hashCode。Java规定:如果两个对象equals比较为true,那么它们的hashCode必须相同;反过来不成立。所以当你重写equals时,必须同步重写hashCode,否则HashMap、HashSet这类依赖hash的集合会出现严重问题。
你可能会问:我不重写hashCode会怎样?举个例子,我定义一个只重写equals不重写hashCode的类,然后用HashSet去重,两个内容相同的对象会被当成两个不同的元素,因为它们的hashCode不同,被放到了不同的桶里。这个坑在真实业务里特别隐蔽。面试官往往让你设计一个Person类,要求按id判断相等,这时候你就需要这样写:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return id == person.id; } @Override public int hashCode() { return Objects.hash(id); }记住一个原则:equals里用到的字段,hashCode里也尽量用到,两者保持一致性。用Objects.hash()方法可以避免手写散列算法的错误,但注意它的性能相对较差,框架底层会自己实现,普通业务代码里用没问题。
4. JVM内存与编译期异常:从OutOfMemoryError到“源发行版17”的完整排查链路
4.1 OutOfMemoryError: insufficient memory到底是什么错
java: outofmemoryerror: insufficient memory这种报错,单独看前半句是JVM内存不足,但“insufficient memory”和常见的Java heap space不完全一样。Java heap space通常指堆内存不足,而insufficient memory可能指操作系统层面没有足够的本地内存给JVM分配。也就是说,可能是堆太小,也可能是你机器本身内存快被占满了,还可能是容器限制导致JVM申请内存失败。
排查路径一般是这样:第一步,用jmap -heap看堆内存使用情况;第二步,用jstat -gcutil观察GC频率和Full GC次数;第三步,结合线程快照jstack看是否有线程持有大对象不释放。如果是容器环境,还要检查容器内存限制和JVM参数是否匹配。比如你给JVM设了-Xmx4g,但容器总内存只有4g,系统其他进程也要占用,那JVM启动时就会报insufficient memory。
笔试不一定会让你写完整排查步骤,但一定会考“OOM有哪些类型”和“如何定位OOM根因”这两个方向。准备时可以围绕几个经典场景展开:大对象分配、内存泄漏(对象被集合长期引用)、线程过多导致本地内存不足、MetaSpace不断增长。每一个场景对应一个解决方案,这样答简答题时才不空泛。
4.2 Lombok报错与注解处理器问题
热搜词里有个很经典的报错:java: you aren't using a compiler supported by lombok, so lombok will not work。这个报错通常发生在你用新版本JDK编译项目,而项目依赖的Lombok版本过旧,注解处理器没有适配该JDK。Lombok不是运行时的库,而是编译期通过javac的注解处理器生成代码的工具,所以它和JDK编译器的兼容性非常敏感。
遇到这个错误,我建议按以下顺序排查:
- 查看JDK版本:
java -version。 - 查看Lombok版本:在Maven依赖或Gradle配置里确认。
- 升级Lombok版本到适配JDK的新版本,如果项目用的是Spring Boot,可以直接用Spring Boot BOM管理版本,避免手动配出错。
- 如果升级后仍然报错,检查IDE的注解处理是否开启。IDEA中需要开启“Annotation Processing”,Eclipse也需要在Compiler设置里勾选。
- 检查是否在编译命令中显式指定了
-processor参数,如果和Lombok冲突,会导致Lombok处理器没有被调用。
这类问题在笔试里不会让你现场解决,但“为什么Lombok需要编译期注解处理”是很好的面试题。理解了注解处理机制,就不会遇到报错时一脸茫然。
4.3 “警告: 源发行版 17 需要目标发行版 17”的根因与修复
java: 警告: 源发行版 17 需要目标发行版 17本质上是一个编译版本不匹配的问题。意思是当前编译器的source版本是17,但target版本没有跟上,或者相反。最常见的场景是:本机安装的JDK是17,但项目里的pom.xml或IDEA中的Language Level还停留在8或11;或者你用Maven编译时,没有配置maven-compiler-plugin的release参数,导致Maven用了默认版本。
标准的修复方式是在pom.xml里显式声明:
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>更推荐用release参数,因为它会同时约束source和target,还能避免误用JDK内部API:
<properties> <maven.compiler.release>17</maven.compiler.release> </properties>如果你用的是IDEA,还需要检查三处设置保持一致:Project Structure中的Project SDK、Project language level,以及Settings里Java Compiler的Target bytecode version。这三个地方只要有一个不一致,就会出现这种警告。我在帮别人排查环境问题时发现,大部分情况根本不是代码问题,而是IDE里的缓存或默认配置没刷过来,重启或Reimport一下Maven项目就好。
4.4 定位编译问题的完整排查链路
上面提到的错误类型,很多时候不是单一原因,而是一连串环境配置叠加导致的结果。我给自己的排查流程总结成一个固定套路,遇到这类问题先走一遍:
- 复现问题,记录完整报错信息,包括堆栈中的文件名和行号。
- 确认JDK版本与项目要求的Java版本是否匹配:
java -version和mvn -version分别看。 - 检查Maven/Gradle构建工具的编译参数,尤其是source、target、release。
- 排查依赖版本,重点看注解处理器相关依赖(Lombok、MapStruct等)是否和JDK兼容。
- 检查IDE配置和项目配置是否一致,必要时清除缓存重启。
- 最后再用一个最小示例去验证基础配置,排除代码自身问题。
这套流程看起来简单,但非常实用。笔试中如果遇到“结合实际排查经验的题目”,按这个思路作答,面试官会觉得你真的是在线上处理过问题的,而不是只会背命令。
5. 算法与数据结构:排序算法在笔试中的标准答法与边界控制
5.1 冒泡排序的写法与优化
手写冒泡排序几乎是Java工程师笔试的保留节目。它本身不难,但不同实现之间的差异能看出候选人代码功底。最基础的写法:
public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); } } } }这是入门版。进阶一点,可以加一个swapped标记,如果某一轮没有任何交换,说明数组已经有序,直接退出循环:
public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } 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]) { swap(arr, j, j + 1); swapped = true; } } if (!swapped) { break; } } }不要小看这个优化。最好情况(数组已经有序)下,基础版冒泡排序的时间复杂度是O(n²),优化后可以降到O(n)。面试官问“冒泡排序在什么情况下效率最高”,答案就是“已经接近有序时”,因为可以提前退出。写代码时还要注意边界条件,入参为null或长度为0/1时直接return,避免数组越界。
5.2 快速排序的Java实现与性能
快速排序是面试中出现频率数一数二的排序算法,因为它既考分治思想,又考递归和指针操作。快速排序的平均时间复杂度是O(n log n),最坏情况是O(n²),最坏情况基本发生在每次选取的pivot都是当前区间最大或最小值。标准写法:
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; }这里的关键是partition函数里的循环不变量:区间[left, i)内的元素都小于pivot,区间[i, j)内的元素都大于等于pivot,j是当前扫描位置。快排手写题最怕边界条件搞混,j的遍历范围是left到right - 1,因为right本身是pivot,不能参与比较。最后把pivot交换到i的位置,保证pivot左边都比它小,右边都比它大。
笔试时如果能在快排实现后主动提一句“最坏情况发生在数组已经有序且每次取最后一个元素作为pivot时,可以通过随机化pivot或三数取中来优化”,会显得思路更完整。当然,如果编程题明确要求快排,就不要花太多时间优化,先把正确版本写出来,再去补充说明。
5.3 复杂度的分析与边界控制
排序算法题里,除了写出代码,面试官还会追问“为什么这个复杂度是这个量级”。这时候要能说清楚冒泡排序的每一轮会确定一个元素的最终位置,所以需要n-1轮,每轮比较n-i次,总次数是等差数列求和,得到O(n²)。快排则用主定理或递归树来理解:每次partition把数组分成两半,递归深度是log n,每层处理总规模是n,所以平均O(n log n)。
边界控制是手写排序最容易踩坑的地方。我之前看过不少人写快排,用left <= right还是left < right分不清。实际情况下,递归终止条件用left >= right更安全,因为当区间只有一个元素或为空时就不需要再排序。另外,swap函数如果自己实现,要注意传入数组引用,不能值传递:
private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }这段代码本身不难,难的是在面试的压力环境下不写错。手写代码时我会刻意遵循“先画递归展开图,再确定循环不变量,最后写循环”的顺序,这样能大大减少边界Bug。
6. 框架与工具链:Spring Boot、API安全与开发环境排错能力
6.1 Spring Boot项目结构、自动配置与启动流程
爱奇艺这类公司后端基本都是Java技术栈,Spring Boot是绕不开的一环。笔试范围里Spring Boot考察的点集中在三块:项目结构、自动配置原理、启动流程。
项目结构方面,至少要知道Controller、Service、Mapper、Entity、Config这些包的作用。这是最基础的工程分层。自动配置原理是面试官最爱深挖的问题,核心注解是@SpringBootApplication,它组合了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解。而自动配置的核心机制是通过spring.factories或AutoConfiguration.imports导入大量的XxxAutoConfiguration类,再用@Conditional系列注解判断当前环境是否需要加载某个配置。
我建议准备一个实际场景:为什么引入spring-boot-starter-web依赖后,不需要手动配置Tomcat就能启动Web服务?答案就是因为ServletWebServerFactoryAutoConfiguration这个自动配置类在classpath中检测到Servlet相关类,于是自动创建内嵌Tomcat的ServletWebServerFactory。面试时能把“自动配置”拆解到“条件装配”这一层,就比大多数只会说“开箱即用”的人强。
6.2 API Key安全对接与接口设计
热搜词里有一条“java springboot apikey 安全对接”,这其实对应着校招中对接口安全设计的考察。实际开发中,服务间调用、第三方平台接入都会用到API Key。笔试可能不会让你写出完整的鉴权过滤器,但会问“如何设计一个安全的API对接方案”。
我的常规回答分三点:
- 身份认证:用API Key标识调用方身份,可以放在请求头
X-API-Key中。Key本身只做身份标识,不能作为唯一凭证,需要配合签名。 - 请求签名:将所有参数按字典序拼接,加上时间戳和随机数,用HMAC-SHA256生成签名,服务端用相同的Secret重新计算,防止参数被篡改。
- 防止重放:时间戳超过一定范围(比如5分钟)直接拒绝,同时用Redis缓存随机数或请求ID,保证同一个请求不能被执行多次。
如果笔试让你写一个Spring Boot拦截器校验API Key,一个简单实现是写一个HandlerInterceptor,在preHandle方法中从请求头取出Key,和配置中心或数据库里的Key做比对,比对通过返回true,否则返回401。具体到代码:
public class ApiKeyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String apiKey = request.getHeader("X-API-Key"); if (!"expected-key".equals(apiKey)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } return true; } }当然实际工程里不会把Key写在代码里,会放在配置中心或环境变量中。但笔试时代码能体现你的思路就够了。
6.3 环境变量配置与VSCode运行Java报错乱码
环境变量配置属于“看起来简单但考起来没人敢说全对”的知识点。Java环境配置的核心是三个变量:JAVA_HOME、CLASSPATH、PATH。JAVA_HOME指向JDK安装目录,PATH里加上%JAVA_HOME%\bin,这样终端里才能直接执行java和javac。CLASSPATH在JDK 1.5之后其实很少需要手动配置,但笔试偶尔会问它的作用。
“VSCode运行java报错乱码”是另一个很常见的环境问题。VSCode默认控制台编码和项目源码编码不一致时,中文输出就会变成乱码。根源在于Windows控制台默认使用GBK编码,而项目代码是UTF-8。解决办法有三种:
- 在启动配置中加上JVM参数
-Dfile.encoding=UTF-8。 - 修改VSCode的
terminal.integrated.profiles.windows,把默认终端编码改为UTF-8。 - 在
settings.json中设置"java.debug.settings.console": "internalConsole",用VSCode内置控制台而不是外部终端。
如果遇到drozer找不到Java、PCL启动器提示Java环境异常,本质都和JAVA_HOME配置有关。遇到这种问题时,不要一上来重装JDK,先检查java -version能否正常执行,再检查echo $JAVA_HOME是否指向正确路径。这一套排查逻辑在面试时也能体现你的工程排错能力。
7. 备考策略:把“背八股文”变成“真正理解原理”
准备校招笔试,一个很常见的误区是只看不练,尤其是“Java面试八股文”这种资料,很容易让人产生“我好像都看过”的错觉。我记得自己第一次参加类似笔试时,提前一周把所有高频题背了一遍,结果遇到手写排序加解释HashMap扩容细节时,还是慌了。因为背下来的东西没有形成知识网络,题目换个问法就识别不出来了。
我后来调整了策略,效果提升明显。具体做法是:
- 按专题整理知识树,每个专题只留一张A4纸的笔记。Java基础、集合、JVM、并发、Spring、算法,每一棵树的根节点是一个核心问题,比如“HashMap是怎么工作的”“为什么JVM需要分代回收”。
- 每个知识点都配一个“实际排查场景”。比如学JVM时,就去本地写一个死循环创建对象的程序,让它触发OutOfMemoryError,再用jvisualvm连上去观察堆变化。这个过程比看十篇博客都有效。
- 算法题每天固定花一小时手写两个排序或链表题,写完再在纸上画出执行过程。重点不是背代码,而是训练边界控制能力。
- 找一套机考环境模拟题,严格按考试时间做一次,培养时间分配和压力应对能力。
笔记里不要写太多解释,只写关键词和图示。考前看笔记回忆完整知识点,回忆不出来的地方重点补。这个方法能把“背八股文”变成“用问题串知识”,面试时的表达也会更有结构性,因为你的大脑里存的是逻辑链,而不是孤立的一句话。
最后再分享一个我自己的习惯:每次笔试或面试结束后,把当时没有答上来的问题记到错题本上,标注“当时卡在哪一步”。然后回去翻源码、翻官方文档,尽量找到问题的原始出处。这个动作坚持几个月,你会发现很多看似零散的知识点其实都指向同一批底层机制——比如集合的哈希机制、JVM的内存管理、Spring的条件装配。把这些底层机制吃透了,换什么公司、换哪一年的秋招题,你都不会慌。