2019年秋招那会儿,爱奇艺的Java方向笔试题(B)在求职圈子里流传度相当高。很多人拿它当模拟卷反复刷,因为它不仅是视频平台对Java工程师基础能力的一次全面检验,更代表了大厂笔试“考基础、考细节、考代码功力”的典型风格。我当时也认真复盘过这套题,后来带新人准备校招,还是会把它里面的考点单独拎出来讲一遍。
这篇内容,就是围绕这套题的完整复盘,把高频考点、常见陷阱、手写代码的答题思路都过一遍。适合正在准备校招、跳槽的Java工程师,也适合想系统梳理一遍Java核心知识的人。不管你现在处于哪个阶段,把一套经典笔试题吃透,比走马观花刷十套题有用得多。
1. 先搞清楚出题人到底想考什么
1.1 一套笔试题背后的命题逻辑
爱奇艺这类视频平台,后端技术栈以Java为主,核心业务覆盖内容管理、播放调度、会员体系、弹幕评论、推荐系统等。这些业务有个共同特点:高并发、高可用、大量IO操作。面试官坐在屏幕对面,最想知道的是:这个候选人有没有扎实的Java功底,能不能在入职后快速接手业务代码。
所以笔试几乎不会考具体的业务功能,而是考“基础能力”。题量通常在60到90分钟,题目的分布大致分三类:概念选择题、代码阅读题、手写代码题。概念题考察记忆和理解,代码阅读题考察对源码和执行流程的熟悉度,手写代码题考察工程编码能力和边界处理意识。这套题里,选择题和代码阅读题占比不低,手写题集中在排序和数据结构上。
有人会问,2019年的题放到现在还有参考价值吗?有。虽然Java版本从8升到了17甚至21,但核心基础几乎没有变过。HashMap的扩容机制、JVM内存模型、synchronized和volatile的区别、快速排序怎么写,这些依然是面试必考的内容。把一套经典题吃透,相当于把Java面试最底层的知识体系过了一遍。
1.2 知识点分布与分值倾向
根据这套题的复盘,出题范围其实相对固定。我整理成一张表,方便复习时对照。
| 模块 | 典型考点 | 考察意图 |
|---|---|---|
| Java基础 | String、包装类、运算符、异常 | 语言功底是否扎实 |
| 面向对象 | 重载重写、多态、抽象类与接口 | 设计能力与抽象思维 |
| 集合框架 | HashMap、ArrayList、迭代器 | 对常用数据结构的源码理解 |
| JVM | 内存区域、GC、类加载 | 对运行时的理解深度 |
| 并发编程 | volatile、synchronized、线程池 | 高并发场景下的基本素养 |
| 算法与数据结构 | 冒泡、快排、二分查找、数组操作 | 编码基本功 |
分值和难度上,Java基础类题目最多,属于“必须拿分”的部分;集合框架和JVM属于“拉开差距”的部分;并发编程和手写算法属于“高分项”。如果目标是笔试通过,优先保证前两块不出错,后面两块能写多少是多少,这样的策略最稳妥。
2. Java基础与面向对象:送分题里藏着不少坑
2.1 字符串与包装类型:String、Integer的经典陷阱
Java基础类题目看似简单,其实坑最多。String和Integer是高频主角。
String面试题多数围绕“不可变性”和“常量池”展开。String是final修饰的,底层字符数组也是final的,所以任何修改操作都会生成新对象,而不是改动原对象。这个特性带来了线程安全性和字符串常量池的复用机制。笔试题里常考的是这段代码的输出:
String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); System.out.println(s1 == s2); // true,指向常量池同一个对象 System.out.println(s1 == s3); // false,s3指向堆上新对象 System.out.println(s1.equals(s3)); // true,比较的是值s1和s2都从常量池取,所以引用相同;new String一定在堆上创建新对象,即使常量池里已经有"abc"。如果笔试里再延伸问StringBuilder和StringBuffer的区别,回答要点是:StringBuffer线程安全、方法加了synchronized,StringBuilder线程不安全但性能更高,单线程场景优先用StringBuilder。
Integer的经典考法是缓存机制。直接看这段代码:
Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println(a == b); // true System.out.println(c == d); // false原因在于Integer在-128到127范围内有缓存,装箱时直接返回缓存对象。超过这个范围就new新对象,==比较的是引用地址,所以结果为false。这个知识点年年考,我每次给新人讲都会多说一句:比较包装类型是否相等,一律用equals,不要用==,除非你非常明确自己在比较引用。
2.2 重载、重写与多态:面向对象考点的主要形态
面向对象三大特性是笔试必考,但考法很细。最常见的是区分重载和重写:
- 重载发生在同一个类中,方法名相同,参数列表不同,编译期就确定调用哪个方法,属于编译期多态。
- 重写发生在子类与父类之间,方法签名相同,运行期由实际对象类型决定调用哪个版本,属于运行期多态。
笔试题里常出的变体包括:构造器能不能重写、静态方法能不能重写、重写方法的访问修饰符能不能更小。答案是:构造器不能被重写;静态方法可以被子类继承调用,但不存在真正的重写,因为它是编译期绑定;重写方法的访问修饰符不能比父类更小,否则直接编译报错。
还有一道很典型的多态题,结合了继承和初始化顺序:
class Parent { static { System.out.print("1 "); } { System.out.print("3 "); } public Parent() { System.out.print("4 "); } } class Child extends Parent { static { System.out.print("2 "); } { System.out.print("5 "); } public Child() { System.out.print("6 "); } } // new Child() 输出什么? // 1 2 3 4 5 6输出顺序是:父类静态块、子类静态块、父类实例块、父类构造器、子类实例块、子类构造器。这个考点考察的是对类加载和对象创建过程的理解,笔试中出现频率很高。
抽象类与接口的区别也是高频题。Java 8之后接口允许默认方法和静态方法,两者的界限开始模糊。我习惯这样记:抽象类描述“是什么”,接口描述“能干什么”。一个类只能继承一个抽象类,但可以实现多个接口。如果题目问“什么场景选抽象类,什么场景选接口”,回答要点是:抽象类适合抽取公共状态和公共方法,接口适合定义能力契约。
2.3 hashCode与equals的“连体”关系
hashCode和equals是Java基础里最容易被低估的一对组合。如果你重写了equals却不重写hashCode,后果会很隐蔽。
举个例子。一个User类,只重写equals比较id,没有重写hashCode。往HashSet里放两个id相同的User对象时,HashSet先调用hashCode计算存储位置,两个对象hashCode不同,被放到不同桶,导致两个“相同”对象同时存在集合里,这在逻辑上就错了。
所以规则只有一条:equals相等的两个对象,hashCode必须相等;hashCode相等,equals不一定相等。HashMap查找元素时,先用hashCode定位到桶,再用equals比较链表中具体元素,两个方法协作完成查找。这也是为什么有人在HashMap源码题里被问“为什么重写equals一定要重写hashCode”——因为不重写就破坏了这个协作契约。
3. 集合框架:HashMap几乎是必考核心
3.1 HashMap的底层结构与扩容机制
集合框架里如果只押一道题,我肯定押HashMap。这套笔试题里,HashMap相关的题目出现频率非常高,考察点集中在底层结构、put流程、扩容机制。
JDK 1.8之后HashMap的底层结构是“数组+链表+红黑树”。数组的每个槽位叫bucket,存入元素时先对key的hashCode做扰动运算,再通过(n - 1) & hash计算槽位。hash冲突时用链表或红黑树存储。链表长度超过8且数组长度大于等于64时,链表转红黑树,把查找效率从O(n)降到O(logn)。
负载因子默认0.75,意思是元素个数达到数组长度的75%时触发扩容,容量变为原来的两倍。为什么是0.75?这是时间与空间的平衡。负载因子太小,浪费空间;太大,冲突概率增加,链表变长,查询效率下降。0.75是官方经过多次测试的折中值。扩容时元素会重新计算槽位,这也是HashMap在并发场景下可能丢数据的原因。所以笔试里如果问“HashMap线程安全吗”,标准答案是线程不安全,并发场景用ConcurrentHashMap。
手写HashMap的put流程简述时,建议按这个顺序说:计算key的哈希值,定位桶;桶为空直接放入;桶不为空,判断首节点是否key相同,相同则覆盖;否则遍历链表或红黑树,找到相同key则覆盖,找不到则插入;插入后判断size是否超过阈值,超过则扩容。这个流程能一口气讲清楚,说明你对源码是真的理解。
3.2 ArrayList与LinkedList的选型问题
ArrayList和LinkedList的对比题目,表面问区别,实际考的是数据结构复杂度。
ArrayList底层是动态数组,随机访问是O(1),中间位置插入删除是O(n),因为要移动元素。LinkedList底层是双向链表,随机访问是O(n),头尾插入删除是O(1)。很多初学者会在“频繁插入删除”场景无脑选LinkedList,但实际开发中这是一个误区。
ArrayList的插入慢在于元素搬移,但内存连续、CPU缓存友好;LinkedList每个节点需要额外存储前后指针,内存开销大,且链表节点在内存中不一定连续,遍历效率并不一定比ArrayList高。所以多数业务场景下ArrayList依然是首选,除非你非常明确只在头部做大量插入。
关于ArrayList还有一个细节值得注意:默认容量是10,扩容时newCapacity = oldCapacity + (oldCapacity >> 1),也就是1.5倍。知道这个公式在笔试里能直接得分。如果题目再深挖一点,问“如何预估ArrayList容量避免频繁扩容”,可以回答说在构造时传入初始大小new ArrayList<>(1000),减少扩容次数带来的性能损耗。
3.3 集合遍历时如何安全删除元素
有一道很经典的题目:给一个ArrayList,边遍历边删除元素,问会不会抛异常,为什么。
List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); for (String s : list) { if ("b".equals(s)) { list.remove(s); } }运行后会抛ConcurrentModificationException。原因是迭代器内部维护一个modCount字段,每次对集合结构性修改(add、remove)都会让modCount自增。迭代器在next时校验modCount是否发生变化,变化了就抛异常。for-each本质是迭代器遍历,所以在循环里直接remove会触发校验失败。
安全删除有几种常见方式:
- 使用迭代器自身的
iterator.remove(),它会同步修改expectedModCount。 - 倒序遍历,用
list.remove(index)删除,避免索引错乱。 - 使用Java 8的
removeIf,一行代码搞定。 - 收集要删除的元素,最后统一removeAll。
removeIf是我在日常代码里用得最多的方式,简洁且安全。笔试里如果能解释清楚modCount机制,比只写对代码更能加分。
3.4 Comparable、Comparator与lambda排序
排序考点里,笔试经常要求对对象数组或集合排序。Comparable是自然排序,定义在类内部,实现compareTo方法。Comparator是外部比较器,可以定义多种排序规则,不需要修改原类。
从Java 8开始,Comparator配合lambda表达式让写法变得非常简洁。比如对一个User列表按年龄排序:
List<User> users = getUsers(); users.sort(Comparator.comparingInt(User::getAge));按年龄降序:
users.sort(Comparator.comparingInt(User::getAge).reversed());先按年龄降序,再按姓名升序:
users.sort(Comparator.comparingInt(User::getAge) .reversed() .thenComparing(User::getName));这类代码在笔试里出现时,考察点不单纯是API记忆,而是你是否理解排序的稳定性和链式调用逻辑。我的建议是:Comparable和Comparator的区别要说清楚“自然排序”和“定制排序”两个词,lambda语法要能现场手写。如果笔试要求不用lambda,用匿名内部类也要能写出来。
4. JVM与并发:拉开差距的硬核题
4.1 内存区域与OOM:看到报错要能定位
JVM相关的题,这套里考过内存区域划分和OutOfMemoryError的场景判断。如果你在简历上写“熟悉JVM”,这几乎是必问项。
Java运行时数据区分为线程私有的虚拟机栈、本地方法栈、程序计数器,以及线程共享的堆、方法区。JDK 8以后,方法区被元空间取代,元空间使用本地内存,不再受堆大小限制。堆内存是对象分配的主要区域,又分为新生代和老年代,新生代里再有Eden区和两个Survivor区。
OutOfMemoryError是笔试喜欢结合代码问的题目。new大量对象导致堆内存不足,报java.lang.OutOfMemoryError: Java heap space;创建线程过多导致无法创建本地线程,报unable to create new native thread;Java 8之前还有永久代溢出,现在的元空间默认也有上限。排查思路一般是:先看堆栈,判断是堆溢出还是栈溢出,再决定是调大内存还是优化代码。
来看一个简单的JVM参数配置示例:
java -Xms512m -Xmx2g -XX:+UseG1GC -jar app.jar-Xms是初始堆大小,-Xmx是最大堆大小,-XX:+UseG1GC指定使用G1垃圾回收器。这里有两个容易踩的坑:第一,-Xmx必须不小于-Xms,否则JVM启动直接报错;第二,生产环境建议把两者设为相等,避免运行期动态扩容引入性能抖动。还有一个常见报错是“源发行版17需要目标发行版17”,这属于编译版本配置问题,不是JVM运行时问题,但面试时经常被混在一起问,需要区分开。
4.2 垃圾回收与常用收集器
GC相关题目主要考三点:怎么判断对象可以回收、有哪些收集算法、常见收集器的区别。
判定对象是否为垃圾,主流方案是可达性分析。从GC Roots出发,沿着引用链遍历,不可达的对象被标记为可回收。GC Roots包括虚拟机栈中引用的对象、静态变量引用的对象、本地方法栈引用的对象等。引用计数法因为循环引用问题,在主流JVM中已经不用了。
收集算法方面,新生代用复制算法,因为新生代对象大多朝生夕死,复制成本低;老年代用标记-清除或标记-整理。CMS是并发标记清除收集器,以低停顿为目标,缺点是会产生内存碎片;G1是分区的垃圾回收器,把堆分成多个Region,能够做到可预测的停顿时间。现在的JDK 17里G1是默认收集器,ZGC的停顿时间更是低到了毫秒级。
笔试里如果问到“什么场景触发Full GC”,标准答案是老年代空间不足、元空间不足、调用System.gc等。排查Full GC的思路是观察GC日志,看每次Full GC回收了多少内存。如果回收很少但频繁发生,大概率是内存泄漏或对象被长期持有。这类题需要结合实际场景回答,能举出“大对象直接进入老年代”、“内存泄漏导致频繁Full GC”这些例子会更专业。
4.3 volatile与synchronized:并发编程的一对关键概念
并发编程的题几乎是秋招笔试的标配。volatile和synchronized的区别是一道必背题,但我更建议你理解它背后的“看得到”和“碰得到”。
volatile解决的是可见性和有序性问题。它保证一个线程修改了变量,其他线程能立刻看到变化,且禁止指令重排序。但它不保证原子性。经典例子:
volatile int count = 0; // 线程A count++; // 线程B count++;count++看似一步,实际上是“读取-计算-写入”三步,volatile只能保证每次读取都是最新值,但读取和写入之间可能被其他线程插队,所以最终结果可能小于2。需要原子性时,改用AtomicInteger或加锁。
synchronized是重量级锁,但经过锁升级优化后,并没有大家想象的那么重。synchronized可以修饰方法,也可以修饰代码块,作用是保证同一时刻只有一个线程执行临界区代码,同时具备可见性。它的底层依赖对象监视器monitor,JDK 1.6之后引入了偏向锁、轻量级锁、重量级锁的升级过程。
这张对比表可以直接背:
| 对比项 | volatile | synchronized |
|---|---|---|
| 可见性 | 保证 | 保证 |
| 原子性 | 不保证 | 保证 |
| 有序性 | 禁止指令重排 | 保证 |
| 能否修饰方法 | 只能修饰变量 | 可以 |
| 性能 | 轻量 | 相对较重(有优化) |
4.4 线程池参数与拒绝策略
线程池是并发题目的另一个重点。Java提供的ThreadPoolExecutor有七个构造参数,笔试高频考的是核心线程数、最大线程数、阻塞队列和拒绝策略。
参数含义:
- corePoolSize:核心线程数,即使空闲也会保留。
- maximumPoolSize:最大线程数。
- workQueue:任务队列,线程都在忙时新任务进入队列等待。
- keepAliveTime:非核心线程空闲存活时间。
- threadFactory:线程工厂。
- handler:拒绝策略。
任务提交流程是:先判断当前线程数是否小于核心线程数,小于则新创建线程;否则尝试放入队列;队列满了再判断是否小于最大线程数,小于则创建新线程;还是不满足,走拒绝策略。
四种拒绝策略要记住:
- AbortPolicy:默认,直接抛RejectedExecutionException。
- CallerRunsPolicy:由提交任务的线程自己执行任务。
- DiscardPolicy:直接丢弃任务,不抛异常。
- DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新提交当前任务。
线程数怎么设置?分场景。CPU密集型任务建议设置为CPU核数 + 1;IO密集型任务,线程可以多一些,常用公式是CPU核数 * (1 + 平均等待时间 / 平均计算时间)。笔试里能说出来这个公式,说明你有实际思考。
另外一句老生常谈:阿里规约里建议不要用Executors的快捷方法创建线程池,因为FixedThreadPool和SingleThreadPool的队列是Integer.MAX_VALUE,可能堆积大量请求导致OOM;CachedThreadPool最大线程数是MAX_VALUE,可能创建大量线程导致资源耗尽。建议手动new ThreadPoolExecutor,把参数掌握在自己手里。
5. 手写算法与代码规范:这类题怎么拿分
5.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; } } }冒泡的优化点在于用一个swapped标志位判断本轮是否发生交换,如果没交换说明数组已经有序,提前结束。时间复杂度最好O(n),最坏O(n²),平均O(n²)。笔试时写冒泡,最容易被忽略的就是内层循环的- i,少了它虽然结果可能对,但多做了很多无效比较。
快速排序考得更多,因为它涉及递归和分区思想:
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 pivot = arr[left]; int i = left, j = right; while (i < j) { while (i < j && arr[j] >= pivot) { j--; } arr[i] = arr[j]; while (i < j && arr[i] <= pivot) { i++; } arr[j] = arr[i]; } arr[i] = pivot; return i; }这里用的是“挖坑法”,把基准值先存起来留一个坑,然后从右往左找比基准小的填坑,从左往右找比基准大的填坑,最后把基准放回i的位置。快排的平均时间复杂度是O(nlogn),最坏O(n²),比如数组本身有序且每次选第一个元素作基准时。笔试手写时,只要分区逻辑清晰、递归边界正确,就能拿到大部分分数。
排序算法还有个知识点容易被忽略:稳定性。冒泡、插入排序是稳定的,快排、堆排序是不稳定的。如果题目问“什么场景需要用稳定排序”,可以回答:按多个字段依次排序时,稳定排序能保留前一次排序的相对顺序,比如Excel里先按日期排序,再按优先级排序。
5.2 数组越界与边界条件:一场隐藏的“扣分战”
数组越界异常ArrayIndexOutOfBoundsException是笔试代码题最常见的报错。很多候选人思路对了,结果因为边界条件处理不当,运行直接崩了。
典型场景是二分查找。先看一个容易写错的版本:
// 错误写法:可能溢出 int mid = (left + right) / 2;当left和right都很大时,left + right可能溢出,结果mid变成负数,再访问arr[mid]就数组越界。正确写法:
int mid = left + (right - left) / 2;二分查找的循环条件是left <= right,移动边界时用mid - 1和mid + 1,避免死循环。笔试里写代码,边界条件是第一扣分点,第二是空数组、单元素数组、重复元素数组没有测试意识。我一般建议大家写完代码后立刻在脑子里跑三个用例:空数组、长度1的数组、最大长度数组。
还有一个常见的数组比较问题:不能用arr1 == arr2比较两个数组内容是否相等,因为==比较的是引用地址。比较内容要用Arrays.equals(arr1, arr2)。这个知识点虽然基础,但在笔试题里经常被用来设置陷阱。
5.3 从笔试题到面试:如何在一道题里展示工程素养
笔试不是只改判对错,很多公司会有人工复查。同样的代码,评分差距可能来自细节。
命名习惯很重要。临时变量用i、j可以,但算法核心变量最好能表达语义,比如pivot、left、right比a、b、c好得多。防御式编程也加分,入口处判空、判断长度小于2直接返回,这些看起来多余,实际是工程素养的体现。写完之后标注时间复杂度和空间复杂度,也是很多高分答卷的共性。
代码格式上,缩进统一、花括号换行风格一致、没有多余空行,这类细节会被阅卷人看在眼里。笔试环境下时间紧张,不一定能写得完美,但至少保持清晰易读。我见过很多候选人,思路完全正确,但代码乱得没法看,白白丢分。
另外一个容易被忽视的点是:如果题目要求输出结果而不是写代码,那么一定要把输入输出格式看清楚。曾经有人快排写对了,结果main函数读取数据的方式不对,整个程序跑不起来,这种失误是最可惜的。
6. 备考与实战:我复盘这套题的一些心得
6.1 复习优先级与时间分配建议
如果你准备时间有限,按这个顺序复习性价比最高:
- Java基础与面向对象,包括String、Integer、hashCode/equals、抽象类与接口,这部分拿分最稳,2到3天可以过完。
- 集合框架,重点HashMap、ArrayList、迭代器,配合源码阅读,3到4天。
- JVM基础,内存区域、垃圾回收、类加载,这部分广度优先,4到5天。
- 并发编程,volatile、synchronized、线程池,2到3天。
- 手写算法,每天固定练3道题,优先排序、二分、链表反转。
这个顺序的逻辑是先易后难,先把基础分拿到手,再攻硬骨头。这套题整体难度中上,如果前面五个模块都扎实,通过笔试的概率很大。
刷题的时候不要只背答案,要把每道题的考点扩展成一篇笔记。比如遇到HashMap的题,可以顺带把ConcurrentHashMap、HashTable、LinkedHashMap的对比都整理一遍。知识是网状的,单点记忆很容易忘,连成网才牢固。
6.2 这套题之外,你还需要准备什么
笔试只是第一关,通过之后还有面试。笔试里考到的知识点,面试会问得更深。比如HashMap会延伸到ConcurrentHashMap的实现原理,JVM会延伸到线上OOM怎么排查,线程池会问你实际项目里怎么设置参数。
除此之外,数据库、Spring全家桶、Redis、消息队列也是面试高频区。2019年那会儿Spring Boot和微服务已经开始流行,现在更是人人必谈。如果你只刷Java基础题,过了笔试也容易挂在技术面。
我建议翻一翻最新的JDK版本特性。这几年JDK 8的历史地位还在,但17和21已经普及,新版本里的record、switch表达式、虚拟线程等特性,正逐渐成为面试话题。学有余力时,把JDK 8到21的关键特性过一遍,会有惊喜。这里有个经验:在简历上写“熟悉Java 17新特性”,面试官很可能会顺着问虚拟线程和ZGC,准备充分的话是很好的加分项。
6.3 常见错误清单:考前看一眼能少踩一半坑
我把平时带人刷题时遇到的常见错误整理成一张速查表,考前十分钟翻一遍很有用。
| 错误类型 | 具体表现 | 正确做法 |
|---|---|---|
| 包装类型用== | Integer超127比较结果出错 | 用equals |
| 遍历时直接remove | 抛ConcurrentModificationException | 用iterator.remove或removeIf |
| HashMap并发使用 | 多线程put丢数据 | 用ConcurrentHashMap |
| volatile用于计数 | count++结果不准 | 用AtomicInteger或加锁 |
| 二分查找mid溢出 | left+right溢出导致越界 | 用left + (right - left) / 2 |
| 线程池用Executors | 队列或线程数无上限 | 手动new ThreadPoolExecutor |
| 快排递归无边界 | 栈溢出 | 注意left >= right时return |
| 数组比较用== | 比较的是引用 | Arrays.equals(arr1, arr2) |
这张表里的坑,我在面试别人时见过无数次。别小看这些细节,笔试的分数往往就是这些细节堆积出来的。
我个人刷题的感觉是,这套题放到今天依然没有过时。Java面试的底层逻辑始终没变——基础要牢,原理要懂,代码要能写。当时我把每道题的知识点扩展成笔记,后来面试时遇到类似问题都能从容应对。如果你正在准备这类考试,不妨把这道题当成一面镜子,看看自己在哪块最薄弱,集中火力补上。
最后再分享一个小方法:刷完题别急着看答案,先独立把思路写下来,哪怕写错了也比直接背答案有用得多。这个习惯让我受益至今,写代码、排查问题、甚至带新人都靠它。笔试没有什么捷径,把一道经典题吃透,比走马观花刷十套题更有价值。