
投过爱奇艺2019秋招Java岗的朋友应该对这份《Java方向笔试题A》有点印象。那套题给我最直观的感受是不偏不怪但覆盖面非常广基础、集合、JVM、并发、算法全都扫了一遍而且很多题表面考记忆实际考的是你有没有真正写过代码、踩过坑。我当年做完对答案错了不少很多是“好像会但一选就错”的题。现在回头看这份题其实很适合拿来当Java校招复习的提纲。这篇文章就把我当时复盘的重点、常见的考点陷阱以及对应的底层原理整理出来给准备Java笔试的同学做个参考。1. 笔试全貌与考点分布先说这套题的整体结构。爱奇艺那场笔试是典型的互联网公司Java岗出题风格题型大致分为几类基础知识选择题、读代码判断题、程序输出题以及最后的手写算法题。A卷整体难度中等偏上不会一上来就给你特别偏门的底层原理但会在你“觉得会”的知识点上埋坑。考察范围比较集中我按当时的题目印象和复习重点整理了这样一张考点分布表考点模块主要考察内容约占比Java基础语法标识符、运算符、类型转换、面向对象特性30%集合框架HashMap原理、List/Set区别、比较器排序20%JVM与内存内存分区、异常类型、GC基本概念15%并发编程线程创建、线程池、锁、线程安全15%算法与数据结构排序、字符串处理、链表操作20%为什么爱奇艺这种视频网站会这样出题核心原因是后端业务对高并发、大数据量处理的依赖。视频平台有海量用户同时点播、评论、弹幕后端服务要处理大量请求Java工程师必须具备扎实的集合与并发功底所以HashMap原理、线程池参数、内存溢出排查这类题几乎是必考的。算法题则用来筛选基本的编码能力不要求你写出多高深的算法但常用的排序、链表翻转、字符串处理必须熟练。我当时复习策略也比较笨先把基础语法地毯式过一遍然后集中攻克集合和并发最后刷了几十道基础算法题。事实证明这个顺序是对的因为基础语法题虽然简单但陷阱最多最容易丢分。2. 基础语法陷阱标识符、运算符与面向对象基础语法这块是整份卷子最容易“大意失荆州”的部分。你感觉全都会但每道题都有让人犹豫的选项。我结合当年做题的感受和后来复习总结把高频考点一个个拆开说。2.1 标识符规则藏在细节里的送命题有一道题大概是问哪些是合法的Java标识符。选项里有$username、_id、1test、class之类。很多人看到class就觉得这是关键字肯定不行但看到$username会犹豫因为平时几乎不会有人用美元符号开头命名变量。Java标识符的硬性规则其实就三条只能由字母、数字、下划线_和美元符号$组成不能以数字开头不能是Java关键字或保留字。所以$username是合法的_id也是合法的1test非法class非法。这里有个冷门考点关键字不能用但true、false、null这三个严格来说不是关键字它们是字面量literal但同样不能用作标识符。还有goto和const是保留字也不能用。这些都是出题人爱埋的坑。汉字能不能做标识符可以。因为Java底层用的是Unicode字符集中文属于字母类字符所以String 名字 张三;是能编译通过的。但实际开发没人这么干笔试也几乎不考知道一下就行。2.2 运算符优先级与隐式类型转换运算符这块最容易出错的是自增自减和隐式类型转换。我给你出一道当年我做错的类似题感受一下int i 0; i i; System.out.println(i);输出是什么答案是0。原因是i这个表达式的值是i自增之前的值也就是0然后这个0赋值给i把自增的结果覆盖掉了。如果写成i i;输出就是1因为i表达式的值是自增后的值。这种题在选择题里出现的频率非常高考察的就是对表达式求值顺序的理解。还有一类陷阱是复合赋值运算符的隐式类型转换。看这段代码byte b 10; b b 1; // 编译报错不兼容的类型 b 1; // 编译通过为什么b b 1会报错因为b 1的结果是int类型int赋值给byte需要强制转换。而b 1等价于b (byte)(b 1)编译器会自动做强制类型转换所以不会报错。这类题考的是Java语言规范里复合赋值的隐式转型规则属于基础中的基础但每年都能错倒一批人。三元运算符也有类似的坑。看这个例子Object result true ? 1 : 2.0;result的值是1.0而不是1因为三元运算符的两个分支会做类型统一int会被提升为double。这类题在爱奇艺的卷子上出现过变体只要记住“二元数值提升”这个规则就能避开。2.3 面向对象特性重载、重写与初始化顺序面向对象部分重载Overload和重写Override的区别是必考题。爱奇艺的题比较精不给那种一眼就能看出答案的而是结合返回类型、访问修饰符、异常来考。重载看的是方法签名方法名相同、参数列表不同。关键点返回类型不是方法签名的一部分所以“仅返回类型不同”的两个方法不构成重载会编译报错。重写看的是继承关系子类方法名和参数列表要和父类一致返回类型可以是父类返回类型的子类型协变返回访问修饰符不能比父类更严格不能抛出比父类更宽的受检异常。初始化顺序也是超高频考点。有一段典型的笔试代码class Parent { static { System.out.print(P-static ); } { System.out.print(P-instance ); } Parent() { System.out.print(P-constructor ); } } class Child extends Parent { static { System.out.print(C-static ); } { System.out.print(C-instance ); } Child() { System.out.print(C-constructor ); } } public class Test { public static void main(String[] args) { new Child(); } }输出顺序是P-static C-static P-instance P-constructor C-instance C-constructor。记忆口诀很简单先父后子先静态后实例先实例块后构造方法。静态块只在类加载时执行一次所以即使创建多个Child对象静态块也只输出一遍。这个知识点在笔试里基本是必出的务必记牢。2.4 枚举与常用类的隐藏考点热搜词里出现了“java枚举类型的使用”这说明枚举也是笔试常客。Java枚举不是简单的常量集合它本质上是继承了java.lang.Enum的类。这意味着枚举可以有构造方法、成员变量和方法甚至可以实现接口。enum Level { LOW(1), MEDIUM(2), HIGH(3); private int code; Level(int code) { this.code code; } public int getCode() { return code; } }笔试常考的一个点枚举的构造方法默认是private的因为枚举实例是在类内部创建的外部无法通过new创建。还有个点是values()和valueOf(String)这两个静态方法是编译器自动生成的不是Enum类自带的但可以直接使用。常用类里包装类的缓存机制是经典考点。Integer默认缓存了-128 ~ 127之间的值所以Integer a 127; Integer b 127; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false这个知识点几乎每套Java笔试题都会出现。理解起来很简单Integer a 127会自动装箱调用valueOf(127)而valueOf对缓存范围内的值直接返回缓存对象所以a b指向同一个对象。超过缓存范围就new新对象比较结果自然为false。笔试里只要看到包装类用比较的题第一反应就应该是“缓存范围”。3. 集合框架与泛型HashMap为什么是必考王集合框架是Java笔试的绝对主力爱奇艺A卷里HashMap相关的题目占了不小比重。这部分不只是背结论要真正理解底层实现因为题目喜欢换个说法考原理。3.1 HashMap的实现原理与JDK版本差异爱奇艺的笔试时间在2019年当时JDK 8已经成为主流所以HashMap的考题基本围绕JDK 8的实现来出。但如果你在简历里写了熟悉JDK 7也可能会被问到两个版本的区别。JDK 8的HashMap底层是“数组链表红黑树”的结构。核心过程计算key的hash值通过(n - 1) hash计算出在数组中的下标。如果该位置没有元素直接放入。如果该位置有元素遍历链表比较hash和equals。如果key已存在覆盖旧值并返回旧值。如果key不存在插入链表尾部尾插法。当链表长度达到8且数组长度达到64时链表转为红黑树当树节点数小于6时退化为链表。这里有个细节容易被考为什么链表转红黑树的阈值是8因为源码注释里给了一个泊松分布的计算在负载因子0.75、随机hash的情况下链表长度达到8的概率大约是千万分之六是一个非常小的概率。这个阈值的设计是为了在理想情况下几乎不会触发红黑树化。HashMap和Hashtable的区别也是常考对比比较维度HashMapHashtable线程安全不安全安全方法加synchronizednull键/值允许不允许初始容量1611扩容方式2倍扩容2倍1遍历方式IteratorIterator/Enumeration3.2 Comparator与Comparable的底层逻辑热搜词里有一条“java comparator.comparing 将某元素值放第一个”这其实就是Comparator在排序中的典型应用。笔试里比较器排序也算高频题通常和Collections.sort()或Stream.sorted()结合出题。Comparable是内部比较器实现它的类需要重写compareTo方法定义自然排序规则。Comparator是外部比较器可以随时定义不同的排序规则而不改变类本身的代码。写个例子ListPerson list new ArrayList(); // 按年龄升序 list.sort(Comparator.comparingInt(Person::getAge)); // 按年龄升序年龄相同按姓名降序 list.sort(Comparator.comparingInt(Person::getAge) .thenComparing(Comparator.comparing(Person::getName).reversed()));那“把某个元素放第一个”怎么做比如想按年龄排序但希望年龄为0的人排在最前面list.sort(Comparator.comparingInt((Person p) - p.getAge() 0 ? -1 : 1) .thenComparingInt(Person::getAge));这个技巧笔试不一定考但面试问排序灵活性的概率很大知道这个写法会加分不少。3.3 fail-fast机制与并发修改异常集合里还有一个高频考点ConcurrentModificationException。看这段代码ListString list new ArrayList(); for (String item : list) { if (某个条件.equals(item)) { list.remove(item); } }这段代码在遍历过程中用list.remove()修改集合会触发fail-fast机制抛出ConcurrentModificationException。原因是ArrayList内部的Itr迭代器维护了一个expectedModCount字段每次next()和remove()都会检查实际修改次数modCount是否与期望值一致不一致就抛异常。正确的删除方式是使用迭代器自己的remove()IteratorString iterator list.iterator(); while (iterator.hasNext()) { String item iterator.next(); if (某个条件.equals(item)) { iterator.remove(); } }或者用JDK 8的removeIflist.removeIf(item - 某个条件.equals(item));这个知识点在笔试里常以“下面哪种方式不会抛出异常”的形式出现理解了底层机制就不会选错。4. JVM内存异常与排查路径JVM这块是Java笔试的分水岭基础好的同学能拿满分基础薄弱的全靠猜。爱奇艺A卷里关于内存异常的知识点考得比较多这跟视频网站后端处理海量图片、视频元数据有一定关系内存管理对服务稳定性至关重要。4.1 OutOfMemoryError的常见类型热搜词里有一条“java: outofmemoryerror: insufficient memory”这条虽然不是笔试原题但代表了面试官和笔试都爱问的方向内存溢出。OutOfMemoryError有几种常见类型每种对应不同的内存区域和场景笔试常考它们的区别异常类型内存区域典型触发场景Java heap space堆内存创建的对象太多堆无法分配GC overhead limit exceeded堆内存GC频繁但回收效果差超过98%时间在做GCMetaspace元空间加载的类太多元空间不足unable to create new native thread本地方法栈创建线程过多无法再创建新线程Direct buffer memory直接内存NIO使用DirectByteBuffer直接内存满笔试里常见的形式是给一段代码问你抛出什么异常。比如不断往List里添加大对象抛出的是Java heap space加载大量动态生成类抛出的是Metaspace多线程无限创建线程抛出的是unable to create new native thread。4.2 JVM参数配置与内存模型JVM参数也是必考项。爱奇艺A卷有一类题是问哪个参数能设置堆内存大小。这里理一下常规的JVM参数-Xms初始堆大小-Xmx最大堆大小-Xmn新生代大小-XX:MetaspaceSize元空间初始大小-XX:MaxMetaspaceSize元空间最大大小-XX:HeapDumpOnOutOfMemoryErrorOOM时自动导出堆快照-XX:HeapDumpPath堆快照保存路径实际开发中遇到堆内存溢出排查思路一般是先jps找到进程号再用jmap -dump:formatb,fileheap.hprof pid导出堆快照然后用MAT或VisualVM分析。笔试虽然不会让你写命令但会考你怎么配置参数、怎么分析堆快照理解这个排查链路对答选择题很有帮助。4.3 异常处理机制try-catch-finally与返回值异常处理是Java基础里的必考知识点爱奇艺的卷子也考了。最经典的是下面这类题public static int test() { int x 1; try { return x; } finally { x; } }返回值是多少答案是1。因为return x执行时先把x的值1保存在栈帧的返回值槽里然后执行finally块x变成2但返回的还是之前保存的值1。如果把finally改成return x;那返回值就成了2因为finally里的return会覆盖try里的return。所以有一条铁律不要在finally里写return否则会吞掉try块里的异常和返回值。受检异常和运行时异常的区别也是高频题。IOException、SQLException是受检异常编译器强制你处理NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException是运行时异常编译时不强制处理。笔试常考哪个异常属于运行时异常、哪个不是。5. 并发编程素养从线程池到锁的粒度并发是Java岗笔试的重头戏爱奇艺A卷在并发这块出题很有代表性不仅有概念题还有读代码判断输出的题。视频网站的场景天然和并发强相关万人同时刷弹幕、点赞、评论后端不加锁不做线程池控制系统早就崩了。5.1 线程池核心参数与拒绝策略线程池是必考题。JDK原生提供的Executors工具类可以快速创建几种固定线程池但阿里开发规约强制要求直接用ThreadPoolExecutor原因就是Executors创建的那几种线程池有隐患。ThreadPoolExecutor有七个核心参数笔试常考其中几个的含义参数作用corePoolSize核心线程数即使空闲也不会被回收maximumPoolSize最大线程数线程数不能超过这个值keepAliveTime非核心线程空闲存活时间workQueue任务队列用于缓存来不及处理的任务threadFactory线程工厂用于创建新线程handler拒绝策略当任务无法执行时的处理方式四种拒绝策略也要能区分清楚AbortPolicy直接抛RejectedExecutionException。CallerRunsPolicy让提交任务的线程自己执行这个任务。DiscardPolicy直接丢弃不抛异常。DiscardOldestPolicy丢弃队列中最旧的任务然后重新提交当前任务。为什么Executors.newFixedThreadPool()有隐患因为它用的是LinkedBlockingQueue队列容量是Integer.MAX_VALUE当任务提交速度大于执行速度时任务会无限堆积在队列里最终导致OOM。newCachedThreadPool()的坑在于最大线程数是Integer.MAX_VALUE会无限创建线程也可能造成OOM或线程过多。5.2 synchronized与volatile的底层区别synchronized和volatile的区别是并发选择题的标准配置。我复习时给这两个关键字做了个对比对比维度synchronizedvolatile原子性保证不保证可见性保证保证有序性保证通过锁保证通过内存屏障禁止指令重排是否阻塞是否使用场景复合操作、写操作依赖当前值状态标志、单次读写经典题目volatile int i 0;两个线程分别执行1000次i最终结果是多少答案是小于等于2000且不确定。因为i不是原子操作它包含读取、加一、写回三步volatile只能保证每次读写都从主内存读取和写入但不能保证三步之间不被其他线程穿插。synchronized解决这个问题的原理是锁的互斥性同一时刻只有一个线程能进入同步代码块其他线程必须等待。JDK 6以后synchronized做了大量优化引入了偏向锁、轻量级锁、重量级锁的升级路径笔试如果考到“锁升级”的过程要能说清楚。5.3 死锁的四个必要条件与排查死锁在多线程编程里几乎必考爱奇艺的卷子用一种变体题型考了给一段多线程代码问是否会产生死锁以及如何避免。死锁的四个必要条件是互斥条件资源在同一时刻只能被一个线程占用。持有并等待线程已经持有一个资源同时等待另一个资源。不可剥夺线程持有的资源不能被其他线程强行剥夺只能自己主动释放。循环等待线程之间形成循环等待资源的关系。笔试常考的是如何避免死锁。最简单有效的手段是破坏循环等待条件即对资源进行排序所有线程按固定顺序获取锁。比如两个线程分别需要锁A和锁B如果都按先A后B的顺序获取就不会形成循环等待。我给出一个死锁的简化示例笔试时能看代码判断死锁也是基本要求class Account { private int balance; // 转账方法可能产生死锁 void transfer(Account target, int amount) { synchronized (this) { synchronized (target) { // 转账逻辑 } } } }线程1执行a.transfer(b, 100)线程2执行b.transfer(a, 100)就可能出现线程1持有a的锁等待b的锁线程2持有b的锁等待a的锁形成死锁。6. 手写算法题冒泡、快排与字符串处理爱奇艺A卷最后的算法题不算特别难但非常基础而且基本都是LeetCode easy到medium难度级别的题。热搜词里“冒泡排序java”和“快速排序java实现”出现频率很高说明排序算法是很多人复习的重点。我建议备考时把基础排序都手写一遍不要只看不写因为笔试的时候是白纸写代码手生的很容易写出边界错误。6.1 冒泡排序的优化版本冒泡排序是排序算法里的入门题但笔试题会要求写出优化版本。经典写法public static 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 temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { break; // 本轮没有发生交换说明已有序 } } }优化点在于用一个swapped标志位记录本轮是否发生了交换。如果某一轮没有交换说明数组已经有序直接终止排序。最优情况下数组本身有序时间复杂度从O(n^2)降为O(n)。6.2 快速排序的实现与边界控制快速排序是笔试最爱考的手写题之一因为代码短、逻辑清晰又能考察对递归和分治的理解。我写一个不容易出错的版本public static 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 static int partition(int[] arr, int left, int right) { // 取最左边元素作为基准值 int pivotValue arr[left]; int i left; int j right; while (i j) { // 从右往左找小于基准值的元素 while (i j arr[j] pivotValue) { j--; } // 从左往右找大于基准值的元素 while (i j arr[i] pivotValue) { i; } if (i j) { swap(arr, i, j); } } // 基准值归位 arr[left] arr[i]; arr[i] pivotValue; return i; }这个版本的partition逻辑是经典的“挖坑填数”法边界条件都在i j里做了限制不容易越界。笔试里只要注意while循环里必须判断i j否则数组越界是必然的。6.3 高频字符串题反转、去重与最长无重复子串字符串处理是算法题的另一大出题方向。爱奇艺A卷的算法题里我记得有一道和字符串相关的具体题目记不太清了但字符串反转和最长无重复子串这种题型基本年年考。字符串反转的简单写法String s hello; String reversed new StringBuilder(s).reverse().toString();但笔试如果只要求写思路最好用双指针手写一下public static String reverse(String s) { char[] chars s.toCharArray(); int left 0, right chars.length - 1; while (left right) { char temp chars[left]; chars[left] chars[right]; chars[right] temp; left; right--; } return new String(chars); }最长无重复子串是一道经典题用滑动窗口解决时间复杂度O(n)public static int lengthOfLongestSubstring(String s) { MapCharacter, Integer map new HashMap(); int left 0; int maxLen 0; for (int right 0; right s.length(); right) { char c s.charAt(right); if (map.containsKey(c) map.get(c) left) { left map.get(c) 1; } map.put(c, right); maxLen Math.max(maxLen, right - left 1); } return maxLen; }思路是右指针不断向右扩展遇到重复字符时把左指针移动到上次出现这个字符的位置的下一位同时更新最大长度。笔试时如果能写出这个解法并且解释清楚为什么左指针只往前不往后基本就没问题了。7. 考场复盘的几个真实教训最后聊聊笔试题之外的东西。我做完这套题复盘时发现自己丢分的地方主要集中在三个原因时间分配不合理、审题不仔细、代码规范性不足。7.1 时间分配算法题别死磕笔试题量一般不小爱奇艺A卷我记得选择题数量不少加上最后的算法题时间其实挺紧的。我的教训是不要在选择题上过度纠结一道题如果想了2分钟还没有头绪先标记起来跳过做后面的。尤其是读代码判断输出的题这种题最耗时间而且错了也不影响后面。算法题要留足至少30分钟因为手写代码需要思考边界条件还要检查语法错误。如果最后实在没时间写出核心思路和伪代码也能得部分分比空着强。7.2 读题陷阱把“不正确”圈出来这是我错得最冤的地方。很多选择题问的是“下列说法不正确的是”我一眼扫过去看到A选项是对的直接选了A结果题目问的是“不正确”。这类陷阱在每一套笔试题里几乎都有而且是故意设置的。我的经验是读题时先把题干中的“不正确”“错误”“不包括”这些否定词圈出来再看选项。做选择题时凡是遇到拿不准的选项先排除明显错误的剩下的再仔细对比。7.3 写代码的规范性能加分手写算法题时阅卷老师看的不只是答案对不对还看代码风格。变量命名要有意义i、j、temp这种在循环里没问题但业务变量最好用能表达用途的名字。缩进要统一括号要对齐这些细节在笔试里虽然不影响编译运行但会影响整体印象分。我还有一个习惯写完代码后在旁边用一小段注释说明时间复杂度比如// O(n log n)这样可以展示你考虑到了算法的效率。爱奇艺这套2019秋招Java笔试题其实放到现在依然有很强的参考价值。Java基础的知识点就那么多每年的题换个花样出但核心原理是不变的。备考的同学与其刷大量偏题怪题不如把这几个高频模块吃透基础语法、集合原理、JVM内存、并发机制、手写算法。每个模块都做到能给别人讲明白的程度笔试自然就稳了。