ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

B站2020后端笔试卷全解析:从Java基础到场景设计的考点拆解

B站2020后端笔试卷全解析:从Java基础到场景设计的考点拆解 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()编号只取rn1就是每个人最近一笔。这个写法在笔试题里出现频率极高建议顺手把RANK()和DENSE_RANK()的区别也弄清楚——RANK()在遇到并列时会跳号比如并列第一后会直接到第三DENSE_RANK()不跳号并列第一后还是第二。除了查询这套卷子的SQL题还可能出现更新删除操作。比如“如何安全地批量更新一张大表的记录避免锁表”。很多人直接写UPDATE全表这在数据量大时会导致长事务、锁冲突、主从延迟。实际可行的方案是分批更新比如每次只取1000条主键在事务里更新完提交循环执行。这也是我在排查线上慢SQL时最常跟同事强调的点。3.3 TCP/IP和操作系统的高频简答网络部分TCP三次握手四次挥手是万年不变的老题。但我建议你别只背状态流转得理解一个核心问题为什么握手要三次挥手要四次三次握手是为了确认双方的发送和接收能力都正常。客户端发送SYN服务端收到后知道客户端的发送能力没问题服务端回复SYNACK客户端收到后知道服务端的发送、接收能力都没问题同时自己的接收能力也没问题客户端再回一个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 LinkedHashMapInteger, Integer { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity capacity; } Override protected boolean removeEldestEntry(Map.EntryInteger, 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) { PriorityQueueInteger 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、长度为0K大于数组长度K等于0这些情况在代码开头就要处理。很多候选人功能写对了一跑测试用例就挂在空指针上。第二变量命名要见名知意。笔试环境没有IDE智能提示你写一个n1、n2、n3阅卷人看着都头疼。用left、right、cur、newNode这种名字哪怕代码有一两个小错面试官也愿意继续看下去。第三写完后自己口头跑一遍测试用例。选一个普通用例、一个边界用例、一个重复元素用例在脑子里过一遍。这个习惯能解决60%以上的低级错误。第四别一上来就写最优解。如果你只能写出来暴力解法那就先把暴力解法写出来保证得分然后再补一句“数据量大的话可以用堆优化”。笔试首先是拿分然后才是炫技。5. 场景设计题这类题怎么答才不飘5.1 排行榜/热门榜设计从SQL到Redis的演进这套卷子的简答或编程题里容易出现一类场景题比如“设计一个视频热门榜要求实时更新TopN”。这类题没有标准答案考察的是你遇到需求时有没有系统思考能力。最朴素的方案是直接查数据库给播放量、点赞数、评论数各建索引按综合得分排序取Top100。这个方案在数据量小、实时性要求不高的时候完全可行也是我建议你在笔试中先给出的答案。但面试官一定会追问如果视频数量到了千万级用户每秒都在刷榜单MySQL扛得住吗这时候就要往缓存方向想。Redis的ZSET有序集合适合做排行榜member是视频IDscore是综合得分。每次播放量增加时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到手。
返回列表