ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

爱奇艺2019秋招Java笔试题B卷全解析:从HashMap到JVM

2026/8/31 21:18:12 拓冰建站 浏览量
爱奇艺2019秋招Java笔试题B卷全解析:从HashMap到JVM 不少同学一听到“校招笔试题”第一反应就是刷题、背概念觉得这就是过一道门槛而已。但如果你拿到的是爱奇艺2019秋招Java方向笔试题B这样的卷子你会发现它真正考的不是背得多熟而是你对Java这套体系有没有形成清晰的网状结构。这套题覆盖的模块跨度挺大从基础语法、集合框架、JVM到多线程、Spring框架都有涉及而且很多题目换了个表达方式就专门踩那些“看起来懂、实际模糊”的点。这篇文章我会以这套题为主线把各个知识模块的考点、出题姿势、典型真题和扩展思路都拆开讲一遍。不管你是正在准备秋招的应届生还是想查漏补缺的初级工程师甚至是想了解大厂笔试套路的面试官应该都能从这里拿到一些有价值的东西。1. 试题全景概览爱奇艺秋招Java笔试题的结构与特点1.1 笔试环节在秋招流程中的定位秋招和春招最大的区别在于量级。爱奇艺这类体量的互联网公司一个技术岗在牛客网或者自家招聘系统上收到的简历动辄几千份不可能全走面试流程。笔试存在的意义本质上就是第一轮“粗筛”把明显不具备基础编程素养的候选人在进入面试官视野之前过滤掉。所以这套笔试题的定位很清晰不考偏题怪题考的是Java工程师日常开发中最常用、最容易踩坑、最需要理解原理的知识点。你不需要是源码分析专家但你必须对类的加载机制、HashMap的底层结构、线程池的参数含义这些基础问题了如指掌。笔试题目通常不会直接问你“HashMap的put流程是什么”而是换个场景问扩容之后元素怎么迁移或者在高并发下会发生什么。1.2 题型结构与建议时间分配从近几年的经验来看这类大厂Java笔试题B卷通常会包含以下几类题型单选题主要考察语言基础细节比如访问修饰符的作用范围、基本类型的默认值、字符串比较等。多选题通常是概念辨析比如哪些集合是线程安全的、哪些异常是运行时异常这类题最容易丢分因为多选意味着漏选、错选都不得分。填空题一般是看代码写结果考察块执行顺序、静态变量初始化、异常处理流程等。简答题比如描述HashMap的put原理、synchronized和Lock的区别。编程题通常是字符串处理、数组排序、链表操作这类数据结构题偶尔会有一道多线程相关的场景题。整体做题时间一般控制在90分钟到120分钟内。我的建议是遇到拿不准的选择题别死磕给自己限时90秒超过就蒙一个先标记把时间留给后面的编程题。编程题分值高而且只要思路正确、代码能跑通得分效率比抠单选题高得多。2. Java基础核心考点拆解从语法到面向对象2.1 基本语法与运算符的陷阱这套试卷的基础部分通常会先出几个“热身题”考察基本语法和运算符细节。看着简单实际上坑不少。比如下面这个经典题型int i 0; i i; System.out.println(i);输出是多少答案是0。因为在Java中i作为一个表达式会先把i的值压入操作数栈然后才对局部变量表的i做加1操作最后再把操作数栈的值赋值给i。所以i i执行完i的值反而没有变化。这个细节比C语言中的处理方式更反直觉但恰好是笔试爱考的。再比如switch语句能接受的数据类型。很多同学只知道int其实在Java 7之后switch也可以传String类型原理是编译时把字符串的hashCode计算出来再用equals做二次校验。枚举类型也支持switch。这些看起来不起眼的小知识点简答题和选择题都爱出。还有一道高频陷阱题char类型能否存储一个中文字符答案是可以因为Java的char是16位无符号整数采用Unicode编码一个中文字符完全放得下。这个考点不算难但如果出题人把char和byte放在一起混淆就会有人选错。2.2 面向对象三大特性的出题角度封装、继承、多态这三大特性在笔试题里几乎从不缺席但出题方式往往不是直接问概念而是放在代码里让你判断结果。尤其多态相关的题稍微绕一下就有一大批人会错。比如一个典型的考察动态分派的题目class Animal { public void eat() { System.out.println(Animal eat); } } class Dog extends Animal { public void eat() { System.out.println(Dog eat); } public void bark() { System.out.println(Dog bark); } } Animal a new Dog(); a.eat(); a.bark(); // 编译报错a.eat()输出的是Dog eat因为JVM在运行时根据实际对象类型决定调用哪个方法这是动态绑定。而a.bark()编译都过不了因为编译期看的是变量声明类型AnimalAnimal里没有bark方法。这种“编译看左边运行看右边”的规则笔试必考。另外还有一个高频考点是静态方法不具备多态性。静态方法是类级别的跟具体对象无关。如果父类和子类都定义了相同的静态方法实际上这是隐藏而不是重写调用时根据变量的声明类型来决定。很多人在这上面翻车就是因为把静态方法当成了普通成员方法。2.3 字符串系列的经典对比字符串相关题目在Java笔试中的出现频率高得离谱几乎每套卷子都有。核心就是String、StringBuilder、StringBuffer三者的对比以及字符串常量池的运行机制。你需要记住的要点是String是不可变的每次拼接都会创建新对象StringBuilder是非线程安全的可变字符串单线程拼接效率最高StringBuffer是线程安全的内部方法加了synchronized但也因此有了额外的同步开销。以下代码是考场上出现频率极高的一个小题目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这里涉及到的核心知识点是字符串常量池。s1和s2都指向常量池中的同一个对象所以为true。s3是堆中新建的对象对象引用和常量池中的引用不同所以为false。intern()方法会把字符串内容放到常量池中并返回常量池的引用所以再次和s1比较又变成了true。把这道题的底层逻辑吃透字符串相关的选择题基本就拿下了。注意在笔试题里比较引用equals比较内容。凡是看到字符串比较的题先问自己一句题里比的是引用还是内容这一步判断正确答案就出来一大半了。2.4 异常体系与finally执行时机异常同样是Java基础模块的高频考点而且出题人非常喜欢把try-catch-finally和return结合起来考。比如这个经典题目public static int test() { int i 1; try { return i; } finally { i; } }返回值是多少是1而不是2。因为在try的return语句执行前会先把返回值1保存到临时变量中或者更准确地说是把这个值放入操作数栈然后执行finally中的i这时候i变成了2但返回值用的已经是保存好的1。所以无论finally里怎么改局部变量都不会改变这个已经确定的返回值。但如果finally里也写了return语句情况就不一样了。finally中的return会直接覆盖try中的返回值并且吞掉之前可能抛出的异常。所以在实际开发中有一条铁律永远不要在finally块里写return这会让你的代码逻辑变得极其难排查。这个知识点在爱奇艺的笔试题中出现过类似变体很多人在第一步判断对了第二步又因为finally里的操作而犹豫说明对执行顺序还是没完全吃透。3. 集合框架与泛型笔试中的重头戏3.1 List与Set的实现细节对比集合框架类题目在Java笔试题中的占比很高基本上占基础部分的30%以上。我喜欢把这部分称为“送分题和送命题之间的分水岭”因为集合的知识点每个人都知道一些但真正掌握底层原理的人并没有想象中多。ArrayList和LinkedList的对比是入门必考。ArrayList底层是Object数组查询按索引访问时间复杂度是O(1)随机访问性能优异但插入和删除如果发生在中间位置需要移动后续所有元素最坏情况是O(n)。LinkedList底层是双向链表插入删除只需要改变节点指针理论上时间复杂度是O(1)但前提是你已经定位到了那个节点如果按索引找节点它需要从头或尾遍历时间复杂度反而退化到O(n)。笔试题有时候会出一个场景在循环中频繁往集合头部插入数据应该选哪个答案是LinkedList。但如果题目改成频繁按index读取数据那必须选ArrayList。理解了底层结构这类选择题就是白送的。HashSet的底层其实就是一个HashMap它的元素存储在HashMap的key上value统一是一个固定的Object。所以HashSet要求放入的对象必须正确重写hashCode和equals方法否则会导致两个逻辑上相等的对象被当成不同元素存进去出现“重复元素”的假象。笔试多选题经常问“哪些集合不允许重复元素”会混入ArrayList、LinkedList之类的干扰项。3.2 HashMap的底层机制与扩容细节HashMap是集合框架中当之无愧的考点之王。爱奇艺这套卷子我印象里至少有一道简答题或选择题是围绕HashMap展开的。实际上我在后面的题目解析里会单独展开HashMap的put流程这里先提炼几个笔试必须记牢的点默认初始容量是16默认负载因子是0.75。当链表长度超过8并且数组长度大于等于64时链表会转为红黑树如果数组长度小于64优先扩容而不是树化。扩容时容量翻倍数组下标重新计算的方式是(n - 1) hash这样既高效又能保证分布均匀。JDK 1.7中并发扩容可能形成环形链表导致CPU占用100%JDK 1.8优化了扩容机制采用高低位拆分的方式避免了环问题但并发下仍会丢数据。这里有一个很值得展开的点为什么用数组长度减一再与hash值做位与运算而不是直接用取模因为只有当数组长度是2的幂次方时(n - 1) hash才等价于hash % n而且位运算比取模快得多。所以HashMap要求容量必须是2的幂即使你初始化时传了一个17它也会自动扩容到32目的是保证这个数学性质成立。这类推导过程写在简答题里一定能加分。3.3 线程安全的集合类怎么选笔试多选题经常列出一组集合类让你判断哪些是线程安全的。通常干扰项会包含HashMap、ArrayList、HashSet这些是明确的非线程安全类而正确选项一般是Hashtable、ConcurrentHashMap、CopyOnWriteArrayList、Vector等。但要注意一个更隐蔽的考点Collections.synchronizedList()可以把普通List包装成线程安全List它和CopyOnWriteArrayList有什么区别前者依然基于锁所有读写操作都加了synchronized后者在写入时复制一份新的数组读取完全不加锁所以读多写少的场景下CopyOnWriteArrayList性能更好。这类题目不会直接考概念对比而是出一种业务场景“实现一个缓存读操作远远多于写操作并且不允许读取结果出现脏数据应该选择哪种集合”答案就是CopyOnWriteArrayList或ConcurrentHashMap。你得能从场景回推出数据结构的适用条件而不是只背概念。3.4 泛型与类型擦除的边界泛型在笔试中不算大头但两三道选择题是有的。最常见的考点是Java泛型是编译期类型检查运行时类型信息会被擦除。例如ListString和ListInteger在运行时其实是同一个类型ArrayList你没法通过getClass()区分它们。因此有这样一个经典判断题ListInteger intList new ArrayList(); ListString strList new ArrayList(); System.out.println(intList.getClass() strList.getClass()); // true答案是true因为运行时class都是java.util.ArrayList泛型信息已经被擦除了。这类题考的就是类型擦除这个机制本身。理解了这一点你也就理解了为什么不能通过instanceof判断一个泛型集合的具体类型参数。4. 多线程与并发Java进阶的必争之地4.1 线程的创建方式与状态流转多线程是Java笔试进阶模块的第一座山。爱奇艺的试卷中多线程题目的占比通常不低于JVM因为并发能力是后端开发的核心要求。创建线程的方式书本上写得是两种继承Thread类、实现Runnable接口。但在实际开发中更常用的其实是实现Runnable或Callable接口并把任务提交给线程池。为什么因为Java是单继承的一旦继承了Thread类就不能再继承其他类灵活性大打折扣而实现接口的方式更符合“任务和线程分离”的设计思想。如果任务需要返回结果就选Callable搭配FutureTask获取返回值或异常信息。线程的状态流转也是高频考点。Java线程有六种状态NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。笔试会给你一段代码问你某一行执行完之后线程处于什么状态。比如调用t.join()会让当前线程进入WAITING状态直到被join的线程执行完毕调用Thread.sleep(1000)则进入TIMED_WAITING状态睡眠结束后自动回到RUNNABLE。提示很多同学把BLOCKED和WAITING混为一谈。其实这两个状态有本质区别BLOCKED是线程在竞争synchronized锁时没抢到锁被阻塞WAITING是线程已经持有锁主动调用wait、join或park进入等待。一个是被动等待锁一个是主动进入等待回答问题前先分清这个。4.2 synchronized与Lock的底层差别synchronized是Java内置关键字Lock是JDK提供的接口。笔试问二者区别的概率极高而且简答题和大题都适用。我整理了一个最精简的对比维度synchronized是隐式锁加锁和释放锁由JVM自动控制Lock需要手动调用lock()和unlock()通常配合try-finally使用。synchronized可以锁方法和代码块Lock提供了更灵活的锁获取方式比如tryLock()支持非阻塞尝试获取锁。synchronized在JDK 1.6之后引入了偏向锁、轻量级锁、重量级锁的升级路径性能已经不输Lock但在竞争激烈时Lock仍能提供更细粒度的控制。中断响应能力不同。synchronized在等待锁时不能响应中断Lock的lockInterruptibly()可以。笔试题如果给一段代码问你这段并发代码是否有问题常见的考点就是加了synchronized的方法如果锁的是this那把锁加到static方法上锁的就是Class对象两者不是同一把锁。这种情况在RPC框架或单例模式里特别容易发生属于“看似同步实则不同步”的经典场景。4.3 volatile与可见性问题可见性问题是并发编程中比较抽象、也比较难理解的概念所以笔试题常常围着它转。volatile关键字有两个核心语义保证变量在多线程之间的可见性以及禁止指令重排序。但volatile不保证原子性。i这种复合操作即使变量加了volatile在多线程环境下依然会丢数据。笔试多选题常问“哪些操作是原子操作”。答案是基本类型的赋值和引用类型的赋值比如int x 5和Object obj new Object()这些操作本身是原子的。但long和double在32位JVM上不是原子的因为在32位虚拟机中long/double的高低位分两次写入中间可能读到脏数据。不过在现代64位JVM上这个问题基本不存在了。出题人如果在这个点上出题很可能是在考你对JVM规范的了解程度。4.4 线程池参数与拒绝策略线程池相关的题目在大厂后端笔试题里几乎是必出的。核心参数有七个核心线程数、最大线程数、空闲线程存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。笔试的重点放在线程池的执行流程上当新任务提交时如果当前线程数小于核心线程数直接创建新线程执行如果大于等于核心线程数且队列没满任务进入队列等待如果队列满了且线程数小于最大线程数创建临时线程执行如果线程数已经达到最大且队列也满了触发拒绝策略。这个执行顺序必须记牢它和你想象的“先队列后扩容”是一致的不对准确说是——先核心线程然后队列然后临时线程最后拒绝策略。画成图就很好理解但在笔试中你需要在脑子里快速走一遍这个流程很多选择题就是把某个参数改一下问你队列满之后会发生什么。拒绝策略有四种AbortPolicy直接抛异常、CallerRunsPolicy由提交任务的线程自己执行、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队首任务。实际生产中一般不用DiscardPolicy因为静默丢任务很难排查。4.5 死锁的必要条件与定位手段死锁是并发编程的经典问题笔试简答题大概率会考。死锁产生的四个必要条件是互斥、持有并等待、不可剥夺、循环等待。任何一条被打破死锁就不成立。常见的笔试题是这样的有A、B两个线程线程A持有锁1等待锁2线程B持有锁2等待锁1。问该程序是否会发生死锁答案是会。但如果做题时稍微改变一下加锁顺序让两个线程都按“先锁1后锁2”的顺序获取资源死锁就避免了。所以解决死锁最常用的办法就是保证所有线程以全局一致的顺序获取多个锁。如果你在面试中遇到这个题光答出四个条件还不够最好能补一句实际项目中可以通过jstack命令查看线程堆栈来定位死锁发现Found one Java-level deadlock字样就能明确是哪两个线程、哪把锁出了问题。这种实操经验会显得你比背概念的人高一个段位。5. JVM与内存模型从运行时数据区到类加载5.1 运行时数据区的角色划分与对应关系JVM模块的题目在爱奇艺的Java笔试题里经常以选择题和分析题的形式出现。为什么互联网公司这么重视JVM因为线上服务一旦出现内存溢出、频繁GC、CPU飙升不懂JVM的人根本无从下手。运行时数据区可以分成五块堆、虚拟机栈、本地方法栈、方法区、程序计数器。其中堆是对象分配的主要区域也是垃圾回收的重点区域虚拟机栈保存着局部变量表、操作数栈、动态链接、方法出口等信息每个线程都有自己的栈。笔试常考的一道题是哪些区域是线程共享的哪些是线程私有的。堆和方法区是线程共享虚拟机栈、本地方法栈、程序计数器是线程私有。这道题的变体是堆外内存溢出和栈溢出分别对应哪个区域。栈溢出就是StackOverflowError通常由无限递归或过大的局部变量表导致堆溢出是OutOfMemoryError通常由对象不停创建且无法回收导致。这两个异常名字的字面含义就够你正确回答绝大多数场景题了。5.2 垃圾回收算法与分代收集JVM中的GC算法笔试常考三种标记-清除、标记-复制、标记-整理。标记-清除会产生内存碎片标记-复制不产生碎片但浪费空间标记-整理不会浪费空间但移动对象需要额外的开销。现代JVM采取分代收集新生代用复制算法老年代用标记-整理或标记-清除。为什么新生代用复制算法因为新生代的对象“朝生夕死”大部分活不过第一次GC复制算法的效率最高而且只需要保留少量存活对象空间浪费不大。老年代对象存活时间长用复制算法不划算所以选择标记-整理或标记-清除。笔试题可能还会问一个比例参数Eden区、Survivor区S0、Survivor区S1默认比例是8:1:1新生代可用的内存实际上是90%因为有10%的Survivor区作为复制算法的保留区。如果题目问你新生代内存利用率不是100%而是90%这就是考点。5.3 类加载过程与双亲委派模型类加载机制是JVM模块另一个高频考点。类从被加载到卸载的完整生命周期包括加载、验证、准备、解析、初始化、使用、卸载七个阶段。笔试常考的“哪个阶段做什么”通常在准备阶段和初始化阶段之间区分。准备阶段是为类变量static修饰的变量分配内存并设置默认初始值的阶段比如static int a 10在准备阶段a的值是0在初始化阶段才被赋值成10。这个点很细但出题人非常爱考尤其是当变量被static final修饰时——常量在编译期就已经确定准备阶段就赋予真实值不再有二次赋值的过程。双亲委派模型在Java 9模块化之后有所改变但笔试依然以经典模型为主。简单来说一个类加载器收到加载请求后先不自己加载而是委托给父类加载器一直往上回溯到启动类加载器只有父类加载器无法加载时才由子加载器自行加载。这样做最大的好处是保证Java核心类库的安全比如java.lang.String永远是由启动类加载器加载防止自定义类伪造核心类。5.4 类初始化顺序的代码分析类加载的初始化顺序是笔试代码填空题的宠儿几乎成为每套Java卷子的保留曲目。这类题一般长这样class Parent { static { System.out.println(parent static); } { System.out.println(parent block); } public Parent() { System.out.println(parent constructor); } } class Child extends Parent { static { System.out.println(child static); } { System.out.println(child block); } public Child() { System.out.println(child constructor); } } new Child();输出顺序是parent static、child static、parent block、parent constructor、child block、child constructor。记住这个口诀静态先行父类先行实例初始化时先父后子。静态代码块在类加载的初始化阶段执行且只执行一次实例代码块在每次创建对象时执行在构造函数之前。掌握这个顺序的关键点在于理解new子类对象时一定是先触发父类初始化再触发子类初始化而在实例化阶段又是先执行父类的实例代码块和构造器再执行子类的实例代码块和构造器。这个考点综合性很强把类加载机制和对象创建过程放在一起考能筛选出真正理解底层逻辑的人。6. Spring核心机制与常见笔试考点6.1 IOC容器与Bean的生命周期对于Java后端岗位Spring框架是简历上的标配也必然出现在笔试题中。爱奇艺的Java方向笔试题不会考特别具体的Spring Boot配置但IOC和AOP这种核心思想一定会涉及。IOC控制反转的本质是把对象的创建和管理权从程序员手中交给容器。这意味着你不需要在代码里频繁new对象而是通过配置文件或者注解声明依赖关系由容器完成装配。笔试不会直接问你概念而是问一个Bean从创建到销毁依次要经历哪些阶段简化后的Bean生命周期可以这样记实例化容器通过构造器或工厂方法创建Bean实例。属性填充把配置文件中的属性值或依赖的其他Bean注入进来。初始化如果Bean实现了InitializingBean接口调用afterPropertiesSet方法如果配置了init-method执行该方法。使用Bean处于就绪状态可以被业务代码调用。销毁容器关闭时如果实现了DisposableBean接口调用destroy方法如果配置了destroy-method执行该方法。掌握这个顺序不仅笔试能得分在业务中排查“Bean为什么没初始化完成就被使用了”这类问题时也有直接帮助。6.2 AOP的代理机制与五种通知类型AOP面向切面编程在笔试中的高频考点集中在两个代理方式的选择以及通知类型的执行顺序。Spring AOP默认使用JDK动态代理或CGLIB代理。JDK动态代理要求目标类实现接口通过InvocationHandler生成一个实现同接口的代理对象CGLIB代理通过继承目标类生成子类来增强方法所以目标类不能是final的。五种通知类型前置通知Before、后置通知AfterReturning、异常通知AfterThrowing、最终通知After、环绕通知Around。注意顺序问题——即使代码里没有异常After也会执行它在语义上等同于finally块。如果题中同时有AfterReturning和After在无异常场景下后者的执行时机要晚于前者。这个细节常被出题人拿来挖坑务必对比理解。6.3 SpringMVC处理请求的完整链路SpringMVC是Java后端的传统技能笔试选择题偶尔会考一个请求从接收到响应的完整流程。标准答案顺序是浏览器发起请求前端控制器DispatcherServlet接收。DispatcherServlet调用HandlerMapping找到对应的Controller方法。通过HandlerAdapter调用Controller业务逻辑。Controller处理完返回ModelAndView。DispatcherServlet把ModelAndView交给ViewResolver解析得到真正的View对象。View渲染出响应内容返回给浏览器。这里面有一个容易混淆的点ModelAndView携带的数据模型最终是怎么渲染到页面上的它通过View对象绑定数据不同的View技术绑定方式不同但整体链路是固定的。笔试如果出一道“哪个组件负责定位Controller”的题答案就是HandlerMapping不是DispatcherServlet也不是HandlerAdapter。把每个组件的职责边界划清楚这类题就不容易错了。6.4 SpringBoot自动配置的简化理解爱奇艺2019年正值SpringBoot大规模应用的时期笔试题也向SpringBoot倾斜。自动配置是SpringBoot的核心特性但笔试不会考原理太深的内容更可能问SpringBootApplication注解包含了哪些派生注解答案SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。笔试还可能考RestController与Controller的区别。前者的返回内容默认是JSON对象后者返回的是视图名称必须配合模板引擎或ResponseBody使用。这类题的难度不大但胜在覆盖面广它考察的是你有没有真正动手写过项目而不只是背过框架概念。如果你平时写过SpringBoot接口这些东西看一眼就知道答案。7. 典型真题模拟与答题思路拆解7.1 经典选择题HashMap的put流程这是一道不论哪家公司的卷子都会出现的题但爱奇艺的版本通常会在细节上做文章。题目大概是这样HashMapString, String map new HashMap(); map.put(key1, value1);问题当执行上述代码时以下哪一步不会发生A. 计算key1的hashCode并经过扰动函数处理 B. 根据数组长度n计算下标( n - 1 ) hash C. 如果该下标位置为空直接创建Node节点放入数组 D. 如果链表长度超过8立即将链表转成红黑树这道题的答案是D。因为链表长度超过8时还需要判断数组长度是否大于等于64如果数组长度不足64会先进行扩容而不是直接树化。出题人把“树化”的条件精简成“链表长度超过8”就是要考察你是否真正理解这两个条件的先后关系。如果你只记得“大于8就转红黑树”这个口诀就会踩进陷阱。这类题给我的经验是经典考点一定要把触发条件背全尤其是“先扩容还是先树化”这种边界逻辑。捷径可以在笔试有限时间内得分但只有把源码实现看一遍才能保证万变不离其宗。7.2 经典代码题单例模式的DCL写法单例模式的写法笔试出现频率极高尤其是在“第二个编程题”或简答题里。最推荐的写法是双重检查锁Double-Checked Locking代码长这样public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }这道题的考点有两个外层判断和内层判断分别干什么为什么instance必须加volatile。外层判断是为了避免每次调用都进入同步块减少锁竞争内层判断是为了防止多个线程同时通过外层判断后一个线程创建完实例后另一个线程再次创建新实例。而volatile的作用是禁止指令重排序。instance new Singleton()这行代码在JVM层面可以拆成三步分配内存空间、调用构造器初始化对象、把引用指向那块内存。如果没有volatile编译器和CPU可能把第三步重排到第二步之前。线程A先执行了引用赋值但对象还没初始化完线程B此时判断instance不为null直接返回了一个半初始化的对象程序就出问题了。如果笔试题让你手写单例直接用这个DCL版本并在旁边简单注释两句说明为什么用volatile。如果你能在注释里写清楚“防止指令重排序导致返回未初始化对象”这道题的分数基本就稳了。7.3 经典编程题数组中的重复数字编程题部分爱奇艺笔试常见的是数据结构与算法题不太会超纲到动态规划或数论。通常是一道LeetCode简单或中等偏简单的题目。比如“找出数组中任意一个重复的数字”public int findRepeatNumber(int[] nums) { SetInteger set new HashSet(); for (int num : nums) { if (set.contains(num)) { return num; } set.add(num); } return -1; }这里考察的不是你会不会放for循环而是你能否想到用HashSet来降低时间复杂度。如果你对集合有基本的掌握这道题的思路几乎是秒出的。但还有一种更“省空间”的进阶解法把数组本身当作哈希表用因为题目如果限定“数字范围在0到n-1之间”那么可以通过交换元素的方式在O(1)空间内解决。面试官看到你写这种解法会认为你对数组下标和值的对应关系有深入理解。笔试编程题的时间限制比较紧我的建议是第一优先级是保证代码能跑通不要把时间浪费在追求最优解。先写出用集合的解法如果时间充足再优化千万不要一上来就写复杂算法导致最后连基础版本都没写完。7.4 经典简答题synchronized与Lock的区别简答题在笔试中分值不低爱奇艺的卷子一般会有两三道。考场上要写出一段结构清晰、踩分点明确的答案建议按以下模板来写synchronized是JVM层面的关键字底层通过Monitor对象实现使用简单不需要手动释放锁。Lock是java.util.concurrent包下的接口使用前提是必须手动调用lock()获取锁并在finally中调用unlock()释放锁。synchronized在JDK 1.6后引入了偏向锁、轻量级锁、重量级锁的升级过程锁粒度粗但性能已经大幅优化。Lock支持非阻塞获取锁tryLock、可中断获取锁lockInterruptibly、超时获取锁tryLock(timeout)并且可以实现公平锁。两者适用的业务场景也不同简单方法级同步用synchronized减少出错概率需要尝试获取锁、或者需要公平锁、或者需要处理锁中断等高级场景选Lock更合适。答案里只要出现这几个关键词——JVM层面、手动释放、可中断、公平锁、性能优化踩分点就覆盖得差不多了。答题时注意分段不要一大段写完阅卷体验会好很多。8. 备考策略与踩坑经验8.1 笔试中的时间分配策略秋招笔试的时间是固定的但题目难度分布不平均。我见过不少同学在单选题上抠得太久导致后面编程题只有十几分钟最后只能写个半成品。我自己的做题策略是“三遍法”第一遍快速过完全部题目把一眼就有把握的题直接填上答案第二遍集中攻克需要计算和推理的题目比如代码结果题、多线程分析题第三遍再回来啃那些拿不准的题目。这样做的好处是确保自己有把握的分数先落袋而不是被一两道难题卡住全盘节奏。对于编程题把时间控制在每题20分钟以内。如果20分钟还没思路先写一个暴力解法保证用例得分再考虑优化。毕竟笔试不是竞赛分分必争才是策略。8.2 常见错误类型与规避方法根据我接触到的考生复盘和批改经验Java笔试最常见的错误集中在五个方面第一字符串比较用错了运算符。比较的是引用地址内容比较要用equals。这个问题看似幼稚但在紧张状态下出题人稍微包装一下场景很多人就会写错。第二多线程代码中忘记处理中断或异常。比如用ExecutorService提交任务后没有正确关闭线程池导致程序无法退出或者Thread.sleep()漏写InterruptedException捕获。虽然笔试不跑代码但编译错误一眼就会被扣分。第三集合框架的边界条件不清晰。比如HashMap的容量为什么是2的幂次方链表在什么条件下树化这些“底层原因”如果只背结论不记原理遇到换着法子提问的题目就容易翻车。第四类初始化顺序判断错误。这个问题很典型不是不会而是紧张时把“父类静态块”和“父类实例块”的顺序搞反。解决这个问题没有什么捷径只能通过多练几道同类型的题目形成肌肉记忆。第五答题时分点不清。笔试简答题的人工批改最怕看到一大段没有结构的文字。不管答案对不对逻辑层级混乱会直接影响阅卷人的判断。我建议简答题一律使用“第一…第二…第三…”的分点格式让踩分点一目了然。8.3 高效刷题路线从基础到真题如果你想系统准备Java笔试我的建议不是盲目刷题而是按阶段推进。第一阶段打好语言基础。围绕《Java核心技术卷I》或者网上的Java基础系列教程把集合、异常、I/O、反射、泛型这些点过一遍。这个阶段不需要做难题重点是建立完整的概念框架。第二阶段攻克并发和JVM。这两个模块是校招笔试的分水岭推荐阅读《Java并发编程的艺术》和《深入理解Java虚拟机》。不要试图一口气看完而是按知识点跳着读比如今天只看线程池的源码分析明天只看垃圾收集器对比。第三阶段做真题和模拟题。牛客网上有大厂历年真题先去刷选择题建一个错题本把每次做错的知识点记录成一条考点笔记。等你积累了100条左右的错题笔记再回头看整套卷子会发现很多题都开始变得有规律可循。第四阶段冲刺编程题。刷LeetCode的热门100题重点放在数组、链表、字符串、二叉树、栈和队列这几个类别上。笔试编程题很少考图论和高级动态规划所以不要花大量时间在偏题怪题上。8.4 一套亲测有效的错题整理模板说到错题整理很多人买一个厚厚的笔记本抄了一堆题但基本不看第二次。我后来摸索出了一种更实用的方式——给每条错题建立三个字段知识点、错误原因、正确理解。举个例子知识点HashMap扩容错误原因只记住了负载因子0.75忘记了扩容后元素重新定位的具体过程。正确理解扩容后新下标要么是原下标要么是原下标加原数组长度判断依据是节点hash值新增的bit位是0还是1。这样整理出来的错题记录到考前翻一遍就能快速唤起记忆比对着题目重做一遍效率高得多。这套方法在秋招期间帮我节约了很多复习时间亲测有效。9. 写在最后练好内功比刷题更重要爱奇艺2019秋招Java方向笔试题B这份卷子放在今天看依然有很强的参考意义。它考的很多东西——HashMap的底层机制、线程池的执行流程、JVM内存模型、Spring的核心思想——都是Java工程师长期依赖的基础能力。你可能在面试中不会遇到一模一样的原题但换汤不换药的题目会反复出现。我个人在准备笔试时最深的一点体会是刷题只是手段真正让你和竞争者拉开差距的是你对一个知识点能不能做到“知其然也知其所以然”。同样的考题有人靠背答案得分有人靠底层理解推导出答案后者在后面的技术面试中往往走得更远。最后再分享一个小技巧在笔试前找几张白纸把HashMap的put流程、SpringMVC的请求链路、线程池的工作机制、synchronized与Lock的对比分别画一遍流程图。画得出来说明你是真的理解了画不出来回去再看一遍对应的源码和文档。这个办法比临时抱佛脚刷几十道题更管用。祝正在准备秋招的你笔试顺利offer到手。