爱奇艺2020校招Java方向第二场笔试,考完那会儿我刷了不少讨论帖,发现大家对这套题的普遍评价是:整体不算偏,但基础不扎实的人会被打得很惨。这套题和第一场相比,算法题比重更重,并发和JVM的考察也更细,明显能看出来出题人是想让真正研究过底层的人浮出水面。作为经历过多个大厂校招笔试的人,我结合当时这类视频平台的技术栈和笔试特点,把完整考点拆开揉碎讲一遍,这份内容适合正在准备Java校招、想摸清笔试出题套路、以及投了爱奇艺或其他视频类互联网公司的同学参考。
1. 这场笔试到底在考什么:爱奇艺Java方向的出题逻辑
1.1 视频平台业务对Java工程师的真实要求
爱奇艺的核心业务是视频点播、直播、会员、弹幕、推荐和广告系统,这些业务有一个共同特征:高并发、大流量、对延迟敏感。你想想看,一部热剧上线,直播间同时在线几十万人,弹幕消息每秒可能几万条,会员购买和鉴权请求在高峰期也要扛住瞬间流量。这种业务场景决定了他们招Java工程师时,最看重的不是你会不会用某个框架,而是你有没有能力处理并发、内存、性能这类问题。
所以笔试题目表面上是考察Java基础语法,实际上是围绕“能不能上手写线上代码”这个目标来设计的。比如集合类的考点会指向HashMap的并发问题,JVM考点的指向是线上OOM怎么排查,算法题则直接对应日常开发里对数据结构和逻辑思维的硬性要求。理解了这一层,你再看题目就不会觉得东一榔头西一棒槌,它们本质上都在考察同一个东西:你对Java这门语言和它运行环境的理解深度。
1.2 笔试题型分布与时间分配建议
根据当时参加过考试的同学回忆和我看到的讨论信息,这套题的题型大概包含单选题、多选题、编程题,部分批次可能有简答题。整体分配大致是这样的:
| 模块 | 大致占比 | 考察重点 | 建议用时 |
|---|---|---|---|
| Java基础 | 25% | 语法、String、集合、异常 | 15分钟 |
| 并发与JVM | 20% | 线程池、锁、内存模型、GC | 20分钟 |
| 数据结构与算法 | 25% | 排序、链表、字符串、动态规划 | 40分钟 |
| 数据库与网络 | 15% | SQL索引、TCP协议 | 15分钟 |
| 框架与设计模式 | 15% | Spring、设计原则 | 10分钟 |
总时长一般90到120分钟,选择题不能犹豫太久,超过2分钟做不出来就标记一下先跳。真正拉开差距的是算法编程题,一定要预留充足时间。我那批同学里,有人选择题做得特别好,编程题直接白卷,最后一样被刷。笔试的规则是宁可选错不要空题,编程题哪怕暴力解法也要写上去,至少有测试点能拿分。
2. Java基础八股文:易拿分也易丢分的考点精讲
2.1 复杂对象比较与String:先搞懂==和equals
这套题里有一个很经典的组合:给你一段代码,问输出结果是true还是false,涉及String的==比较和equals比较。这种题是Java基础里最典型的“看着简单、错得最多”的类型,核心考的是字符串常量池和引用比较的概念。
String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); String s4 = s3.intern(); System.out.println(s1 == s2); // true,都在常量池里 System.out.println(s1 == s3); // false,一个是常量池引用,一个是堆对象 System.out.println(s1 == s4); // true,intern()返回常量池里的引用 System.out.println(s3.equals(s4)); // true,equals比较的是内容很多人在 s1 == s3 这里翻车,因为对“new String()到底创建了几个对象”理解不清晰。这里我多说一句:当常量池里已经有“abc”时,new String("abc")会创建两个对象,一个是堆里的String对象,一个是字符串对象内部的char数组;而s1直接指向常量池中的那个“abc”。所以==比较的是引用地址,当然不相等。
这类题真正的突破口是记住三条:第一,字面量赋值会走常量池;第二,new一定是在堆上创建新对象;第三,equals比较的是内容,除非你重写的时候没有覆盖默认实现。笔试里如果遇到自定义类没有重写equals的情况,那默认就是Object的==逻辑,比较引用地址。这个坑在选择题里也出现过。
2.2 HashMap在JDK 1.7和1.8的差异:面试官盯上的核心考点
集合类是Java基础里的重头戏,而这套题里HashMap出现的频率相当高。关于HashMap,我们被问得最多的问题无非是这几个:底层数据结构是什么?put的流程是什么?什么时候扩容?为什么线程不安全?
HashMap在JDK 1.8之后的底层结构是数组加链表加红黑树。put一个key的时候,先用key的hashCode计算一个扰动后的hash值,再通过(n-1) & hash确定在数组中的位置。如果这个位置已经有元素,就用链表把它们串起来,当链表长度达到8并且数组长度达到64时,链表会转成红黑树,降低查询时间复杂度从O(n)到O(log n)。
JDK 1.7和1.8的差异也是常考点,最核心的区别是:1.7用头插法扩容,多线程环境下扩容可能造成环形链表,下次get的时候死循环;1.8改成尾插法,解决了这个死循环问题,但多线程下put还是会丢数据,因为put操作整体没有加锁。所以HashMap线程不安全这件事,不是1.8修复了,而是没有彻底修复。并发场景请用ConcurrentHashMap,它用CAS加synchronized保证线程安全,粒度比1.7的Segment更细。
这类题在笔试里最常见的出法是你不需要写代码,而是给一段初始化代码问你容量是多少。比如new HashMap(7),实际容量是多少?这里考的是tableSizeFor,它会找到大于等于7的最小2的幂,也就是8。如果new HashMap(10),那还是16,因为10大于8,所以结果是16。这种细节题没有背过源码就很容易翻车。
2.3 异常处理与数组越界:一块代码暴露你的编码习惯
异常处理也是选择题高发区,特别是数组越界,也就是ArrayIndexOutOfBoundsException。出题方式通常是这样:给你一个for循环,让你找错误,或者给try-catch-finally的组合,问finally里的代码会不会执行,以及返回值是什么。
public static int test() { try { int[] arr = {1, 2, 3}; return arr[5]; } catch (ArrayIndexOutOfBoundsException e) { return 10; } finally { System.out.println("finally execute"); } }这段代码的返回值是10,而且finally一定会执行。但有个更隐蔽的考点:如果finally里也有return,那finally的return会覆盖try或catch里的return。比如finally里写return 20,最终返回的就是20。这个机制在真实项目里是反模式,但笔试题就爱考。我的建议是,笔试中看到try-catch-finally必须第一时间检查finally里有没有return,有的话直接锁定答案。
数组越界异常的底层原因是访问了不存在的索引,比如长度为3的数组你访问arr[3]。虽然这类异常本身很好排查,但笔试不会只考你认不认识这个异常,而是会用代码逻辑绕你。比如多层循环里索引变量被修改、二维数组的行列搞反,这些才是真正的坑。
2.4 枚举与Lambda:爱奇艺笔试题里的新特性考点
Java新特性在笔试里占比不大,但出现的频率相当稳定,这套题里就涉及枚举(enum)和Lambda表达式。
枚举在笔试里最常见的考法是问你枚举能不能继承类、能不能实现接口,以及枚举的构造器用什么修饰符。正确答案是:枚举隐式继承java.lang.Enum,所以不能显式继承其他类,但可以实现接口;枚举的构造器一定是private的,这是Java语言规范强制规定的。还有一个冷门考点:枚举可以定义抽象方法,每个枚举常量都可以有自己独立的方法实现,这一点在策略模式的场景里很实用。
Lambda表达式的考点稍微灵活一些,一般是给一个List,问你用Lambda和Stream怎么过滤、排序或者映射。比如把一组字符串按长度排序的写法:
List<String> list = Arrays.asList("java", "python", "c", "golang"); list.sort((a, b) -> a.length() - b.length());这里有个容易踩的细节:Lambda捕获的外部变量必须是effectively final,也就是这个变量在初始化之后不能被重新赋值。笔试里如果给你一段代码,在外层定义了一个int i,然后在Lambda里修改它,编译器一定会报错。这个点看过报错的人记得住,没写过的人容易懵。新特性这块最有效的复习方式就是打开IDE自己跑一遍,比看十篇八股文都管用。
3. 并发与JVM:高流量系统笔试的硬核区
3.1 线程池七参数与拒绝策略:背下来只是及格线
并发模块里,线程池是绝对的高频考点,爱奇艺这套题里至少有一道题在考ThreadPoolExecutor的构造参数。七个参数分别是:核心线程数corePoolSize、最大线程数maximumPoolSize、空闲存活时间keepAliveTime、时间单位unit、阻塞队列workQueue、线程工厂threadFactory、拒绝策略handler。
面试官喜欢问的其实是这个场景:核心线程已满、队列已满、最大线程也满的时候,新提交的任务会走到哪个拒绝策略。JDK默认提供四种策略:AbortPolicy直接抛异常、CallerRunsPolicy让提交任务的线程自己跑、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列里最老的任务。实际项目里我一般会选择CallerRunsPolicy,因为它在流量高峰期能起到天然限流的作用,让业务线程自己去执行任务,反馈压力而不是直接丢数据。
笔试还有一个高频变体:让你计算线程池的参数。比如一个任务平均耗时100ms,目标QPS是50,要求系统稳定,那核心线程数是多少?粗算一下,单线程每秒能处理10个任务,50 QPS至少需要5个线程。这个估算公式是:线程数 = QPS × 单任务耗时(秒)。笔试里一般考到这个程度就够了,但如果你能答出还要考虑CPU密集型和IO密集型的差异,肯定是加分项。CPU密集型线程数约等于CPU核数加一,IO密集型则核数乘以二。
3.2 volatile与synchronized的正确打开方式
并发题里volatile和synchronized经常放在一起考,核心区别就三句话:volatile保证可见性和有序性,但不保证原子性;synchronized保证原子性、可见性和有序性;volatile不能替代synchronized,反之亦然。
为什么volatile不保证原子性?举一个经典例子:多个线程同时执行count++,即使count被volatile修饰,结果依然可能小于预期。因为count++本质上不是一步操作,而是“读取-修改-写入”三步,volatile只能保证每次读取都拿到最新值,但中间这个改写的窗口期仍然存在竞态条件。笔试题里如果给你一段多线程累加的代码问结果,不要被volatile带偏,只要看到自增操作就应该想到atomic类或者加锁。
volatile真正的应用场景是状态标志位,比如线程启动和停止的标记,或者单例模式里防止指令重排的双重检查锁。考synchronized的时候,更多是问你锁的升级过程:无锁到偏向锁、轻量级锁、重量级锁。这里提醒一句,偏向锁在JDK 15里被默认禁用了,但笔试题目如果明确说JDK 1.8,答案还是按经典的锁升级过程来答。
3.3 JVM内存分区与OOM排查思路
JVM相关的题目在整套卷子里属于拉开分数梯度的部分。比较基础的是内存区域划分:堆、栈、方法区、程序计数器、本地方法栈。考得细一点会问你哪些区域会发生OOM,哪些不会。比如程序计数器是唯一不会抛OOM的区域,因为它的空间是固定的,每条线程一个。栈溢出抛的是StackOverflowError,堆和方法区才可能抛OutOfMemoryError。
结合热搜词里出现的“java: outofmemoryerror: insufficient memory”,这种问题其实在校招笔试里对应的就是OOM排查思路。如果让你解决一个堆内存溢出的问题,你的回答顺序应该是:先通过jmap dump出堆转储文件,再用MAT或VisualVM分析大对象,定位是哪个业务对象占用了过多内存,最后判断是内存泄漏还是内存分配不足,分别对应修代码和调堆参数。
# 查看JVM堆内存使用情况 jmap -heap <pid> # 导出堆转储文件 jmap -dump:format=b,file=/tmp/heap.hprof <pid> # 查看GC日志 jstat -gcutil <pid> 1000说实话,笔试不会让你现场敲这些命令,但如果你在简答题里能写出jmap、jstat这个级别的工具名,比只会说“调大-Xmx”要加分太多。另外还有一个隐含考点:Xms和Xmx的大小关系。生产环境建议把初始堆和最大堆设置成一样大,避免运行时频繁扩容和缩容,触发不必要的Full GC。
3.4 类加载与双亲委派:判断基础扎实程度的试金石
类加载机制在笔试里出现频次也不低,核心考点是双亲委派模型。主要类加载器分了三层:BootstrapClassLoader、ExtensionClassLoader、ApplicationClassLoader。双亲委派的核心逻辑是:一个类加载器收到加载请求后,不会自己先加载,而是先把请求委托给父类加载器,每一层都这么做,直到顶层,然后从上往下尝试加载。这样做最大的意义是防止Java核心类库被篡改,保证核心类在所有环境下都是同一个版本。
笔试里最喜欢这么出题:你自己写一个java.lang.String放入classpath,启动程序的时候会用哪个类加载器加载它?答案是不会加载你自己写的这个,因为双亲委派机制会让BootstrapClassLoader优先加载rt.jar里的String,导致你的实现被忽略。如果想破坏双亲委派模型,方法就是重写loadClass方法而不是findClass方法,这也是Tomcat等Web容器热加载的实现基础。这种题很多人只听了个概念、没深入理解,考场上很容易选错。
4. 手写算法:快速排序、冒泡排序等代码题的现场还原
4.1 排序题的两种出法:手写实现与分析复杂度
编程题里排序是最常见的题型,爱奇艺这套题里据说出现了快速排序和冒泡排序的变体。快速排序的考察不只是让你默写代码,还会问你时间复杂度和最坏情况。快排平均时间复杂度O(n log n),最坏O(n^2),最坏情况发生在每次选的基准值都是最大或最小元素的时候。绕开这个问题的方法是随机化基准值或者三数取中法。
我给出一个比较标准的主函数写法:
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[left]; while (left < right) { while (left < right && arr[right] >= pivot) right--; arr[left] = arr[right]; while (left < right && arr[left] <= pivot) left++; arr[right] = arr[left]; } arr[left] = pivot; return left; }冒泡排序相比之下考察频率低一些,但笔试里经常出优化版:如果某一轮没有发生过交换,说明整个数组已经有序,提前终止排序。这里有一段加优化标记的冒泡:
public void bubbleSort(int[] arr) { 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; } }我建议在笔试里优先写优化版冒泡,代码量没有多几行,但体现的思路层次完全不同。还有一点,笔试的评判系统对时间比较敏感,如果你在算法题里写了一个可以提前break的版本,在数据量大一点的时候能明显跑得更快,这是出题人隐藏的给分点。
4.2 链表反转与字符串处理:高频代码题的通用套路
链表相关的题目在爱奇艺笔试里出现概率极高,因为视频平台的业务里面,用户的行为序列、播放记录、推荐列表都是典型的链表或类似结构。链表题的核心考察点是指针的运用能力,其中反转链表是其他链表题的基础。
public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } return prev; }反转链表的记忆方法很简单:每一步都是“存下一个、指向前一个、往后挪”。不要小看这一个循环,很多变体题,比如反转链表的前N个节点、K个一组反转,都是从这一步扩展出来的。
字符串处理的题也很高频,常见的是判断回文、最长公共前缀、字符去重等。这类题有一件事特别重要:在Java里字符串是不可变的,每次拼接都会生成新对象,循环里用+会导致O(n^2)的时间复杂度。笔试里如果你的代码在循环中拼接字符串,一定要用StringBuilder,这既是性能考量,也是代码风格的加分项。如果笔试环境允许用Java 8以上的特性,可以用String.join或者Stream的Collectors.joining,一行解决问题。
4.3 从题目看算法准备的优先级
复盘这套题,我的判断是算法题的难度分布大概是:一到两道LeetCode easy级别的题目,一道medium级别的核心题,可能还有一道带业务场景包装的题目。优先级最高的几个方向分别是:排序(快排、归并)、链表(反转、环形链表、合并有序链表)、二叉树(层序遍历、最近公共祖先)、字符串(回文、子串问题)、动态规划(斐波那契、爬楼梯、背包问题)。
动态规划是很多人头疼的地方,但校招笔试里考得并不难。记住一个通用思路就够了:先定义dp数组的含义,再找状态转移方程,最后明确初始化条件和遍历顺序。拿经典的爬楼梯问题来说,dp[i]表示爬到第i阶的方法数,dp[i] = dp[i-1] + dp[i-2],初始化dp[1]=1、dp[2]=2。这是常数空间可以优化的版本,用两个变量滚动更新就行。如果在笔试里时间不够,先写出二维dp的版本也比空着强,因为评分是按测试点给的,能过一部分就是一部分分。
5. 框架与设计:Spring、数据库与设计模式在笔试中的体现
5.1 Spring IOC/AOP:概念题背后的设计思想
Spring相关内容在校招笔试里一般不会考得太深,但IOC和AOP这两个核心概念是绕不开的。IOC(控制反转)的考点是:传统开发里对象由自己new,Spring把new的过程交给了容器,对象之间的依赖关系由容器在启动时注入。这样做的核心价值是解耦,模块和模块之间不直接依赖具体实现,而是依赖接口。
AOP(面向切面编程)的考点主要是:可以在不修改业务代码的前提下,把日志、事务、权限校验这些横切逻辑织入到目标方法中。常见实现方式有JDK动态代理和CGLIB代理。这里有个区别,JDK动态代理要求目标类必须实现接口,而CGLIB是通过生成子类来实现代理,所以不需要接口。Spring默认对接口的实现类用JDK动态代理,对没有接口的类用CGLIB。但Spring Boot 2.x之后默认调整了机制,统一用CGLIB,这个区别你听说过吗?没听过的话,笔试里看到“Spring Boot中的AOP默认使用什么代理方式”很有可能答错。
5.2 SQL索引与慢查询:结合会员、弹幕等业务场景
爱奇艺这套笔试里有一道数据库相关的题,印象里是给一段SQL问慢查询的原因,以及如何优化。核心考点是索引失效的场景。比如在where条件中对索引列使用了函数,或者使用like '%关键词'这种前模糊查询,都会导致索引失效,走了全表扫描。
我建议你把最常见的索引失效场景背熟:对索引列做计算或函数、隐式类型转换、前模糊匹配、or连接非索引列、联合索引不满足最左前缀。
举一个很实际的业务场景:用户中心有一个订单表,需要查询某个会员最近90天的订单记录。如果表上建立了(create_time, user_id)的联合索引,而你查询时where条件写的是user_id = ? and create_time between ? and ?,那这个查询依然可以用索引,因为条件的顺序不影响索引命中,优化器会自行调整。但如果你在create_time上加了DATE()函数,索引就会失效。这道题的优化从来不是学会某条SQL,而是要建立“先explain看执行计划”的思维习惯。
5.3 设计模式:笔试题里的“场景识别题”
设计模式在笔试里一般不会让你默写某个模式的实现代码,而是给一个场景,问你适合用哪种模式。最常考的有:单例模式(线程池、配置中心)、工厂模式(创建对象时屏蔽细节)、策略模式(多种同类算法切换)、模板方法(流程固定但步骤可变)、观察者模式(事件通知)。
给个具体例子:视频平台每次用户登录成功后,需要发送消息通知、发放积分、记录登录日志,同时还可能扩展新的动作。如果把这些逻辑都在登录方法里写死,代码会越来越臃肿。这种场景最适合什么模式?观察者模式,把登录成功作为一个事件发布出去,各个监听器自己去订阅和处理。这题本质上是考“开闭原则”,对扩展开放、对修改关闭。笔试里我不建议背每个模式的UML图,重点掌握“什么场景下用哪个模式”就够了,判断对了模式,后面的简答题基本就能拿到分。
6. 笔试现场容易踩的坑与备考建议
6.1 环境问题:JDK版本、编译器乱码,别让环境坑了你
很多人刷了一堆题,结果倒在了笔试环境配置上。当时同学里有人遇到编译器把中文注释显示成乱码,还有人报“源发行版 17 需要目标发行版 17”这种错误,整个心态都崩了。这里我提个醒,校招笔试系统一般用的是线上编辑器,但很多题需要你本地跑一遍,环境没配好真的是致命伤。
Java环境配置这块,确保你的JDK版本和Maven或Gradle配置的source/target一致。如果出现源发行版问题,直接在当前项目的pom.xml里加:
<properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>如果你用的是VS Code写Java,中文乱码多半是编码问题。检查一下settings.json里有没有配置file.encoding,没有的话加一行:
{ "files.encoding": "utf8", "terminal.integrated.profiles.windows": { "PowerShell": { "path": "powershell.exe", "args": ["-NoExit", "-Command", "chcp 65001"] } } }这个乱码问题万一发生在线上笔试环境里,尝试切换浏览器、关闭浏览器翻译插件,多数能缓解。但最根本的解法永远是:提前把环境都跑通,笔试当天就不要折腾本地编译了。
6.2 时间分配失误:一道题卡住,整个考试崩盘
笔试最大的敌人不是题目难,而是时间分配失控。我见过太多人,选择题纠结太久,留给编程题只剩二十分钟,结果会写的也没时间写。我的策略是固定一套节奏:选择题每道不超过90秒,遇到犹豫的直接标记跳过,快速过完全部选择题;编程题先全部看一遍,挑最简单的、最确定能AC的题先做,再回头啃难题。
如果你在某道题上卡了15分钟还毫无头绪,果断换一个思路再试一次:把题目缩小到最简单的版本,先暴力解,拿到部分分,然后在这个基础上去优化。很多时候暴力解写出来之后,你会突然发现优化点在哪里,这是面试和笔试都通用的技巧。还有一个小经验:不要把第一道题想得太简单,有时候出题人故意把easy题藏在中间,顺序不是难度顺序。
6.3 系统性备考路线:一个月从刷题到拿offer
最后说下我建议的备考节奏,也综合了当时考过这场笔试的同学反馈。如果你的目标是接下来几个月的秋招,时间充裕的话按四周来规划:第一周主攻Java基础八股文,集合、异常、String这些高频考点过一遍,每看一个知识点就写一个小demo验证;第二周专攻并发和JVM,线程池参数、锁升级、内存分区、GC算法这些要能描述清楚,最好背下jps、jmap、jstat这些命令;第三周集中刷算法,LeetCode的hot 100题至少刷两遍,重点是这个提到的链表、树、字符串、动态规划几类题;第四周做模拟笔试,找真题套卷按真实时间限时训练,只练考场节奏和心态。
面试八股文这块,我的观点是背要背,但不能死背。最好的状态是能把每个概念用自己的话讲出来,比如“为什么HashMap线程不安全”,你能从一个具体场景开始讲,而不只是背结论。校招笔试的淘汰率虽然高,但它考察的东西是非常具体的,方向对了、基础扎实了,通过并不是难事。这套爱奇艺的题目就是一个很好的风向标,把这些点吃透,对你接下来面其他视频、直播类公司也有直接帮助。