先说结论:有赞2019校招Java笔试(B卷),我要把它写成一个回忆向深水帖的形态——以一个拿到过B卷、经历过完整投递流程的校招Java开发者的视角,把B卷考察的模块、知识点、典型题目类型以及临场应对策略串起来讲清楚。
这篇内容适合两类人看:一类是正在准备校招Java岗笔试的同学,另一类是已经工作一两年、想回顾基础是否扎实的开发者。校招笔试和社招面试的最大区别在于,笔试考察的是“广度+熟练度”,你很难靠一两道深题翻盘,更多的是看你在有限时间内能不能稳定输出基础知识的准确率。有赞的B卷在这一点上非常典型,不偏不怪,但覆盖面很广,含金量不低。
1. 笔试概况与考察方向拆解
1.1 有赞校招笔试的整体风格
有赞的校招笔试在互联网公司里属于中等偏上难度,题目质量一直不错。它的B卷和A卷相比,题量相近,但考察侧重略有不同——B卷更偏向Java基础、集合框架、并发编程和简单的算法逻辑,而不是把重心全压在复杂算法和系统设计上。
整份试卷大致由三部分组成:单选题、多选题、编程题。选择题覆盖Java核心知识点,编程题一般是一道或两道在线OJ题目,限时30到60分钟不等。
很多同学第一反应是先刷算法题,但实际参加过B卷之后会发现,选择题才是拉开差距的关键。有赞的单选题不是简单的“八股文”背诵,它会把多个知识点糅合在一起,考察你是否真正理解底层原理。比如说,给一段代码让你判断输出结果,这类题考察的就是你对JVM内存模型、字符串常量池、自动装箱拆箱等细节的熟悉程度。
1.2 B卷的主要考察模块占比
根据我掌握的校招笔试信息,以及大量同期考生反馈,有赞B卷的考点分布大致可以总结为下表:
| 考察模块 | 大致占比 | 典型题型 |
|---|---|---|
| Java核心基础 | 25% | 语法细节、集合类、String/包装类 |
| JVM与内存模型 | 15% | 运行时数据区、GC策略、类加载 |
| 并发与多线程 | 15% | synchronized、volatile、线程池 |
| 框架与工具(Spring等) | 10% | 注解、Bean生命周期、常用设计模式 |
| 数据库与SQL | 10% | 索引、事务隔离级别、SQL写法 |
| 数据结构与算法 | 15% | 排序、链表操作、字符串处理 |
| 计算机网络与操作系统 | 10% | TCP/IP、HTTP、进程与线程 |
这个比例不一定完全精确,但大体代表了B卷的命题倾向。Java基础和并发是重头戏,这两块如果平时基础扎实,笔试就已经稳了一半。
2. Java核心基础:不容小觑的选择题重灾区
2.1 集合框架与HashMap的底层原理
有赞B卷几乎每年都会出现HashMap相关题目,而且考察角度在逐渐加深。19年的B卷尤其明显,已经不满足于“HashMap是不是线程安全的”这种送分题了,而是会问到扩容机制、红黑树转换条件、并发场景下的问题等。
我印象很深的一道题是:给定HashMap初始容量和加载因子,问实际触发扩容的元素数量是多少。如果你只记得默认容量16、加载因子0.75,可能会直接写12。但题目如果考察构造函数的细节,就要注意initialCapacity会被HashMap内部处理成2的幂次方,也就是JDK源码中的tableSizeFor方法。假设传入初始容量为10,实际容量会被调整为16,那真正触发扩容的数量就和直接传10不一样了。
这类题目给出的教训是:刷基础不能只看面试题总结,得真正打开源码看一遍关键方法的实现逻辑。建议重点关注:
putVal方法的完整执行流程,包括hash扰动、索引计算、链表转红黑树的阈值(8和6)resize方法中新旧数组迁移的逻辑,特别是不用rehash而是通过高位判断来分流的做法get方法的查找逻辑,以及在链表长度较长时的效率问题
我的实操建议:备考阶段自己动手画一遍HashMap的put流程,把每个分支条件标清楚。手画和看源码是两个概念,画过一遍之后,考试遇到换皮版本都能秒解。
2.2 String、包装类与自动装箱的细节
B卷的选择题特别喜欢出“输出结果是什么”这一类。表面上看是考代码输出,实际上考的是底层对象创建机制。
最常见的陷阱就是字符串拼接和intern方法。比如String s1 = new String("abc")创建了几个对象,s1.intern()做了什么,以及不同字符串常量池版本(JDK 6和JDK 7/8)的行为差异。这些知识点在笔试中反复出现,很多人挂在细节上。
再比如自动装箱的缓存机制。Integer a = 127; Integer b = 127;比较返回true,但换成128就返回false,原因就是IntegerCache缓存的默认范围是-128到127。有赞B卷如果考到这个点,通常还会叠加一层:通过new Integer(127)创建的对象走的是新对象逻辑,不参与缓存。这就在考察你是否真正理解了“自动装箱调用了valueOf方法,而valueOf才有缓存逻辑”这个本质。
还有一道经典变体是混合运算时的类型提升问题:
short s1 = 1; s1 = s1 + 1; // 编译报错,因为1默认是int,s1 + 1结果是int s1 += 1; // 没问题,复合赋值运算符自带强转这类题目本身不难,但考场上容易因为紧张而看错。我的心得是:遇到输出结果题,先判断考的是哪个知识点,再逐行推演,不要凭感觉选答案。很多时候一道题就那么两三行代码,但每个细节都是出题人故意埋的雷。
2.3 设计模式与常见框架原理
有赞B卷对设计模式的考察不会单独出题,但会糅合在Spring框架相关的题目里。比如问Spring中Bean的默认作用域是什么,这背后其实是在考察单例模式在框架中的实际应用。
还有一些题目会让你判断某段代码符合哪种设计模式,常见的有模板方法模式、策略模式、工厂模式、代理模式。Java开发日常写业务代码时,这些模式的使用频率很高,所以这类题其实在考察你的实际编码经验。
以代理模式为例,有赞笔试可能会这样出题:JDK动态代理和CGLIB代理有什么区别,分别适用于什么场景。回答要点集中在:
- JDK动态代理基于接口,通过
Proxy.newProxyInstance生成代理类,要求目标类必须实现接口 - CGLIB代理基于继承,通过生成目标类的子类来代理方法,因此不能用final修饰的类
- Spring中默认策略是目标类实现了接口就用JDK代理,没实现接口就用CGLIB代理
这里有个容易踩的坑:Spring Boot 2.x之后的版本,spring.aop.proxy-target-class默认是true,也就是说即使目标类实现了接口,默认也会使用CGLIB代理。这个细节在有赞笔试中如果出现,很多人会直接用“实现接口就JDK代理”的老经验去选,结果就错了。
3. 并发编程:B卷拉开差距的核心模块
3.1 synchronized锁升级流程与实现原理
有赞B卷的并发题不会太偏,但synchronized是必考的。19年B卷考察的是锁升级流程,也就是无锁→偏向锁→轻量级锁→重量级锁的演变过程,以及每个状态下锁对象头Mark Word的存储内容。
这类题目如果只是背结论,很容易在细节上失分。比如偏向锁延迟启动的问题——JVM启动后有几秒延迟,所有锁都直接进入轻量级锁状态,这个点很多面经里不会重点提。还有偏向锁撤销的触发条件、批量重偏向和批量撤销的逻辑,都是加分的深水区。
我的建议是,备考时把锁升级过程和时间线结合起来理解:
- 无锁状态:对象头Mark Word存储对象自身的哈希码、分代年龄
- 偏向锁:Mark Word存储持有偏向锁的线程ID
- 轻量级锁:Mark Word指向栈帧中锁记录的指针,通过CAS竞争
- 重量级锁:Mark Word指向监视器Monitor对象
笔试不会让你写锁升级的实现代码,但给你一段代码,问某时刻锁处于什么状态,或者在某个具体情境下锁能否降级,这些是常见的出题方向。要注意锁只能升级不能降级,这个结论要记住。
3.2 volatile的可见性与禁止重排序
volatile是B卷的另一个高频考点。它解决两个问题:多线程下的可见性和指令重排序。这道题几乎年年出现,但每年换一个包装方式。
比如可能出现这样的题目:多个线程同时读写某个int变量,如果这个变量没有加volatile,会出现什么问题。这时候你要答出:主内存与工作内存之间数据不同步,线程A修改的值,线程B可能读不到,加上volatile能保证修改立即写回主内存并让其他线程的缓存失效。
更进一步,有赞B卷会考到volatile为什么不能保证原子性,典型场景是count++操作。这里要说明volatile只能保证读写操作本身是原子的,但count++是“读-改-写”三个操作,不是原子的。并发环境下两个线程同时读到相同值,各自加1再写回,结果就丢失了一次更新。
这里我推荐一个记忆方法:volatile解决的是可见性和排序问题,解决不了复合操作的原子性问题;想保证原子性,得用synchronized、Lock或AtomicInteger。
3.3 线程池参数设置与拒绝策略
线程池是B卷的“压轴常客”,因为它不仅能考察基础参数,还能和业务场景结合出题。核心参数就是那七个:核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。
B卷会考到的变形题目包括:
- corePoolSize=2,maximumPoolSize=5,队列容量=10,此时有20个任务提交,最终会有多少个线程被创建?答案是5个,因为当任务数超过队列容量(10 + 2 = 12)时,才会触发非核心线程的创建。
- 拒绝策略的执行时机:只有当前线程数达到maximumPoolSize,并且队列已满,才会触发拒绝策略。
拒绝策略这块,四种内置策略要分清:AbortPolicy(直接抛异常)、CallerRunsPolicy(调用者线程执行)、DiscardPolicy(直接丢弃)、DiscardOldestPolicy(丢弃队列中最旧的任务)。有赞可能会给个场景,问选择哪种策略比较合适。比如不允许丢失任务,并且要感知到任务被拒绝,那就应该用CallerRunsPolicy或者自定义策略。
实操心得:线程池参数的设置,笔试中偶尔会让你根据业务场景“估算”参数值。比如一个接口的QPS是1000,每个任务耗时50ms,要求响应时间不超过200ms,让你算核心线程数。这种时候不要慌,用最简单的公式估算:线程数 = QPS × 单任务耗时。1000 × 0.05 = 50个线程。当然这只是理论值,实际还需要考虑CPU核数和IO等待时间,但笔试能写出这个估算逻辑就已经是加分项了。
4. 编程题实战:从读题到AC的完整过程
4.1 B卷编程题的高频题型
有赞B卷的编程题通常是1到2道,难度介于LeetCode中等偏简单。题型分布上,字符串处理、链表、排序与查找这三大类是出现频率最高的。
以我当年的经验来看,可能出现的编程题大致是:
- 字符串去重并保证顺序不变
- 排序算法的变体,比如按频率排序
- 链表反转或合并两个有序链表
- 二分查找或二分答案
结合当年全网考生回忆,B卷出现过一道“按照出现频率对数组元素排序”的变体题。核心思路并不复杂:先用HashMap统计每个元素出现的频率,再把元素按频率降序排列,如果频率相同则按元素值升序排列。
这道题考察的点有三个:HashMap的使用、自定义排序逻辑、以及对Java 8 Stream或Lambda表达式的熟练程度。
4.2 编程题的边界处理技巧
考过笔试的人都知道,编程题最怕的不是思路不对,而是边界条件没处理导致用例过不去。有赞B卷的判题系统用的是标准OJ,不会有任何提示,只会告诉你WA(Wrong Answer)或TLE(Time Limit Exceeded)。
我总结了几个边界条件必查点:
- 空数组和空字符串输入
- 数组长度只有1的情况
- 所有元素值相同的情况
- 元素全是负数或零的情况
- 整数溢出问题(LeetCode的经典坑,比如反转字符串时如果用了
Integer.parseInt,需要小心超出范围)
关于快速排序,网上都在竞速“快速排序Java实现”或者“冒泡排序Java”这种基础排序算法实现,B卷确实有可能让你手写排序,但更多时候是给你一个已经排序的思路,让你优化实现。我的经验是:笔试键盘写代码的时候,优先写不依赖复杂调用的方法,比如Arrays.sort()底层是DualPivotQuicksort,用它来处理基础排序完全够用。如果题目明确要求手写快排或归并,那么要注意原地排序的时间和空间复杂度。
4.3 在线OJ的输入输出处理
这是很多第一次参加校招笔试的同学最容易翻车的地方。有赞的笔试平台用的是牛客网或赛码网,输入输出格式和LeetCode完全不同。LeetCode是函数式提交,而牛客网这类平台是标准输入输出。
以“多行输入”为例,Java的标准写法是:
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNext()) { int n = in.nextInt(); // 处理逻辑 } } }很多人在本地IDE上跑通了,一提交到OJ就报错,原因多半是:
- 方法签名写成了
public static void main(String[] args)以外的形式 - 类名不叫
Main - 循环读取输入的方式不对,导致读不到全部数据
避坑指南:提前去牛客网刷几道“输入输出练习”题目,把Scanner和BufferedReader的两种读取方式都练熟。竞赛场景下BufferedReader性能更好,但笔试大部分题目用Scanner就够用了。
5. 数据库与场景设计题:校招笔试中的“送分与送命”并存项
5.1 SQL优化与索引失效场景
有赞的业务是零售SaaS,数据库相关内容在笔试里占比不低。B卷常见的数据库题型包括:给定一条SQL,问如何优化;给定一个查询场景,问应该建什么索引。
索引失效问题几乎是必考。最典型的就是对索引列使用了函数或隐式类型转换。比如索引建在phone列上,但查询条件是WHERE phone = 13800138000,其中phone是varchar类型,如果写成这样,MySQL会用隐式转换,导致索引失效。
另一个高频考点是联合索引的最左前缀原则。比如有一个联合索引(a, b, c),查询条件分别是:
WHERE a = 1 AND b = 2,索引生效WHERE b = 2 AND c = 3,索引失效WHERE a = 1 AND c = 3,索引部分生效,只用到了a列
这套逻辑一定要记熟,有赞B卷会把场景包装成具体业务,比如订单表、商品表、用户表,让你判断怎么写查询性能最高。
5.2 事务隔离级别与MVCC
数据库事务的隔离级别是笔试选择题的常客。四个级别:读未提交、读已提交、可重复读、串行化。有赞B卷比较爱考的是InnoDB默认隔离级别(可重复读)以及如何在可重复读级别下避免幻读。
这部分问到MVCC(多版本并发控制)时,需要讲清楚三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID,以及ReadView的生成机制。可重复读和读已提交的差别在于,前者在事务第一次执行SELECT时生成ReadView,之后一直复用;后者每次SELECT都会生成新的ReadView。
这里有个易混淆的点:可重复读级别下SELECT不会出现幻读,是因为MVCC的快照机制;但如果两个事务分别执行SELECT ... FOR UPDATE或UPDATE操作,走的则是当前读,直接操作最新版本,此时就可能出现幻读问题。解决方式是加间隙锁或临键锁。这个点如果B卷考到一个判断对错的选择题,很多人会在这里栽跟头。
5.3 场景设计题的回答框架
除了纯技术基础,有赞B卷里偶尔会出现一道简单的系统设计题,通常是“设计一个短链接系统”或“设计一个秒杀系统”或者“设计一个购物车”这类。
这类题不要求你写出完整代码,但会要求你给出核心思路和关键组件。这时候不要慌,按照固定的回答框架来组织答案:
- 需求分析:明确功能需求和非功能需求
- 核心实体设计:有哪些核心表和字段
- 核心流程描述:从请求到响应的完整链路
- 关键技术点:缓存、异步、限流、幂等
比如秒杀系统,核心是防止超卖。解决方案可以是:库存预热到Redis,扣减库存时用Lua脚本保证原子性,把秒杀请求异步写入消息队列,最终由消费者同步数据库。笔试答题时能把这个思路写完整,阅卷老师就已经能判断你具备基本的设计能力了。
6. 备考策略与实战建议:从刷题到踩坑的最后一段路
6.1 校招笔试的准备节奏
如果你准备参加来年的校招笔试,建议按以下节奏安排:
- 提前6个月:系统刷一遍Java核心基础、并发、JVM,看完源码和原理性文章
- 提前3个月:开始集中刷LeetCode热题和牛客网真题,每天2到3道,保持手感
- 提前1个月:针对目标公司(比如有赞)做真题模拟,限时训练,找考场感觉
- 考前1周:只看错题和总结笔记,不再接触新题
很多同学在“刷题量”上非常焦虑,一上来就给自己定下每天10道题的目标。实际执行下来,质量和总结的意义远大于数量。我刷题的时候习惯每道题记录一个模板:题目类型、解题思路、边界条件、代码模板。考试前翻一遍这些模板,比再刷50道题更有用。
6.2 考场时间分配与答题顺序
校招笔试的时间通常是一个半小时到两个小时。以有赞B卷为例,选择题大约20到30道,编程题一到两道。
我推荐的时间分配策略:
- 先花5分钟快速浏览全部题目
- 选择题控制在60到70分钟内完成,每道题不能超过3分钟
- 遇到不确定的题目先标记,不恋战
- 最后留30分钟给编程题
编程题的时间分配上是,先花5分钟读题和确定思路,再花15到20分钟写代码和调试,最后留5分钟检查边界条件。
这里有一个踩过坑的经验:编程题第一道往往不是最容易的。有些人习惯按顺序做题,结果卡在难题上白白浪费了大把时间。正确的做法是从简单的开始,快速拿分,再回头啃难题。
6.3 常见笔试环境问题速查
笔试踩坑不只是知识点问题,环境问题同样让人崩溃。根据我看到的多个小伙伴反馈的视频和截图,常见问题包括:
| 问题描述 | 解决方法 |
|---|---|
| 本地IDE能跑,OJ平台编译失败 | 检查类名是否叫Main,包名是否去掉,JDK版本是否一致 |
| 读不到输入数据 | 检查是否有多个测试用例,有没有用while循环读数据 |
| 中文乱码 | 提交前统一用UTF-8编码,代码里不要打中文注释 |
| 报错Java out of memory | 检查是否创建了超级大的数组,考虑用滚动数组或原地修改 |
| 时间超限TLE | 检查是否有不必要的循环嵌套,优化为O(n)或O(nlogn)的解法 |
有一点要特别提一下:如果你的代码里用到了Lombok的注解,而OJ平台的编译器不支持Lombok,会导致编译失败。虽然笔试的算法题一般不涉及Lombok,但如果考到写实体类的题,尽量别依赖Lombok的@Data注解,老老实实用手写getter/setter更稳妥。不要做出一个源代码提交后因为缺少注解处理器而编译挂掉的情况。
6.4 笔试后复盘的正确姿势
笔试结束不等于万事大吉。有赞的流程是笔试通过后进入面试环节,而面试中很大概率会追问笔试中的题目思路。所以笔试结束后第一时间复盘,趁记忆还热乎,把每道题的正确答案和原理搞清楚,这是很关键的一步。
复盘时不要只看对错,要问自己三个问题:
- 这道题考察的知识点是什么?
- 我有没有见过类似的题?当时是怎么解的?
- 如果今天重新做,能不能写出更优的解法?
这三个问题能帮你把一次笔试转化成相当于三次刷题的效果。我见过很多学员笔试成绩一般,但复盘非常认真,面试时被问到笔试题目反而能答得比笔试更好,最后顺利拿到offer。
我个人在实际操作中的体会是:校招笔试本质上是一场“抗压能力+知识储备+熟练度”的综合测试。知识储备靠平时的积累,熟练度靠刷题,抗压能力只能通过模拟真实考试环境来锻炼。把每一次笔试都当作最后一次机会来对待,提前模拟、提前适应、提前总结,真正的考场上就不会慌。
最后再分享一个小技巧:如果笔试平台支持提前登录,提前10分钟进去,调好IDE、确认好网络和键盘输入,给自己一个平稳的起手状态。你以为这是小事,但在实际笔试中,这10分钟往往能帮你减少大量不必要的紧张情绪。