☰
B站2020后端笔试卷全解析:从Java基础到场景设计的考点拆解
2026/10/24 7:50:49 网站建设 项目流程

2020年那会儿,校招笔试还流行开摄像头做在线题。我当年刷到B站这套后端笔试卷(二)时,第一反应是“题量不小”,第二反应是“考察点很杂”。跟很多只堆八股的公司不一样,这套卷子把Java基础、并发、数据库、网络、算法、场景设计全串在了一起,表面上考知识点,实际上考的是你平时写代码时有没有真的想过“为什么”。我后来带团队面试新人,偶尔还是会翻出类似的卷子当参照——不是因为题目多经典,而是它确实能筛出“只会背”和“真会做”的人。

这篇文章不打算给你一份“标准答案速查表”,那没意思。我更想以从业者的视角,把这套卷子涉及的考点、每道题背后的出题逻辑、以及实际工作中对应的场景,一层层拆开来讲。如果你是准备校招或实习的候选人,可以拿它当复习提纲;如果你已经工作几年,也可以看看当年那些“背过就忘”的知识点,现在是不是真的理解透了。

1. 先聊聊这套卷子的整体布局

1.1 为什么叫“试卷(二)”,它和“试卷(一)”有什么不同

大厂校招笔试通常不是一套题打天下。题库里会有多套平行卷,随机分配或者按投递时间分批开放,目的是防止泄题和作弊。B站这套“2020校园招聘后端笔试卷(二)”对应的是题库里偏后端方向的其中一套,它和“试卷(一)”在题型结构上基本对齐,但具体题目和侧重点会有差异。

从考察内容看,这套卷子比较典型的构成是:选择题(单选+多选)覆盖Java、操作系统、网络、数据库,编程题一两道,再加上SQL题和简答/场景设计题。选择题考的是知识面广度和基础扎实程度,编程题考的是手写代码能力,而SQL题和场景题,说白了就是看你有没有真实项目的影子——很多人八股背得溜,一到手写SQL或者设计评论系统就露馅。

1.2 考点权重大致怎么分布

根据我刷题和带新人的经验,这套卷子的考点权重差不多是这样的:

考察方向常见题型大概占比实际对应能力
Java基础与集合单选、多选20%日常开发80%的CRUD都在跟集合打交道,这里挂说明地基不稳
并发与多线程选择、简答15%高并发业务的基础,B站弹幕、评论、直播这类场景尤其看重
JVM与内存模型选择、简答10%排查线上OOM、GC问题绕不开这些
数据库与SQLSQL题、选择20%后端工程师最核心的硬技能之一
计算机网络选择、简答10%排查接口慢、连接异常时天天用到
操作系统选择5%-10%进程线程、死锁、内存管理,属于基础素养
算法编程编程题15%-20%考察临场写代码的熟练度和思路完整性

这个比例不是官方数据,是我根据自己做题和面试候选人的反馈估的,但大致能反映一个趋势:基础题占大头,编程题是分水岭,SQL和场景题决定你能不能拿高分。

1.3 这套卷子适合哪些人拿来练手

如果你正在准备Java后端方向的校招、实习,或者工作一两年想跳槽但觉得基础不牢,这套卷子很适合作为自测题。它不涉及太偏门的框架知识,也不考具体业务,核心都在计算机基础和Java后端通用能力上。换句话说,哪怕你不是投B站,这套题的知识点也能无缝迁移到其他互联网公司的笔试题里。

有一说一,这套卷子的难度在当年校招里算中等偏上。选择题里有些多选选项特别有迷惑性,编程题虽然不是Hard级别,但想要在30分钟内写出无Bug且考虑了边界条件的代码,还是需要一定功底的。

2. 核心考点逐个拆解:Java基础、集合与并发

2.1 Java基础与集合:HashMap和ArrayList不能只背结论

笔试选择题里,Java集合相关的题目几乎是必出的。这套卷子我记得很清楚,有一道题问HashMap在什么情况下会从链表转成红黑树,选项里提到了链表长度达到8、数组长度达到64、链表长度达到6等条件。很多背过八股的人能选对“长度为8且数组长度>=64”,但你要是追问一句“为什么是8”,不少人就懵了。

这里我展开说一下。链表转红黑树的阈值8,并不是拍脑袋定的,它来自于泊松分布。在负载因子0.75的情况下,HashMap单个桶内链表节点数出现的概率分布中,长度为8的概率已经低到千万分之几,这时候用红黑树去替代链表,虽然插入和删除的常数时间变大,但可以避免极端极端情况下链表过长导致的查询退化。而数组长度不到64就优先扩容,是因为桶还没铺开,扩容的成本比把链表转成红黑树的成本更低,收益更大。

再说ArrayList和LinkedList,这套卷子多半也会有一道。核心区别不是“数组和链表”这么简单,而是随机访问、插入删除的复杂度以及内存局部性。LinkedList的节点是分散在堆里的,CPU缓存不友好,实际遍历性能往往比ArrayList差很多。我在实际工作中很少看到LinkedList有不可替代的场景,笔试里选了它,多半是因为理论上的“中间插入O(1)”——但真要做中间插入,你首先得遍历到那个位置,这里已经是O(n)了。

还有String、StringBuilder、StringBuffer这组老生常谈。String不可变性、字符串常量池、拼接字符串时到底创建了几个对象,这些都是选择题常客。我的建议是别只记结论,去看一下字节码,看看String用+拼接时JVM到底做了什么优化,以及什么情况下StringBuilder是更好的选择。

2.2 并发编程:synchronized、volatile、线程池真考你会不会用

并发这部分的题,笔试里一般不会让你直接写一段多线程代码跑起来,而是通过选择题和简答来考你概念是否清楚、边界条件是否想全。

synchronized和ReentrantLock的区别,这套卷子大概率会涉及。synchronized是JVM层面的锁,异常时JVM会自动释放锁;ReentrantLock是JDK层面的锁,需要手动lock和unlock,配合try-finally使用。ReentrantLock还支持可中断、可轮询、公平锁、多个Condition条件队列等高级能力。早年还有个区别是synchronized在锁竞争激烈时性能不如ReentrantLock,但JDK 6之后做了锁升级优化,性能差距已经不大了。所以现在面试官更爱问的是“既然性能差距不大,为什么还要用ReentrantLock”,答案就是那些高级特性。

volatile也是高频考点。volatile保证可见性和有序性,但不保证原子性。很多人会把它和synchronized搞混,这里我提供一个理解方式:volatile解决的是“一个线程改了值,其他线程能不能立刻看到”的问题;synchronized解决的是“多个线程同时改一个值,会不会改出问题”的问题。用一个生活化的类比,volatile就像站在楼顶喊了一嗓子,整栋楼都听见了;synchronized就像给房间上了锁,想进来得先拿到钥匙。

线程池的考察点集中在几个核心参数:corePoolSize、maximumPoolSize、keepAliveTime、阻塞队列、拒绝策略。B站这种视频网站,评论、弹幕、私信推送都会用到线程池。笔试题喜欢给你一个场景,比如核心线程5、最大线程10、队列容量100,现在同时来了200个任务,问最后任务怎么分配、哪些被拒绝。这种题光是背参数不够,你得清楚线程池的执行顺序:先创建核心线程处理任务,核心线程满了再进队列,队列满了再创建非核心线程,还满就触发拒绝策略。

我见过不少人在这里纠结“队列有界还是无界”,其实笔试只要记住:有界队列配饱和策略是常规用法,无界队列的风险是内存溢出。实际项目里,我一般会建议用有界队列+CallerRunsPolicy,等线程池满了让提交任务的线程自己执行来兜底,同时方便在监控里看到堆积情况。

2.3 JVM与内存模型:一道简单题背后的排查思维

JVM这部分的题,选择题出现频率最高的几个点:堆内存分区、GC Roots、垃圾回收算法、类加载双亲委派、内存溢出和栈溢出。有些公司喜欢出“下面哪些对象可以作为GC Roots”这类多选,答案通常包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、本地方法栈中引用的对象。

这里有一个实战价值极高的点:线上Java服务CPU飙升或者频繁Full GC,排查思路是什么。笔试虽然只考选择题,但背后问的是你知不知道遇到OOM时先看堆转储、用MAT或jprofile分析、找大对象和泄漏点,以及怎么用jstat、jmap、jstack这些命令行工具。我招人的时候,如果候选人能在做JVM题时顺口提到“我们线上用jstat观察GC频率,用jstack抓线程栈”,我会觉得他真的有处理线上问题的经验,而不只是背了书。

双亲委派模型也值得展开聊聊。为什么JDK要设计这种加载机制?核心是安全性和一致性。比如java.lang.String这种核心类,必须由启动类加载器加载,防止你写一个假的String混进JDK里捣乱。但实际场景中也有打破双亲委派模型的框架,比如Tomcat的WebAppClassLoader,它为了加载多个应用的不同版本类库,优先加载Web应用自己的类,实在找不到才委托父加载器。这些问题笔试不一定考,但面试官追问起来,你能说清楚就是加分项。

3. 数据库与网络:卷子里的实战分

3.1 索引与事务:背了八股还要会看执行计划

数据库这块,笔试卷里一般在选择题和SQL题两个地方出现。选择题考索引失效、事务隔离级别、MVCC、间隙锁这些,SQL题直接给你几张表,让你写查询。

索引失效是选择题重灾区。最典型的有:对索引列使用函数、隐式类型转换、LIKE前缀模糊匹配、使用OR连接非索引列、在索引列上做计算。这里我想展开讲一个最常见的坑:字符集和排序规则不一致导致的隐式转换。比如你表里的手机号字段是varchar,但查询时传了一个整数类型,MySQL可能会把字段强转成数字去比较,索引直接失效。看起来只是类型不严谨,线上就可能导致慢查询把数据库拖垮。

事务隔离级别这块,MySQL默认是可重复读(REPEATABLE READ),为什么不是读已提交(READ COMMITTED)?因为MySQL的复制是基于binlog的,而binlog只记录事务提交后的结果,如果使用读已提交并且binlog格式是statement,主从复制可能因为执行顺序不一致导致数据不一致。所以InnoDB直接默认可重复读。另外,可重复读通过MVCC实现了快照读,通过当前读+间隙锁解决了幻读问题,这里面的细节比较深,但面试时能讲清楚“当前读”和“快照读”的区别,就已经超过大多数候选人了。

3.2 手写SQL:排名、分组TopN、更新删除的实战写法

SQL题是这套卷子里最能拉开差距的部分之一。很多人LeetCode刷得飞起,但手写一条“根据分数排名并查询每门课前三名”的SQL,能卡住十分钟。

这里我直接给出最实用的解法:窗口函数。MySQL 8.0之后支持了ROW_NUMBER()、RANK()、DENSE_RANK(),一个开窗函数就解决了过去用自连接、用户变量绕半天的问题。

经典的“分组取Top N”,比如查每个用户最近的一笔订单,可以这样写:

SELECT user_id, order_id, order_time FROM ( SELECT user_id, order_id, order_time, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn FROM orders ) t WHERE rn = 1;

PARTITION BY user_id把每个人的订单分成一组,ORDER BY order_time DESC让最新订单排第一,ROW_NUMBER()编号,只取rn=1就是每个人最近一笔。这个写法在笔试题里出现频率极高,建议顺手把RANK()和DENSE_RANK()的区别也弄清楚——RANK()在遇到并列时会跳号,比如并列第一后会直接到第三;DENSE_RANK()不跳号,并列第一后还是第二。

除了查询,这套卷子的SQL题还可能出现更新删除操作。比如“如何安全地批量更新一张大表的记录,避免锁表”。很多人直接写UPDATE全表,这在数据量大时会导致长事务、锁冲突、主从延迟。实际可行的方案是分批更新,比如每次只取1000条主键,在事务里更新完提交,循环执行。这也是我在排查线上慢SQL时最常跟同事强调的点。

3.3 TCP/IP和操作系统的高频简答

网络部分,TCP三次握手四次挥手是万年不变的老题。但我建议你别只背状态流转,得理解一个核心问题:为什么握手要三次,挥手要四次?

三次握手是为了确认双方的发送和接收能力都正常。客户端发送SYN,服务端收到后知道客户端的发送能力没问题;服务端回复SYN+ACK,客户端收到后知道服务端的发送、接收能力都没问题,同时自己的接收能力也没问题;客户端再回一个ACK,服务端收到后知道自己的发送能力没问题。三次缺一不可。为什么不是两次?因为两次握手时,服务端无法确认客户端的接收能力是否正常,也容易出现历史重复连接请求导致的资源浪费。

挥手要四次,本质是因为TCP是全双工的。客户端发送FIN表示我不再发数据了,但服务端可能还有数据要发给客户端,所以服务端先回ACK确认收到,等到自己的数据发完了,再发FIN。这中间ACK和FIN是分开的,所以多了两次报文的交互。

TIME_WAIT状态也是考点。主动关闭方在收到对方的FIN并回复ACK后,会进入TIME_WAIT,持续2MSL。这个状态的目的是防止最后一个ACK丢失导致对方重发FIN,以及让旧连接的报文在网络中自然消失,避免干扰新连接。实际工作中,如果服务端主动关闭大量短连接,会出现大量TIME_WAIT占用端口,这时候可以考虑开启tcp_tw_reuse或者改用连接池,但改内核参数要谨慎,最好先搞清业务模式再动手。

操作系统部分,比较常见的是进程和线程的区别、死锁的四个必要条件、虚拟内存和分页。这里有个容易被忽略的点:线程切换为什么比进程切换代价小?因为线程共享进程的地址空间,切换时不需要切换页表(逻辑上),缓存命中率更高。明白了这一点,你就知道为什么协程性能更好——它连内核态都不进,直接在用户态完成调度。

4. 编程题实战:手撕代码的门道

4.1 手写LRU缓存:最常见的“数据结构设计”题

这套笔试卷的编程题,我印象中有一道是“设计一个LRU缓存,支持get和put操作,要求在O(1)时间内完成”。这题在LeetCode上是LRU Cache,属于面试高频题里的天花板,既能考哈希表,又能考双向链表,还能考你对删除和插入的边界处理是否干净。

最经典的做法是哈希表+双向链表。哈希表负责O(1)的查找,双向链表负责维护访问顺序。每次get一个key,就把对应节点移动到链表头部;put一个key,如果容量满了,就删除链表尾部的节点,再插入新节点到头部。

手写双向链表比较长,笔试时为了省时间,也可以用LinkedHashMap实现。Java的LinkedHashMap天然支持访问顺序,只要重写removeEldestEntry方法即可:

class LRUCache extends LinkedHashMap<Integer, Integer> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return size() > capacity; } }

这个写法很简洁,但面试官如果追问“LinkedHashMap底层是怎么维护访问顺序的”,你得能答出accessOrder参数和afterNodeAccess/hooks方法。实际工作中,很多框架自带的本地缓存也是基于类似原理,但会加上过期时间、淘汰策略等更复杂的逻辑。

4.2 TopK问题:堆排序和快排思想的取舍

另一道常见的编程题是TopK,比如“求一个数组中最大的K个数”。这道题在B站这种内容平台很实用,因为热门视频榜、热门UP主榜背后都会用到类似的算法。

最直接的思路是把数组全排序再取前K个,时间复杂度O(n log n)。但这显然不是最优解,笔试和面试里通常会期待你给出以下两种方案之一:

方案一:小顶堆(Java里用PriorityQueue)。维护一个大小为K的小顶堆,遍历数组,如果堆没满就直接入堆,堆满了且新元素比堆顶大,就弹出堆顶、入堆新元素。遍历结束后,堆里的K个元素就是最大的K个。时间复杂度O(n log K),空间O(K)。

public int[] topK(int[] nums, int k) { PriorityQueue<Integer> minHeap = new PriorityQueue<>(k); for (int num : nums) { if (minHeap.size() < k) { minHeap.offer(num); } else if (num > minHeap.peek()) { minHeap.poll(); minHeap.offer(num); } } int[] res = new int[k]; int i = 0; for (int num : minHeap) { res[i++] = num; } return res; }

方案二:基于快速排序的partition思想,定位到第K大的位置,左边就是TopK。平均时间复杂度O(n),但最坏情况下会退化到O(n²)。笔试时可以提这个方案作为优化解,但用堆排更稳妥。

我建议笔试做题时优先写堆排,因为代码简单、不容易写错,而且时间复杂度的解释也清晰。至于快排partition,可以在面试聊方案时展示你思路的开阔,不一定要在笔试里硬写。

4.3 答题节奏与代码规范:笔试的隐形分

编程题丢分,很多时候不是不会做,而是写得太乱或者边界条件没想全。这里分享几个我作为面试官最看重的细节。

第一,先确认输入边界。数组为null、长度为0,K大于数组长度,K等于0,这些情况在代码开头就要处理。很多候选人功能写对了,一跑测试用例就挂在空指针上。

第二,变量命名要见名知意。笔试环境没有IDE智能提示,你写一个n1、n2、n3,阅卷人看着都头疼。用left、right、cur、newNode这种名字,哪怕代码有一两个小错,面试官也愿意继续看下去。

第三,写完后自己口头跑一遍测试用例。选一个普通用例、一个边界用例、一个重复元素用例,在脑子里过一遍。这个习惯能解决60%以上的低级错误。

第四,别一上来就写最优解。如果你只能写出来暴力解法,那就先把暴力解法写出来,保证得分,然后再补一句“数据量大的话,可以用堆优化”。笔试首先是拿分,然后才是炫技。

5. 场景设计题:这类题怎么答才不飘

5.1 排行榜/热门榜设计:从SQL到Redis的演进

这套卷子的简答或编程题里,容易出现一类场景题,比如“设计一个视频热门榜,要求实时更新TopN”。这类题没有标准答案,考察的是你遇到需求时有没有系统思考能力。

最朴素的方案是直接查数据库:给播放量、点赞数、评论数各建索引,按综合得分排序取Top100。这个方案在数据量小、实时性要求不高的时候完全可行,也是我建议你在笔试中先给出的答案。但面试官一定会追问:如果视频数量到了千万级,用户每秒都在刷榜单,MySQL扛得住吗?

这时候就要往缓存方向想。Redis的ZSET(有序集合)适合做排行榜,member是视频ID,score是综合得分。每次播放量增加时,ZINCRBY一下;取TopN时,ZREVRANGE直接拿。整个榜单的读写都在内存里完成,性能足够顶住高频访问。

但这里还有一个坑:综合得分往往不是简单的播放量,而是播放量、点赞、评论、分享加权算出来的。线上做法一般是用异步任务,定时把各项指标聚合成综合分,再更新到ZSET里。如果要求毫秒级实时,就得在写入播放量时同步算分,Redis里执行一段Lua脚本保证原子性。笔试里能讲到这个层级,基本就是高分段了。

5.2 评论盖楼与弹幕消息:高并发写入的架构思路

B站业务里很有特色的是评论和弹幕。场景题如果让你设计“百万级弹幕发送系统”,或者“深层次评论盖楼”,其实考的是你对高并发写入的理解。

弹幕发送的核心矛盾是:量大、时效性强,但不要求强一致。每个用户在发送弹幕时,如果都同步写数据库,数据库瞬间就挂了。比较务实的方案是数据先写本地内存队列或消息队列(Kafka/RocketMQ),异步批量刷到数据库和缓存层,同时通过WebSocket或HTTP长轮询推送给前端。弹幕的展示渠道通常是Redis的有序集合或者专门的检索服务,按视频维度和时间维度组织。

评论盖楼则涉及树形结构的存储。常见的方案有四种:邻接表(parent_id)、路径枚举、闭包表、嵌套集。邻接表最简单,但查多层子评论需要递归,性能差;路径枚举用一条字符串存完整路径,查询方便但更新麻烦;闭包表用一张表存所有祖先和后代关系,查询灵活度高,但插入时需要维护多条记录。互联网大厂比较多的是混合方案:用parent_id在建表时存储,楼层数控制在两层,超过两层的只展示“查看完整对话”,从缓存中单独拉取。这种设计的好处是,把高深度的递归查询转换成对少量数据的多次缓存查询,实际效果很好。

5.3 开放题怎么答:先给约束,再给方案

很多同学做场景题时最大的问题是“想一口吃成胖子”。上来就说要用Redis、MQ、分库分表、微服务,结果连QPS是多少、数据量多大都没说清楚。我建议的答题框架是三步走。

第一步,确认约束和目标。比如:榜单更新频率要求多高?数据量是多少?需要持久化还是可以容忍丢失?这个步骤很重要,因为一个日活十万的小应用和日活千万的大应用,架构方案是完全不一样的。笔试中你可以主动写出假设条件,比如“假设QPS在1万以上,单机MySQL无法承受”。

第二步,给出一个简单但可运行的方案。先走通主流程,比如数据库查出来后加缓存。不要一上来就引入分布式组件。

第三步,分析瓶颈并逐步优化。哪里慢了解决哪里,比如数据库压力大就加缓存,单机内存不够就分片,消息堆积就扩容消费者。这个演进过程比最终答案更重要,因为它展示了你从0到1的思考路径。

我在看候选人做这类题时,最欣赏的是那种敢写“如果数据量只有一万,用MySQL定时算一个榜单缓存就足够了,没必要上Redis”的人。能说出“在什么规模下该用什么方案”的,才是真正有实战经验的人。

6. 备考路线:从这份卷子里得到的启示

6.1 我自己踩过的坑

当年我做这套卷子的时候,最大的失误是在选择题上纠结太久。有些多选选项设置得很刁钻,比如“以下哪些线程安全的集合”,ArrayList和CopyOnWriteArrayList放在一起,我反复改了两三次。结果编程题只剩不到20分钟,思路都对,代码却来不及写完,最后因为一个小Bug没跑通测试。后来我总结了一个经验:笔试题量大的时候,选择题纠结超过90秒就先蒙一个标记起来,赶紧跳过去,等做完编程题有空再回头看。

第二个坑是SQL题。当年我习惯用MySQL 5.7的写法,遇到“分组取TopN”只能写子查询和用户变量,费时又容易错。后来发现窗口函数才是这类题的最优解,但很多同学在笔试前根本没学过窗口函数。这里特别提醒:MySQL 8.0和各大云数据库都已经支持窗口函数,这套写法属于高性价比技能,校招笔试前务必练熟。

6.2 按照这套卷子,怎么规划复习路线

如果你现在准备的是校级后端岗位,我建议把复习内容分成四个阶段。

第一阶段,打牢Java基础。集合源码、反射、泛型、IO/NIO、异常体系,这些选择题高频点要能把每个类的继承关系、数据结构和适用场景说出来。

第二阶段,吃透并发和JVM。线程池参数、synchronized和ReentrantLock的差异、volatile语义、GC算法和常用调优参数。这部分不仅笔试要用,面试和日常排查也会反复用到。

第三阶段,主攻数据库和SQL。索引原理、事务隔离级别、MVCC、锁机制,再花一晚上专门练窗口函数,覆盖排名、累积求和、移动平均、分组TopN等常见场景。

第四阶段,刷算法和场景题。LeetCode的Top100里面,链表、二叉树、动态规划、滑动窗口、LRU、TopK、合并区间这些是重点。场景题不用刷太多,核心是把“确认约束、简单方案、逐步优化”的框架练熟。

6.3 关于标准答案的迷思

最后想多说一句:这套卷子里的题,与其去搜一份“标准答案”背下来,不如自己动手把关键代码跑一遍。HashMap转红黑树的条件,你写个测试程序往里面塞数据看阈值到底是多少;线程池的拒绝策略,你写个小Demo让队列满掉,看看AbortPolicy是不是直接抛异常;窗口函数和用户变量的写法,你在本地数据库里各跑一遍,感受一下性能差异。

这些知识的价值不在一张卷子里的分数,而在你以后排查线上问题的时候会不会手忙脚乱。我后来遇到过很多次生产事故,比如缓存穿透把数据库打挂、慢SQL拖垮接口、线程池满导致任务堆积,全靠当年笔试前狠啃的那点底子撑住了。校招笔试只是一个入口,真正决定你能走多远的,是你有没有养成“知其然、知其所以然”的写码习惯。

这套B站2020后端笔试卷(二),放到今天来看依然有参考价值。祝准备校招的你,刷题顺利,offer到手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询