这套卷子虽然叫“映客2020春招研发B卷”,但放在今天看,它依然是直播赛道后端、客户端岗位笔试的一份很典型的样例。直播业务的特点是高并发、低延迟、强互动,所以笔试题目会明显偏向Java基础、并发编程、网络原理、数据结构与算法,再加上一道系统设计题来考察你对直播场景的理解。很多同学刷题只看算法,忽略了基础题和设计题,结果反而在这类卷子上栽跟头。这篇文章我就结合这套B卷的常见题型,把每一类题背后的考察意图、解题思路和复习方法完整拆一遍,尤其适合正在准备直播、社交、音视频类公司研发岗笔试的同学。
1. 整套卷子想考察什么
1.1 从直播业务反推考察重点
我在复盘这套B卷之前,先做了一个反向推导:映客是直播平台,直播场景下研发团队每天要面对什么问题?答案无非是三个方向,海量用户同时在线、消息实时分发、极端流量下的稳定性。这三个方向直接决定了笔试题的选型。
- 海量用户在线,意味着集合类、内存模型、缓存设计要熟练;
- 消息实时分发,意味着网络协议、IO模型、并发编程要扎实;
- 极端流量下的稳定性,意味着算法复杂度、限流降级、系统设计要有全局观。
所以这套B卷里,Java集合、线程池、TCP/IP、算法题和一道直播场景设计题几乎是必出组合。这不是映客独有的偏好,而是整个直播行业研发笔试的共同倾向。搞清楚这一点,你就明白为什么复习不能只闷头刷LeetCode。
1.2 题型构成与时间分配
2020年春招的研发B卷,整体结构分为几个模块。客观题主要覆盖Java基础、操作系统、网络原理,题量大概在20到30道之间,每道题分值不大,但覆盖面广。编程题一般是两到三道,难度从LeetCode中等题到偏难的动态规划或设计类问题不等。最后还有一道系统设计题,主观性很强,考察的是你面对开放性问题时的分析框架。
时间分配上,我建议客观题控制在40分钟内,编程题每道题20分钟左右,系统设计题留出至少30分钟。很多同学在编程题上死磕一道题,导致最后设计题只写了两行字,这是笔试的大忌。设计题只要结构完整、逻辑自洽,即使方案不完美也能拿基础分,而编程题卡住了可以先跳,最后再回来补。
2. 客观题复盘:Java基础与数据结构高频点
2.1 HashMap为什么是必考题
B卷的客观题里,HashMap是出现频率最高的考点,没有之一。考察的角度通常有三个:底层数据结构、put流程、扩容机制。
底层数据结构,在JDK 1.8之后是数组加链表加红黑树。数组是主体,链表解决哈希冲突,当链表长度超过8且数组长度超过64时转为红黑树。这个阈值为什么是8?因为理想情况下随机哈希码导致容器中节点分布频率遵循泊松分布,链表长度达到8的概率已经极其低,所以从时间和空间权衡来看,8是一个合理的阈值。面试官问到这一点时,你能说出这个概率推理,就会比单纯背“8转红黑树”更有说服力。
put流程,要能完整描述出来,先计算key的hash值,通过扰动函数让高位也参与运算,然后通过(n - 1) & hash定位到数组下标,如果该位置为空直接放入,不为空则遍历链表或红黑树,判断key是否存在,存在则覆盖value,否则插入新节点。插入后检查size是否超过threshold,超过则扩容。
扩容机制是另一个高频点,默认容量16,负载因子0.75,扩容时容量翻倍,元素需要重新计算位置。JDK 1.8的扩容做了个优化:元素重新定位时,只需要看原hash值新增的bit位是0还是1,是0则位置不变,是1则原位置加旧容量。这个设计避免了rehash时重新计算hash,是JDK 1.8性能提升的一个细节。
你在复盘这道题时,一定要自己画一遍put和扩容的流程,能把流程图在纸上默写出来,才算真正掌握,而不是只在脑海里有个模糊印象。
2.2 线程安全的集合类要分清适用场景
B卷还喜欢考察线程安全的集合类,常见的有Hashtable、ConcurrentHashMap、Collections.synchronizedMap。这三者的区别是经典题目。
Hashtable是线程安全的,但它的实现方式是给整个数组加锁,也就是所有方法都用synchronized修饰,并发度极低,现在基本不使用了。
ConcurrentHashMap在JDK 1.8之后,放弃了分段锁的设计,改用CAS加synchronized锁住数组中的每个节点,锁粒度变得更细。读操作无锁,写操作只锁当前桶,因此并发度远高于Hashtable。这里有个容易忽略的点:ConcurrentHashMap的size()方法在并发场景下不是精确值,它先无锁统计,如果两次统计结果一致才返回,否则加锁重统计,因此它是弱一致性的。
Collections.synchronizedMap则是在普通Map外面包了一层同步锁,所有方法都被同一个对象锁保护,实现简单但并发性能一般。
复习这个知识点时,不要只背结论,要理解每种方案在并发度、一致性、实现复杂度三个维度上的取舍,这样即使题目换个问法,你也能应对。
2.3 字符串、数组与栈队列的基础题陷阱
客观题里还有一些送分题,但送分题也有陷阱。比如String、StringBuilder、StringBuffer三者的区别,很多同学只记得“String不可变,StringBuilder线程不安全,StringBuffer线程安全”,但忽略了底层实现。
String底层是final char数组(JDK 9之后是byte数组),每次拼接都会创建新对象,所以循环内大量拼接会频繁GC。StringBuilder的append方法是直接操作内部数组,性能好,但如果多线程同时调用,可能出现数组越界或数据错乱。StringBuffer则在append方法上加synchronized,线程安全但性能略低。
这种题目的陷阱在于,它喜欢让你判断一段代码创建了几个对象。比如String s = new String("abc"),问创建了几个对象?答案是1个或2个,字符串常量池中有"abc"则只创建1个堆对象,没有则先在常量池创建1个,再在堆中new出1个。这种细节题就是区分“背过”和“真懂”的分水岭。
栈和队列的基础题,往往结合括号匹配、表达式求值、用两个栈实现队列这类经典问题来考。这些题难度不大,但要注意代码的边界处理,比如栈溢出、空队列弹出等异常情况。
3. 并发与网络:直播场景的隐藏考点
3.1 线程池参数:会背八股更要会计算
线程池是B卷并发题的核心。考察点集中在ThreadPoolExecutor的七个参数,以及拒绝策略。
七个参数是核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。关键问题是:核心线程数和最大线程数怎么设置?这是很多同学的死穴。
实际生产环境的经验公式是:
- CPU密集型任务,线程数设置为CPU核心数加1,目的是减少上下文切换,让CPU尽量满负荷工作;
- IO密集型任务,线程数设置为CPU核心数乘以2,因为IO等待期间线程让出CPU,需要有更多线程来填补等待间隙。
直播场景里的消息推送、弹幕审核这类任务,大部分属于IO密集型,所以核心线程数往往会设置得比CPU核心数大。你可以记住一个经验值思路:核心线程数 = CPU核心数 / (1 - 阻塞系数),阻塞系数在IO密集型任务中通常取0.8到0.9。比如8核机器,阻塞系数取0.8,核心线程数就是40左右。
这块还有一个高频考察点是拒绝策略,有AbortPolicy直接抛异常、CallerRunsPolicy让调用者线程执行、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队列中最老的任务。你要能结合实际场景说明选择理由,比如直播弹幕场景,如果系统过载,弹幕丢几条其实影响不大,可以选择DiscardPolicy;而礼物赠送这种需要保证消息不丢的场景,必须选CallerRunsPolicy或者自己实现拒绝逻辑。
3.2 TCP四次挥手:结合弹幕系统来记
网络部分的经典考点是TCP三次握手和四次挥手,尤其喜欢考TIME_WAIT。为什么会有TIME_WAIT?因为主动关闭连接的一方,要确保对方收到了自己的ACK。如果这个ACK丢失,对方会重发FIN,主动关闭方必须能再次回应ACK,所以要等待2MSL。MSL是报文段最大生存时间,2MSL大约是2到4分钟。
B卷里这道题经常变着花样考,比如问你“大量TIME_WAIT连接是什么原因,怎么解决”。这个问题在直播场景里很真实,因为客户端频繁断开重连,服务端如果充当主动关闭方,就会出现大量TIME_WAIT连接,占用端口和内存。
解决思路有几个方向。一是打开tcp_tw_reuse和tcp_tw_recycle,让处于TIME_WAIT的连接可以被复用,但tcp_tw_recycle在NAT环境下有坑,已经不建议开启。二是调整keepalive时间,减少无效连接。三是在应用层做连接复用,比如HTTP长连接,减少频繁断开。
这类题目如果能结合线上真实场景来答,面试官会觉得你是有实战经验的,而不是只会背书。
3.3 IO模型与NIO:判断是否理解高并发
最后再说IO模型。B卷经常给一段代码,问它是BIO、NIO还是AIO,或者直接考察阻塞与非阻塞、同步与异步的区别。
简单说:
- BIO是同步阻塞,一个连接一个线程,连接数一多线程数爆炸,性能急剧下降;
- NIO是同步非阻塞,一个线程可以处理多个连接,通过Selector轮询事件就绪状态;
- AIO是异步非阻塞,内核完成IO操作后回调通知应用,真正解放应用线程。
直播平台的网关层、长连接服务,底层基本都是NIO模型,因为要支撑数十万甚至百万级的同时在线连接。Netty是NIO的封装,B卷如果提到了Netty,你要能说出它的核心组件,EventLoop、ChannelPipeline、ByteBuf,以及为什么用NIO而不是BIO来支撑高并发。
4. 编程题拆解:从读懂题意到最优解
4.1 高频题型与解题框架
编程题部分,B卷一般会出现链表、二叉树、动态规划、字符串处理这几类。我挑几个最常出现的题型,给大家梳理一套解题框架。
链表类题目,常见的有反转链表、合并两个有序链表、链表是否有环、找链表中点。这类题的核心是掌握虚拟头节点和双指针技巧。虚拟头节点可以避免处理头节点的特殊情况,双指针则是解决“环”“中点”“倒数第k个”这类问题的利器。
二叉树类题目,常见的有层序遍历、最近公共祖先、二叉树的最大深度。层序遍历要记住用队列辅助,而最近公共祖先的递归解法要分清楚三种情况:两节点分别在左右子树、都在左子树、都在右子树。
动态规划类题目,是区分度的关键。常见的有最大子序和、最长上升子序列、编辑距离、背包问题。解题框架很固定,定义状态、写状态转移方程、确定初始化和遍历顺序。最忌讳的就是一上来直接写代码,先把状态定义和转移方程写在草稿纸上,确认无误再动手。
字符串处理类题目,常见的有最长无重复字符子串、字符串全排列、正则表达式匹配。这类题要特别关注边界条件,空字符串、只有一个字符、全部相同字符等。
举个例子,最长无重复字符子串这道题,最优解是滑动窗口加哈希表,时间复杂度O(n)。我见过很多同学第一次写的是暴力解,两层循环枚举所有子串,时间复杂度O(n^2),在笔试中大概率会超时。正确思路是维护一个左指针和一个右指针,右指针不断向右扩展,当遇到重复字符时,将左指针跳到该字符上次出现位置的下一个位置,过程中记录最大长度。
4.2 复杂度分析是隐藏加分项
编程题不只是写对代码就完事,笔试系统通常还会看你代码的运行时间和内存占用。很多同学在本地IDE跑通了,一提交却超时,原因就是复杂度没控制好。
所以在编程题部分,每写完一道题,都要自己评估一遍时间复杂度和空间复杂度。比如用递归做二叉树遍历,时间复杂度O(n),空间复杂度O(h),h是树的高度;但如果用递归做斐波那契,不优化的话时间复杂度是O(2^n),空间复杂度O(n),在n稍大时完全跑不动。
这里有个我踩过的坑,以前笔试时写斐波那契,直接用递归加备忘录,虽然时间复杂度降到了O(n),但空间复杂度还是O(n)。后来看到一个更优的做法,用两个变量滚动迭代,空间复杂度直接降到O(1)。这种细节,笔试系统是会给你加分的,因为同样的功能,你用更少的内存完成,说明你对代码的掌控力更强。
5. 系统设计题:直播互动场景怎么答才不丢分
5.1 从聊天室到直播间:先理清需求
B卷的最后一道设计题,大概率是直播相关的场景,比如设计一个直播弹幕系统、设计一个直播间礼物系统。这类题看起来开放,其实有固定的答题框架。
我先说一下最常见的错误:拿到题就开始画架构图,写用什么中间件、什么数据库,结果连核心需求都没说清楚。系统设计题的第一步永远是确认需求。
以弹幕系统为例,你要先搞清楚几个关键问题:
- 弹幕的延迟要求是多少?实时弹幕要求秒级甚至毫秒级到达;
- 写入量有多大?一个热门直播间可能每秒上万条弹幕;
- 读取量有多大?直播间观众可能几十万人在线,相当于一个热点数据要被大量并发读取。
这些问题的答案直接决定技术选型。延迟要求高,就要用长连接或WebSocket,而不是HTTP轮询;写入量大,就要引入消息队列做削峰;读取量大,就要加缓存层,避免所有读请求都打到数据库。
5.2 架构设计:分层的标准答案
需求确认后,就可以画出分层架构。标准答案是接入层、业务逻辑层、数据层三层。
接入层负责维护客户端的长连接。每个直播间是一个Topic或者Channel,客户端通过WebSocket或自定义TCP协议连接接入层,接入层负责任务分发。
业务逻辑层负责处理弹幕的合法性校验、敏感词过滤、消息存储。这个环节要考虑如何保证消息有序,比如同一个用户的弹幕不能乱序,同一个直播间内的消息整体有序。实践中通常会给消息加自增ID,或者用Redis的INCR生成房间内消息序号。
数据层负责消息的最终存储。弹幕消息的特点是写多读少,而且很少修改,所以适合用消息队列加NoSQL存储。消息先进Kafka或RocketMQ削峰,再由消费者写入存储层。读取时,用户刚进入直播间时只需要读取最近几十条弹幕历史,所以可以用Redis的列表结构保存每个直播间最近的N条弹幕。
如果题目问的是礼物系统,则要额外考虑赠送礼物的高并发扣减、排行榜的实时计算、消息的可靠投递。高并发扣减可以用Redis的原子操作,排行榜可以用Redis的有序集合,消息投递则要考虑失败重试机制。
核心是你要在答题过程中展示出“我能把一个模糊的需求拆解成清晰的技术方案”的能力,而不是堆砌中间件名词。
5.3 评估与演进:在线人数从1万到100万的扩容路径
系统设计题如果只画一个静态架构,只能拿及格分。想拿高分,必须展示演进思维,也就是当系统从1万人同时在线增长到100万人时,你的架构如何变化。
1万人阶段,一台服务器加Redis加MySQL就够了。弹幕写入走WebSocket直连,写入MySQL,读取时查Redis缓存最近消息。
10万人阶段,一台服务器扛不住了,需要引入负载均衡和网关层。WebSocket服务集群化,消息通过内部的发布订阅系统分发。同时引入消息队列,削峰填谷,避免数据库被打垮。
100万人阶段,就需要对直播间做分区或分片。比如按直播间ID做哈希,不同直播间的消息路由到不同的消息节点。这一阶段还要考虑跨区域部署,用户接入最近的接入层节点,层与层之间通过消息中间件做数据同步。
这套演进思路,能直观地告诉阅卷人你是否有过真实的大流量系统设计经验。哪怕你没实际做过,把这个逻辑讲通,也是一个合格的系统设计答案。
6. 容易忽略的细节:失误复盘与备考建议
6.1 笔试中的低级失误清单
自己复盘这套卷子时,我总结了笔试中最容易丢分的几个低级失误,都在这里列出来。
第一,客观题看错选项。经常有同学在“下列说法不正确的是”这类题目上掉坑,明明题干问的是“不正确”,结果选了正确项,丢分丢得非常冤。
第二,编程题没有先想清楚边界条件。比如链表题没考虑空链表,数组题没考虑长度小于k的情况,二叉树题没考虑根节点为空的情况。这些边界条件在笔试系统里往往是隐藏测试用例,一测一个失败。
第三,设计题忽略了非功能需求。很多同学画完架构图就开始写接口,完全没有提到高可用、容灾、监控告警。实际上设计题的评分标准中,可靠性、扩展性、运维性是重要的评分维度。
第四,代码风格问题。变量名用a、b、c代替,没有缩进,看起来就很不专业。笔试系统是机器评阅为主,但代码可读性差会影响后续面试官的人工复核。
6.2 春招笔试的复习策略和时间安排
最后给出一个我个人认为效率比较高的复习思路,适合离笔试还有两到四周的同学。
第一周,主攻客观题。重点过一遍HashMap、ConcurrentHashMap、线程池、JVM内存结构、TCP/UDP、IO模型这些高频考点。不要只刷题,要做笔记,把每个知识点背后的原理写下来。
第二周,主攻编程题。按数据类型分类刷题,链表、二叉树、字符串、动态规划各刷一定数量。每道题做完后,花五分钟写一遍复杂度分析,最好再想想有没有其他解法。
第三周,集中做整套模拟题。网上能找到不少大厂的历年笔试题,挑两三套按真实考试时间来做。做的时候严格计时,做完之后认真复盘每一道错题。
第四周,查漏补缺。把前几周的错题翻出来重做,重点看自己反复出错的知识点,针对性补强。
关于背题和刷题的关系,我一直觉得刷题的目的是形成条件反射,但仅仅背答案是不够的,因为笔试题目稍微变个方式,你不会原理就做不出来。所以建议每做一道题,都要能讲清楚思路,而不是只满足于代码提交通过。
这套B卷虽然已经是2020年的题目,但它考察的知识点和能力模型,到今天依然是直播、社交、音视频类公司笔试的核心。把每一类题背后的原理搞懂,比刷多少道题都重要。我在实际复习过程中发现,真正有用的不是题海战术,而是每做完一道题都问自己一句“为什么”,把这个习惯坚持下来,笔试的通过率会有很明显的提升。