
2018年秋天我在牛客网上参加了爱奇艺2018秋季校招Java工程师的第二场在线笔试。那会儿Java 8已经普及Lambda表达式、流式处理慢慢成了面试高频点网上关于Java面试八股文的讨论也开始热起来。这场笔试和后续面试给我的整体印象是难度不算变态但覆盖面很广算法、Java基础、集合容器、并发、JVM全部都有涉及而且不少题专挑你以为自己会、但其实没想透的知识点下手。这篇复盘我拖了挺久才整理完。当时考完只记了一些零散笔记后来秋招结束工作后又回头重新把排序、集合、异常这些高频点捋了一遍才觉得有底气把考场上的思路和踩过的坑写出来。内容会尽量贴近当年第二场的真实考察方式也会把现在回头看更清楚的知识点补进去。无论你是正在准备Java校招的同学还是想系统梳理一遍Java基础、想看看大厂笔试面试怎么出题的人这篇文章都值得花十分钟过一遍。1. 爱奇艺2018秋招Java第二场整体考点设计拆解1.1 笔试环节的核心模块爱奇艺的校招笔试是在牛客网这类在线评测系统上完成的第二场Java工程师的题目结构大致分成三个模块选择题、编程题、以及少量主观题有些批次有简答有些没有第二场我记得是有两道简答的。选择题覆盖了Java基础、集合、多线程、JVM、数据库、计算机网络和Linux常用命令范围很广但不偏门属于那种“你认真看过一遍Java面试题大全就能做对大半”的风格。编程题部分基本是两道算法题一道偏简单、一道中等难度。第二场的题目我记得有一道涉及字符串处理和数组排序的变形题。这类题在LeetCode上属于easy到medium之间的水平难点往往不在算法本身而在边界条件的考虑和代码规范上。在线笔试的编译器不会帮你做代码风格检查但如果你的代码连编译都过不了那就直接零分。1.2 面试环节更看重什么笔试之后的面试才是重头戏。爱奇艺的技术面通常是两到三轮再加上一轮HR面。技术面里面试官不太会按八股文逐条问你而是喜欢从一个基础问题开始不断往深处追问直到你答不上来为止。这个过程比我预想的要“累”很多因为每个问题都不是孤立的知识点而是串在一起的。比如面试官问“HashMap的底层结构是什么”你背过八股文能答出“数组加链表加红黑树”但接下来他可能会追问为什么链表长度到8才转红黑树为什么加载因子是0.75红黑树和链表的分界点为什么是6而不是7如果你只是死记硬背到这里就卡住了。爱奇艺的面试官普遍很喜欢这种追问方式所以光靠背题是过不了关的必须理解背后的设计逻辑。1.3 为什么这套考点设计值得反复研究回头看我参加过的好几场秋招笔试发现大厂的校招题目都有类似逻辑基础题用来筛掉基本功不扎实的人算法题用来筛掉代码能力不过关的人深度追问用来筛掉只会背答案的人。爱奇艺第二场也不例外。这套设计对准备校招的同学有很强的参考价值。它告诉你Java基础要按“是什么、为什么、怎么用、有什么坑”四个层次去准备算法要把排序、字符串处理、链表操作这些高频题型练到闭着眼都能写出来同时要习惯在纸上或者白板上不依赖IDE写代码。这几个能力听起来很基础但真到考场上紧张加上时间限制能把基础题写得干净利落的人并不算多。2. 笔试算法题排序与边界条件的实战拆解2.1 手写冒泡排序别小看这个“送分题”第二场笔试的选择题里考了冒泡排序的时间复杂度编程题也有一道题可以直接用排序打底。冒泡排序在面试里常被当成热身题但真让你手写不少人在边界条件上会翻车。冒泡排序的基本思想很简单每一轮从头开始两两比较把大的往后挪经过n-1轮之后数组就有序了。但我建议你掌握的不是最基础的版本而是带优化的版本。不优化的话就算数组已经有序它依然会傻乎乎地跑完n-1轮白白浪费时间。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]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { break; } } }关键点就两个第一外层循环只需要跑n-1轮第二每一轮排好一个最大值内层比较范围可以减一。加个swapped标记之后最好情况下时间复杂度能到O(n)这在笔试里是很加分的细节。我当年第一次写的时候外层循环写了n轮内层循环从0到n-1结果虽然也能排出来但存在一次多余的比较和交换面试官看到这种代码印象分就会打折扣。2.2 快速排序考场上最稳的实现方式快速排序在爱奇艺笔试里出现概率很高因为它在笔试系统里测效率比冒泡好太多。当年第二场的编程题里有一道“把数组按某个规则重排”的题用快排思想解决是最快的。快速排序的核心是分治加分区。我推荐你记住“挖坑法”来实现因为这种写法在笔试中不容易写乱逻辑最直观public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left]; int i left; int j right; while (i j) { while (i j arr[j] pivot) { j--; } if (i j) { arr[i] arr[j]; i; } while (i j arr[i] pivot) { i; } if (i j) { arr[j] arr[i]; j--; } } arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }这里有个非常容易错的点内层两个while循环里一定要加上i j的条件。当年跟我一起参加笔试的同学就是因为漏了这个条件代码在数组有序的时候直接数组越界编都编不过。另外选择基准值的时候不要选中间值选最左边的就行这样最简单不容易错。如果你还想着“优化一下选中间值”反而容易把自己的思路绕晕。2.3 数组越界和递归边界最容易丢分的隐形杀手Java里的数组越界异常ArrayIndexOutOfBoundsException是我在爱奇艺笔试中第一个踩到的坑。在线笔试系统只提示“程序运行时出错”不会告诉你具体哪一行越界了所以你必须自己从逻辑上排查。排查思路通常就几条检查循环边界条件里有没有“多1少1”的问题检查空数组和长度为1的数组检查递归的终止条件是否覆盖所有情况。比如快速排序里if (left right) return;这个条件漏了等号就会出现无限递归最后把栈打爆。考场上我的习惯是写完代码后先用三种输入在脑子里模拟走一遍空数组、单元素数组、随机数组。空数组走一遍能发现很多初始化问题单元素数组走一遍能发现边界条件问题随机数组走一遍能验证核心逻辑。这个习惯帮我避免了不少无谓的罚时。2.4 字符串处理题注意不可变性与性能第二场笔试里有一道字符串题要求对给定字符串做某种变换。这类题在Java里有个隐藏考点String是不可变的每次拼接都会产生新对象。如果你用String 在一个循环里拼接字符串性能会非常差数据量大的时候甚至会拖垮整个程序。正确做法是用StringBuilder或StringBuffer。笔试中一般用StringBuilder就够了单线程环境不需要线程安全。我当时用StringBuilder重写之后代码跑得快不说还顺带避免了某些情况下可能出现的OOM风险。很多同学在牛客网刷题时习惯用Python或者C到了Java笔试才发现字符串处理的方式完全不同这个一定要提前适应。3. 面试八股文考点从Java基础到集合容器的深度追问3.1 面向对象爱奇艺面试官爱问的设计题爱奇艺技术面问到面向对象时不会让你背“封装、继承、多态”的定义而是给你一个场景让你设计。我印象很深的一个问题是“如果让你设计一个视频播放器状态流转的类你会怎么设计用户的行为有播放、暂停、拖动进度条、缓冲、结束这些状态怎么管理”这个问题考的是你对状态模式的理解以及对Java面向对象设计的掌握程度。第一反应是写一堆if-else判断当前状态然后决定下一步行为但这样类会变得特别臃肿新增一个状态就要改一堆代码。更好的方式是用枚举加状态流转表或者用状态模式把每个状态封装成独立类。我当时选择用枚举实现因为代码量少逻辑清晰。Java枚举类型本身就可以有属性和方法非常适合表达有限状态集合。这也是我在准备阶段看到的关于Java枚举类型的使用的最典型场景。public enum PlayerState { IDLE { Override public PlayerState play() { return PLAYING; } }, PLAYING { Override public PlayerState pause() { return PAUSED; } Override public PlayerState stop() { return IDLE; } }, PAUSED { Override public PlayerState play() { return PLAYING; } Override public PlayerState stop() { return IDLE; } }; public PlayerState play() { throw new UnsupportedOperationException(当前状态不支持play操作); } public PlayerState pause() { throw new UnsupportedOperationException(当前状态不支持pause操作); } public PlayerState stop() { throw new UnsupportedOperationException(当前状态不支持stop操作); } }这样每个状态只实现自己允许的转换不允许的操作直接抛异常代码可读性和可维护性都很好。面试官看到这个方案一般会点头认可。这里想提醒一句面向对象的题目重点不在于你用了哪个设计模式而在于你能不能让代码在面对需求变化时尽量少改动。3.2 Lambda表达式Java 8时代的新宠爱奇艺面试问Lambda并不奇怪2018年那会儿Java 8已经用了好几年Lambda和Stream在业务代码里已经很常见了。面试官问我的一个题目是把一个字符串列表按长度排序长度相同按字母序排用Lambda怎么写ListString list Arrays.asList(apple, pear, banana, kiwi); list.sort((s1, s2) - { if (s1.length() ! s2.length()) { return Integer.compare(s1.length(), s2.length()); } return s1.compareTo(s2); });这里有一个很隐蔽的坑list.sort需要的是Comparator? super E编译器会根据Lambda表达式的上下文推断参数类型。如果你在IDEA里写编译器会自动提示正确的写法但如果在白板上手写很多人会把s1.compareTo(s2)写成s1 s2这种C语言风格的比较那就编译不过了。换成现在的写法其实还可以更简洁list.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()))。这是Java 8之后引入的比较器链式写法面试时提出来会很加分能说明你对Java函数式编程有实际理解。3.3 集合容器HashMap系列追问现场HashMap是Java面试里雷打不动的重点爱奇艺面试也几乎必考。面试官从“HashMap怎么存数据”问起一路追问到哈希冲突怎么解决、什么时候链表转红黑树、扩容机制是什么、加载因子为什么是0.75。关于加载因子0.75很多背八股文的人只知道“这是时间和空间的权衡”但如果面试官再问一句“为什么0.75是最优的”就答不上来了。这里面的讲究是如果加载因子太大比如1.0哈希冲突会明显增加链表的长度变长查询效率下降如果加载因子太小比如0.5空间浪费严重数组很多位置都是空的。0.75是泊松分布推导出来的一个经验平衡点在这个数值下链表长度达到8的概率已经非常低大概是千万分之一所以链表转红黑树的阈值设为8是有统计学依据的。面试中还容易问到HashMap和Hashtable的区别。除了线程安全、不允许null键值这些基础答案最好能补充一点HashMap为了性能在并发场景下可能出现数据覆盖或死循环JDK 7里扩容时可能出现所以在并发场景下应该用ConcurrentHashMap。这个补充能体现你不仅仅是背了区别而是理解了背后的设计动机。爱奇艺面试官听完这个回答后明显兴趣更浓又让我说了一下ConcurrentHashMap在JDK 8里为什么用CAS加synchronized替代了分段锁。3.4 标识符命名规则与代码规范这个考点听起来小儿科但爱奇艺笔试的选择题确实考了下列哪个是合法的Java标识符选项里有class、_name、2name、$name、name-1这类。正确答案是_name和$name因为Java标识符必须以字母、下划线或美元符开头不能以数字开头不能是关键字。这种题就是送分题但很多人复习Java基础时容易忽略。同样的还有Java运算符和表达式相关的基础题比如i和i的优先级短路运算符和||的行为。我当年就是因为太自信在这些送分题上丢了几分事后特别懊恼。建议大家在刷Java面试题的时候别只盯大块知识一些小而烦的点也要过一遍考场上每一分都很有价值。3.5 异常处理受检异常与非受检异常的边界爱奇艺面试问异常时问法比较实际“你的代码里什么时候应该抛受检异常什么时候应该抛非受检异常”这个问题的核心是区分Exception和RuntimeException。受检异常Checked Exception是编译器强制要求调用方处理或向上抛的比如IOException非受检异常RuntimeException是程序运行时才会暴露的比如NullPointerException、ArrayIndexOutOfBoundsException。我的回答思路是如果调用方必须根据异常做出处理否则程序无法继续就用受检异常如果异常是程序逻辑错误调用方没法通过代码去恢复就用非受检异常。比如参数校验失败应该抛出IllegalArgumentException这种非受检异常而不是让调用方去catch一个受检异常。面试官比较认可这个思路因为这是实际开发中总结出来的经验而不是死记硬背。3.6 并发与多线程从基础问到实战爱奇艺在并发这块问得比较多的是synchronized关键字和ReentrantLock的区别。这个问题虽然基础但可以往深了问synchronized在JDK 6之后经历了锁升级偏向锁、轻量级锁、重量级锁ReentrantLock支持公平锁、可中断、多个条件队列这些是synchronized不具备的。还有一道记忆很深的题多个线程同时对一个int变量做自增操作最终结果会小于预期值为什么这涉及可见性和原子性两个层面。i不是原子操作它分为读取、加一、写回三步多线程执行时可能互相覆盖。解决办法有几种用AtomicInteger的incrementAndGet、用synchronized或ReentrantLock加锁、用LongAdder做高并发计数。面试官还会追问JVM内存模型JMM里的volatile关键字以及volatile为什么不能保证原子性。我当时举了一个很经典的例子两个线程同时执行volatile int count的count因为自增不是原子操作依然会出现丢失更新。面试官点头认可后才算是把这个话题收尾。3.7 JVM内存与OutOfMemoryError不是一个“大”字能解决的JVM在面试里几乎是必问项。爱奇艺的面试官会问Java运行时数据区有哪些哪些线程共享、哪些线程私有栈溢出和堆溢出有什么区别热词里有一条java: outofmemoryerror: insufficient memory这个错误在实际笔试或本地运行时常出现很多人的第一反应是“内存不够就加大内存”但在面试里你需要答得更深OOM可能发生在堆、方法区、元空间或直接内存不同区域的OOM对应的问题不同。堆溢出通常是对象太多或内存泄漏导致的要先用jmap分析堆转储元空间溢出通常是因为加载的类太多或者动态生成类太多栈溢出则往往是因为递归深度过大。我当时也遇到过本地跑测试程序时报OOM的情况排查了半天才发现是代码里用了一个无限递归而不是真的机器内存不够。所以面试官问到OOM你如果能主动提到“我会先通过jstat、jmap、jstack这些工具去看内存和线程状态再做针对性分析”会让面试官觉得你真的是干过活的。4. 真实考场环境与高频踩坑实录4.1 在线笔试系统别让环境配置拖你后腿爱奇艺2018年的在线笔试用的平台对浏览器和Java版本有要求提前一天会发短信和邮件通知。这提醒大家一定要提前把环境配好不要等到考前半小时才慌慌张张去装插件。我记得当时有人因为浏览器插件没装好考试页面加载不出代码编辑器白白浪费了十几分钟。环境变量配置也是一个高频问题。Java环境变量配置详细教程网上到处都是核心就是JAVA_HOME、PATH、CLASS_PATH三个变量。当年很多同学在本地用IDEA开发自带JDK完全不碰环境变量但笔试平台不是IDE检测不到JDK就编译不了代码。建议至少学会在命令行执行java -version和javac -version来确认环境正常。4.2 编译告警与版本混乱那些年我们遇到的source/target报错热词里有一条很写实java: 警告: 源发行版 17 需要目标发行版 17。这类版本不匹配的问题在笔试后、本地练习时经常遇到。主要原因是JDK版本和项目编译级别不一致Maven或Gradle里配置的source/target版本低实际运行的JDK版本高或者反过来。2018年那会儿大家用的还是JDK 8普遍没有这个困扰但放到今天机器上装着JDK 17甚至JDK 21项目还按JDK 8的级别编译就会遇到这种告警。解决办法很简单在IDEA的Project Structure里把Project SDK和Project language level统一或者在pom.xml里把maven.compiler.source和maven.compiler.target改成一致。这个问题看着不严重但如果你拿来当笔试练习的代码编译不了心态会先崩。4.3 Lombok被编译器忽略的经典报错热词里有一条java: you arent using a compiler supported by lombok, so lombok will not work这个报错在我工作后经常看到校招阶段如果用了Lombok笔试系统里大概率编译不过。原因是Lombok依赖注解处理器在编译期修改AST在线评判系统的编译器版本或配置不一定支持。在爱奇艺这类校招笔试里我强烈不建议用Lombok老老实实写getter、setter和构造方法。理由很简单你无法控制评判系统的编译环境一旦Lombok失效整个类编译失败你连运行的机会都没有。这个教训是我在牛客网模拟笔试时踩过的当时差点因为这个丢掉整道编程题的分。4.4 常见问题速查表问题现象可能原因快速排查思路程序运行时报ArrayIndexOutOfBoundsException数组边界判断错误检查for循环结束条件、递归终止条件、空数组和单元素数组字符串拼接循环中性能极差String不可变产生大量临时对象改用StringBuilder不要在循环里直接拼接快速排序递归时栈溢出递归终止条件缺失或错误检查left right是否写成left right本地编译通过笔试平台编译失败JDK版本或编译器不兼容避免使用Lombok统一JDK版本运行报OutOfMemoryError堆内存不足或无限递归生成对象先用jmap -heap查看堆占用再用jstack看线程栈源发行版/目标发行版警告IDE编译级别与JDK版本不一致统一Project SDK和language level或统一pom配置多线程计数结果小于预期i非原子操作使用AtomicInteger、LongAdder或加锁不能只用volatile4.5 考场上那些“差一点就做出来”的遗憾每次复盘校招笔试总会想起一些明明能做对却丢分的题。我印象最深的是一道关于Java运算符优先级的选择题题目里有int a 10; int b a a;问b的值是多少。这种题在平时我一眼就能看出来答案是22但考场上一紧张脑子里想着后面的编程题就写出了错的答案。类似的还有String的equals方法abc new String(abc)返回falseabc.equals(new String(abc))返回true这题也经常出现在Java基础面试题里。其实都是很基础的知识点但因为在选择题里出现反而容易被低估。所以我想给准备笔试的同学一个非常实际的建议考前一定要腾出半天时间把Java基础面试题从头到尾过一遍尤其是运算符优先级、标识符规则、String比较、数组越界、List和Set的区别、HashMap的底层原理这些“小而烦”的点。它们看起来占分不多但选择题错多了笔试分就拉下去了。5. 从爱奇艺第二场到如今我的Java成长复盘回过头看2018年这场校招能聊的不只是具体的题目。我是从这场笔试之后才真正养成了系统整理知识框架的习惯。以前学Java是零散地看视频、敲代码这里会一点那里会一点但老觉得基础不牢。爱奇艺的笔试和面试让我意识到大厂考的不是你会不会写某个功能而是你对这门语言的理解深度。比如没有这场笔试我可能一直都不明白HashMap的加载因子为什么要设成0.75不明白lambda表达式背后其实是函数式接口也不明白为什么用StringBuilder拼字符串比用加号好这么多。这些知识点在项目里可能一辈子都用不到但它们决定了你面试时的底气。很多人说校招是“面试造火箭、工作拧螺丝”但我觉得能把火箭的图纸看明白的人拧起螺丝来也更让人放心。现在工作几年后再看这些八股文和面试题会发现它们不是没有价值的。像是java: 警告: 源发行版 17 需要目标发行版 17、Lombok编译报错、OOM排查这些热词其实都是真实开发环境里的常见问题。当年在校招笔试里踩过的坑工作后依然会遇到只是当时的我会慌现在的我能快速定位。如果你也在准备Java校招我想多说一句刷题和背八股文只是底线把它讲明白、能应对追问才是上线。找一个朋友扮演面试官让他从HashMap问到ConcurrentHashMap再问到JMM问到OOM问到线程池参数反复练几轮。等你能不看资料把一条知识链完整地讲给别人听的时候你就真的没问题了。