又是一年校招季,身边好几个学弟学妹都在刷爱奇艺的历年真题,问到我这里来了。说实话,爱奇艺2020校招Java方向这场笔试,在当年算是比较有代表性的一套题,覆盖范围很广,从Java基础语法到JVM、并发、数据结构、网络和数据库都有涉及,难度属于中等偏上,区分度做得不错。今天就把这场的题型结构、核心考点和踩坑经验整理出来,给准备大厂Java岗笔试的同学做个参考。
先说结论:这套题最大的特点不是"偏",而是"细"。它不会问你"什么是多态"这种概念题,而是给你一段很短的代码,让你判断输出结果,或者指出哪里会报错,再或者让你手写一个链表反转。这些题目看起来基础,但每一道都能筛掉一批基础不扎实的人。我当时考完最大的感受就是:Java基础八股文得背到肌肉记忆,但只背八股文又不够,必须真能写出能跑的代码。
1. 笔试整体布局与备考方向
1.1 题型构成与时间分配策略
爱奇艺这场笔试的题型比较常规,主要分为三个板块:选择题、编程题和简答题。选择题占了大概六成比重,每道题都不长,但信息量很大,经常在一道题里同时考察两三个知识点。编程题大概是两到三道,难度循序渐进,第一道通常是链表或字符串操作,后面会出现DP或者二分这类稍微需要想一想的题目。简答题集中在JVM和并发方向,需要你用文字把原理讲清楚。
时间分配上我建议:选择题控制在40分钟内做完,不会的先标记跳过,千万别在卡壳的题上耗太久。编程题留足70分钟左右,因为不仅要写对,还得考虑边界条件。简答题放在最后20分钟,这种题没有标准答案,按点给分,把关键术语写全、逻辑讲顺就行。我自己当年就是选择题磨太久,导致最后一题只写了一半,这个教训很深刻。
这场的考查范围从反馈来看,基本锁定在Java SE核心、JVM基础、并发编程、常用数据结构和算法、MySQL和网络基础这几个大方向上。Spring框架问得不多,这倒是挺出乎意料的,可能因为笔试是海选阶段,更看基本功,框架知识点留到了面试环节再去深入考察。
1.2 从岗位视角反推考点权重
看这套题之前,得先理解爱奇艺作为一家视频平台,后端Java工程师日常在做什么。搞懂这个,你就知道笔试为什么会这么出题了。视频平台的后端服务有很明显的特点:高并发访问(大量用户同时刷推荐流、看评论)、大流量冲击(热门剧集上线瞬间流量暴涨)、数据一致性要求高(播放进度、会员状态不能出错)。这些业务特点映射到技术上,就是并发编程、缓存设计、消息队列、数据库性能优化这几块必须扎实。
所以笔试里并发相关题目分量很重,JVM内存模型也反复出现,这两块几乎是必考的。反过来,像Swing、AWT这种桌面开发的内容,或者已经被Spring Boot封装得很好的底层细节,笔试基本不会涉及。备考的时候要有侧重点,别把时间浪费在低频考点上。聪明的做法是:先花两天时间把高频考点的框架搭起来,再针对自己薄弱的地方做专项突破,最后用整套真题做模拟训练。
2. Java核心基础:题目虽基础,陷阱却不少
2.1 面向对象特性与String类的隐藏考点
几乎所有Java笔试的第一道选择题都会从面向对象说起,这场也不例外。但爱奇艺的出题人显然不想只是让你背出四大特性,而是把封装、继承、多态揉进了具体的代码场景里。比如有一类题是给你父子类,各有一个静态方法和一个实例方法,然后通过父类引用调用,问你输出是什么。这里面就藏着两个关键的坑:静态方法不存在重写,只有隐藏;实例方法才有多态。如果你只是记住了"方法重写看运行时类型"这个结论,但没理解静态方法是类级别的,这题就答错了。
另外String、StringBuilder、StringBuffer的区别几乎是必考项。有一个经典变形:字符串用加号拼接,内部到底创建了几个对象?很多人张口就答"一个",但如果在循环里用加号拼接,那每一个迭代都会new一个StringBuilder,性能损耗非常大。这类题爱奇艺的问法通常不会直接问区别,而是给你一段字符串拼接的代码,问内存中产生了多少个对象,或者运行效率如何。我在实际写代码时也验证过:一万次循环用加号拼接和用StringBuilder拼接,耗时差距能有几十倍。
还有一种出题思路是把Integer缓存机制和"=="比较放在一起考。Integer a = 127; Integer b = 127; a == b的结果是什么?128呢?这涉及Integer内部类IntegerCache的缓存范围(-128到127)。这个知识点在Java开发中太容易踩坑了,尤其是做对象比较的时候,很多人习惯用"==",结果在数值超过127后莫名出错。笔试考这个,其实也是在考察你平时写代码的习惯。我的建议是:涉及到Integer、Long这些包装类型的比较,一律用equals,别去记缓存范围,记了容易忘,忘了就有bug。
2.2 集合框架:HashMap是永远的主角
Java集合框架这一块,HashMap的无悬念占据C位。爱奇艺的笔试题喜欢问这么几个角度:HashMap的底层数据结构、put操作的完整流程、扩容机制、红黑树介入的条件,以及JDK 1.8前后有什么变化。我印象比较深的一道题是:HashMap在并发环境下put,JDK 1.7可能产生死循环导致CPU 100%,JDK 1.8还会吗?答案是:1.8改用了尾插法,解决了扩容死循环的问题,但并发put仍然存在数据覆盖丢数据的风险,所以说HashMap并发不安全这个结论在1.8下依然成立。
还有一类题会考遍历方式。给你一个HashMap,让你在遍历过程中删除符合条件的key,问哪种方式正确。用for-each遍历的时候直接map.remove()会抛ConcurrentModificationException,用Iterator.remove()是安全的。这个知识点我在真实项目里踩过坑,有一次就是遍历中删除元素,测试环境数据量小没暴露,上了生产直接抛异常。大家要记住:凡是集合在遍历过程中做结构性修改(增删元素),就得用迭代器或者Stream的filter方案。
ArrayList和LinkedList的选择问题也经常出。题目会问你"频繁在列表头部插入元素,用哪个更合适",答案是LinkedList,因为它的链表结构在头部插入只需要改指针,复杂度是O(1)。但实际场景里我很少用LinkedList,因为ArrayList虽然头部插入是O(n),但底层是连续内存,缓存命中率高,数据量不大时反而更快。笔试考的是数据结构原理,但实际工程讲究的是综合权衡,这两者的差别,面试官也想听听你的理解。
3. JVM与内存管理:从内存模型到垃圾回收
3.1 运行时数据区与对象存活判定
JVM这一块是区分"会用Java"和"懂Java"的分水岭。爱奇艺的笔试题对JVM的考察很有深度,既考内存区域的划分,也考对象在运行时怎么流转。一个典型的选择题:new出来的对象,在新生代Eden区分配,经历Minor GC后存活,通过Survivor区(S0、S1)的复制算法进行年龄递增,当年龄达到阈值(默认15)时晋升到老年代。这里有个变体题:大对象(比如很长的数组)会直接在老年代分配,以避免在Eden区和Survivor区之间反复复制,这个策略叫什么?答案是"大对象直接进入老年代",可以通过-XX:PretenureSizeThreshold参数设置阈值。
对象存活判定这块,引用计数法和可达性分析是常考点。引用计数法有个致命缺点:解决不了循环引用问题。比如A引用B、B引用A,但两人都不再被外部持有,引用计数法会认为它们都存活,实际上是垃圾。可达性分析从GC Roots出发做遍历,GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、JNI引用的对象等。选择题爱考判断题:比如一个局部变量不再被使用后,它在GC Roots中的引用多久被移除?答案是即时生效(编译阶段就做了优化),所以局部变量作用域结束后,对应的对象引用就断开了,可以被GC回收。
3.2 垃圾回收算法与收集器选型
垃圾回收算法的选择题也比较多。新生代用复制算法,老年代用标记-清除或标记-整理。标记-清除会产生内存碎片,标记-整理避免了碎片但移动对象要STW。这三种算法各自的优缺点,基本每次笔试都会考。我记得有道题给出了两个GC日志片段,让你判断使用的是哪种收集器组合。看日志特征也能猜个大概:Serial New + Serial Old的特点是日志里显示"DefNew"和"Tenured",Parallel Scavenge显示"PSYoungGen"和"ParOldGen",CMS则显示"CMS Initial Mark"之类的阶段日志。
CMS收集器在爱奇艺笔试里出现频率比较高,因为它在响应时间敏感的服务里用得广。CMS的设计目标是低停顿,基于标记-清除实现,所以有碎片问题,而且并发阶段会占用CPU资源,在JDK 9之后被标记为废弃,未来由G1取代。G1则是把堆划分为多个Region,通过追踪各Region的回收价值和停顿时间预测模型,来达到可预期的停顿。笔试如果考到G1,大概率是问你它的核心优势是什么,记住"可预测停顿时间模型"这个答案就够了。
这里的备考建议:不要把每个参数都背下来,那是面试造火箭的玩法。笔试考得更多是概念理解和对比分析,你只要能把"运行时数据区划分""对象在堆中怎么流转""三种收集算法的区别""CMS和G1各自适用场景"这四条主线讲清楚,JVM这部分就稳了。我当年备考就是把这几条主线手写了一遍,边写边默背,效果比看十篇博客都强。
4. 并发编程:校招笔试的高频重头戏
4.1 线程安全与锁机制的考察方式
并发编程一直是Java笔试的金线,爱奇艺这场也不例外。选择题里有一类很多人会错的题:synchronized修饰在静态方法上和修饰在实例方法上有什么区别?答案是:静态方法锁的是Class对象,实例方法锁的是当前实例对象。如果一个类有静态同步方法和实例同步方法各一个,两个线程分别调用它们,是能同时进入的,因为锁的是不同的对象。这个点我一说你就明白,但考场上紧张状态下很容易想岔,所以备考时要把锁对象到底是什么彻底搞清楚。
ReentrantLock和synchronized的对比也是高频考点。公平锁与非公平锁、可中断、可超时、多个条件队列(Condition)支持,这些都是ReentrantLock的优势。但笔试很少问"谁更好",更多是给你一段代码,问加锁顺序不对时为什么会产生死锁。经典死锁四条件:互斥、持有并等待、不可剥夺、循环等待。只要打破任意一个就能避免死锁。实际编码中我常用的手段是"按固定顺序加锁"和"使用tryLock加超时",这两个策略配合起来基本能杜绝死锁。
volatile关键字也是必考之一,而且特别喜欢跟JMM(Java内存模型)一起考。volatile保证可见性和有序性,但不保证原子性。经典的counter++问题:20个线程各执行1000次自增,加了volatile结果依然不是20000,因为count++不是原子操作,包含读改写三步。笔试往往会问:在多线程环境下,想要实现原子自增应该用什么?答案是AtomicInteger或者LongAdder。我在实际项目里写统计类逻辑时,会用LongAdder替代AtomicInteger,因为在超高并发下LongAdder的吞吐量更好,它的核心思想是分段累加,最后再合并sum。
4.2 线程池与并发容器的常见考点
线程池是一个设计得非常巧妙的组件,爱奇艺笔试几乎每年都会出一道关于线程池的题。常见的出法分两种:一种是让你看代码判断线程池的运行结果,另一种是让你写一个最合理的线程池配置。Runable任务丢给一个核心线程数为2、最大线程数为4、队列容量为10的线程池,第15个任务提交时会走什么逻辑?这里关键顺序是:先判断核心线程是否满,满了放队列,队列满了才创建非核心线程到最大线程数,如果连最大线程数都满了就执行拒绝策略。所以第15个任务会触发拒绝策略,而不是创建新线程。这个流程很多人记混,我建议自己画流程图画三遍,画到条件反射为止。
关于线程池参数设置,笔试可能让你根据业务场景选参数。比如一个CPU密集型的计算任务,线程数应该设置在多少?通常建议是CPU核数+1。如果是IO密集型任务,比如大量网络请求和数据库读写,线程数可以适当调大,一个经验值是CPU核数乘以2再加上一些余量。但说实话,生产环境的线程池参数往往是压测压出来的,不是按公式算出来的,笔试考的是估算能力,你只要说出思路和依据就行。
并发容器方面,ConcurrentHashMap是当之无愧的主角。JDK 1.8的ConcurrentHashMap放弃了1.7的Segment分段锁,改用了CAS + synchronized来保证线程安全。锁粒度从Segment级细化到了桶(Node数组的每个元素)级,并发度大幅提升。笔试问"ConcurrentHashMap的size()方法是否准确"也是高频考点:在并发修改下,size()先无锁遍历统计三次,如果前后两次计数改变的次数超过阈值,就加锁重统计,返回的是一个近似值。CopyOnWriteArrayList也是一个有意思的考点,它的读操作完全不加锁,写操作复制一份新数组,因此读多写少场景下适用。但它的内存占用大,写操作开销高,选择题里会问你它的缺点是什么,答案就是:适合读多写少,不适合写多读少。
5. 数据结构与算法:编程题的主战场
5.1 高频编程题:链表与字符串操作
爱奇艺的编程题第一题通常是热身的,但热身不等于送分。链表反转这道题,看起来简单,能一次性写对的人却不多。迭代解法要设置三个指针prev、curr、next,循环里先保存next再反转指针方向,最后更新prev和curr;递归解法更简洁但容易被边界条件绕晕。我建议两种都要会写,并且要能分析各自的空间复杂度——迭代是O(1),递归会用到系统调用栈,空间复杂度是O(n)。如果笔试要求不限语言,用Java写完记得顺手处理空链表和单节点链表这两个边界情况。
字符串相关的题也出现过多次,比如判断两个字符串是否是异位词(anagram),常见解法有排序比较和哈希计数两种。排序的时间复杂度是O(n log n),哈希计数是O(n),笔试里优先写哈希计数。还有一种变形题:字符串中第一个只出现一次的字符。思路是用LinkedHashMap记录字符出现次数,因为LinkedHashMap保持了插入顺序,遍历一遍就能找到第一个计数为1的字符。如果追求极致效率,可以用一个长度为26(或256)的数组代替Map做计数,虽然代码看起来更底层,但省去了自动装箱拆箱的开销。
5.2 经典算法思维与边界条件处理
第二道编程题往往会上升到算法层面,二分查找是出镜率最高的。不是说让你背二分查找的模板,而是考变体:在有序数组中查找第一个大于等于目标值的位置(lower_bound),或者查找最后一个小于等于目标值的位置(upper_bound)。二分查找的坑无非是边界条件和循环不变量。我自己的习惯是使用左闭右开区间[left, right)来写,这样循环条件while (left < right)和mid的更新逻辑都很自然,不容易出错。笔试里如果时间紧张,可以先写出最基础的二分逻辑,再针对题目要求调整返回值。
动态规划也偶尔出现,比如最大子序和、最长递增子序列这类经典题。这些题的核心不是记住状态转移方程,而是理解状态定义。最大子序和的状态定义是dp[i]表示以第i个元素结尾的连续子数组的最大和,转移方程是dp[i] = max(dp[i-1] + nums[i], nums[i])。做过几道之后你会发现,大部分DP题的难点都在于"状态定义"这一步,一旦定义清楚了,转移方程就是一行的数学表达。另外千万别忘了处理输入输出:很多同学算法写对了,却挂在从标准输入读取数据上。笔试环境一般要自己写Scanner或BufferedReader,我建议直接用BufferedReader加StringTokenizer,性能比直接用Scanner好很多,尤其是数据量大的时候。
6. 数据库与网络基础:后端开发的基本盘
6.1 MySQL索引与事务隔离级别的核心理解
视频平台的数据量大、请求量大,数据库是后端开发的基本功,这部分在笔试里也占了不少分值。索引这一块有个经典送命题:最左前缀原则。题目经常给一个联合索引(a, b, c),然后问哪些查询能用上索引。关键判断标准是:查询条件里必须从索引的最左列开始连续匹配,跳跃了中间列就失效了。比如where b = 1 and c = 2用不上这个联合索引,因为跳过了最左边的a列;而where a = 1 and c = 2只能用上a列部分,因为b列断了,c列用不上索引。在实际工作中,联合索引的设计就需要结合业务查询频率来排序列顺序,高频查询条件放最前面。
事务隔离级别也是高频考点。MySQL默认的隔离级别是可重复读(Repeatable Read),它会问你可能出现什么问题(幻读),以及InnoDB怎么解决幻读。InnoDB通过间隙锁(Gap Lock)和Next-Key Lock来解决,在可重复读隔离级别下对范围查询加锁时,除了锁定命中的记录,还会锁住记录之间的间隙,这样其他事务就无法在间隙中插入新记录。有些题还会问你:如果想要读到其他事务已提交的数据,应该设置成什么隔离级别?答案是读已提交(Read Committed),这也是很多互联网公司生产环境实际使用的级别,因为可重复读下的间隙锁有时会造成不必要的锁等待。
6.2 HTTP、TCP/IP要点与Redis缓存基础
网络基础在笔试里考查得比较集中。HTTP状态码几乎每年必考,尤其是和重定向相关的301、302,以及和客户端错误相关的400、401、403、404、405,服务端错误的500、502、503。爱奇艺有大量图片和视频资源,涉及CDN和重定向的业务特别多,所以301永久重定向和302临时重定向的区别要手拿把掐。还有一个容易被忽略的点:HTTP和HTTPS的区别,笔试喜欢考端口号(80和443)以及TLS握手过程中客户端和服务端的交互步骤。
TCP三次握手和四次挥手是网络题的基本盘。三次握手的核心是确认双方收发能力正常,顺便交换初始序号;四次挥手则是因为TCP是全双工的,每个方向都需要单独关闭。笔试变体题:建立连接时如果SYN包丢失了会怎样?答案是客户端超时后重传SYN,重传次数由tcp_syn_retries参数控制。另外TIME_WAIT状态也是一个考点,主动关闭连接的一方会进入TIME_WAIT,持续2个MSL(报文最大生存时间),目的是保证最后一次ACK能到达对方,以及让旧连接的所有报文在网络中消失。
Redis在爱奇艺这类高并发业务里是缓存层的标准配置,笔试也会考察基础用法。缓存穿透(查询不存在的数据,请求打到数据库)、缓存击穿(热点key过期瞬间大量请求打到数据库)、缓存雪崩(大量key同时过期导致数据库压力激增)这三个问题及应对方案,是必背内容。穿透的解决方案是布隆过滤器或者缓存空值;击穿的方案是互斥锁更新缓存或者逻辑过期;雪崩的方案是过期时间加随机值、多级缓存。笔试考到这里,一般不会要求写代码,而是让你把解决方案思路讲清楚。
7. 易错题复盘:那些百考百错的经典细节
7.1 编程语言细节高频易错点集中梳理
在我接触过的几千个Java求职者里,有几个错误几乎是"全国统一"的。第一个是浮点数精确计算:0.1 + 0.2 == 0.3的结果是什么?答案是false,因为浮点数在计算机中无法精确表示。正确的做法是用BigDecimal来做金额计算,而且要用String类型的构造器(new BigDecimal("0.1")),不能直接传double。第二个是try-catch-finally的return陷阱:如果try块和finally块都写了return,finally的return会覆盖try的返回值。题目会问这段代码返回什么,你得知道finally块的代码永远执行,return语句虽然在语法上写在了try里,但实际执行顺序是等finally执行完才开始返回。
第三个是switch语句的类型支持:Java 7之前只支持byte、short、char、int和枚举,Java 7开始支持String,但long、float、double仍然不支持。这个点经常出现在选择题里,问"下面哪个类型不能用在switch中",很多人会错选String。第四个是数组和集合的相互转换:Arrays.asList()返回的List是定长的,不能调用add和remove,否则抛UnsupportedOperationException。这个坑我在实际代码里踩过,排查了半天才发现是Arrays.asList的坑。
7.2 代码阅读与输出判断题的解题思路
代码阅读题是选择题里最拉分的类型,它的特点是不直接问知识点,而是让你逐行模拟代码执行,推导输出结果。我的做题思路是:拿到代码先别急着读,先看main方法里创建了哪些对象,然后盯着类之间的关系(继承、组合),最后按顺序逐行执行,遇到方法调用时先判断是静态绑定还是动态绑定。静态方法看引用类型,实例方法看实际对象类型,这是解多态题目最快的方法。
还有一个经验:这种输出判断题往往藏着"看似能编译,实际编译不过"的陷阱。比如泛型相关的代码,List<Object>和List<String>之间不存在父子关系,List<String>不能赋给List<Object>,编译就报错。我当年就栽在类似的题上,选项里有个输出结果让我纠结半天,最后才意识到那段代码根茂本无法编译。所以拿到代码题,第一步永远是判断能否编译通过,然后再考虑运行结果。
关于刷题策略,我建议按"专题→套题→错题重做"三个阶段来推进。专题阶段按知识点分类刷,比如一天只刷HashMap相关的题,把它的各个变形都见一遍;套题阶段模拟真实笔试环境,限定时间做完整套卷子,训练做题节奏;错题阶段把做错的题整理到一个文档里,记录错因和正确的思考路径,考前反复看。这个方法虽然笨,但确实是我验证过最有效的备考方式。
8. 复盘总结:从笔试到Offer的路上,我踩过的坑
事后复盘这场笔试,我最想强调的是"基础"和"应变"的平衡。Java基础八股文背得再熟,不落到代码上都是虚的;但眼里只有题库和代码,不懂背后原理,面试环节也撑不过去。我自己备考时每天保持两个小时的编码时间,不是刷算法题,而是把集合源码、并发工具类的底层实现用Debug一步步跟进去看执行过程。这套方法学得慢,但记得牢,比单纯刷题效率高得多。
另外有一个很多人忽视的点:笔试过程中记得留意时间。选择题卡住就别恋战,编程题先写核心逻辑再补边界条件,简答题先把关键术语写上再展开解释。我的经验是三段式分配时间最合理:选择题控制在四成时间,编程题四成半,简答题一成半。提前两三分钟做完,用剩余时间检查一遍容易疏忽的角落,比如有没有忘了处理空输入、是否导入了需要的包。
最后再分享一个小技巧:大厂笔试题目虽然每场都不同,但知识点框架是相对固定的。把爱奇艺2020校招Java方向这套题吃透,重点掌握JVM、并发、HashMap、链表、MySQL索引和事务这几个核心板块,再配合平常养成的编码习惯,应对其他大厂的校招笔试也有八成把握。考完之后无论结果如何,一定要把做错的题彻底搞懂,这才是这套题最大的价值所在。