
备考群经常有人问酷狗这类公司2016年的技术工程师笔试题现在拿出来刷还有价值吗我的答案是值得。2016年正好是移动音乐从纯播放器思维转向服务化、大数据化的重要节点试卷里那些看似基础的选择题、手写算法题和开放设计题每一类都能对应到真实业务痛点。我不是官方出题人也不会把当年那套原始卷子原封不动地默写出来下面所有题目场景和考点归类来自我参加考试后的复盘记录以及和同批考生的交叉比对。你发现哪怕题目细节有偏差考察的底层能力是没变的。做在线音乐服务端这几年我越来越认同一个观点笔试不是考你会背多少API而是考你在紧张状态下能不能用最小代价证明自己具备工程思维。这套卷子给我的感觉也正是如此基础题占了一半却不像学校期末考那样只求对错它更在意你对原理的解释、对边界的思考以及对异常情况的处理。所以这篇文章我不按原卷顺序逐题罗列而是按考点模块拆开每一部分都写清楚“考什么、为什么考、怎么答不丢分”。准备校招笔试的朋友可以直接当复习提纲用工作两三年想换赛道的人也能拿来做一次知识体系自检。1. 一场技术笔试换来的认知研发岗真正在筛的是什么1.1 为什么2016年的题目放到现在仍有参考性2016年的移动互联网正处于一个很微妙的阶段4G刚开始大规模普及音乐App的用户量从百万级冲向千万级很多技术团队正处于“服务能跑但说不清楚瓶颈在哪”的状态。酷狗当年的笔试不会刻意堆砌偏题怪题反而把大量篇幅放在了计算机基础、语言底层机制和数据结构这些看起来“大街货”的知识点上。这不是出题人偷懒而是因为在线音乐业务有一个很典型的特征用户请求集中在热门歌曲、热门榜单、歌手主页等有限热点上读多写少、热点集中、时段性强。这种业务场景下工程师对缓存、并发、队列、索引的理解能不能到位直接决定了线上服务质量。基础题正是筛选这类人才最有效的方式。另外2016年前后端边界开始模糊很多客户端团队的工程师也要写服务端逻辑所以试卷里同时出现C、Java和数据库问题就很正常。现在回看这套题的底层逻辑依然成立哪怕技术栈换成Go、换成了云原生架构操作系统、网络、数据结构和算法依然是硬通货。面试官不怕你经验少怕的是你连原理都讲不明白遇到问题只能靠网上抄来的配置堆叠。1.2 整张卷子的考点分布与分数心理预期我根据回忆把试卷内容粗略分成四块权重参考当年同批次考生反馈未必精确但能代表大致方向模块大致占比典型考察形式高频考点计算机基础30%单选、多选、填空进程线程、内存管理、TCP/IP、死锁语言与数据库20%选择、改错、简答C虚函数、Java内存、SQL索引算法与数据结构30%手写代码、复杂度分析链表、二叉树、排序、Top K开放设计与综合20%场景题、设计题排行榜、缓存、高并发下载很多第一次参加笔试的人容易犯同一个错误拿到卷子从第一题开始死磕结果前面选择题消耗太多时间后面手写代码题和设计题反而没时间写。我的建议是发卷后先花两分钟把整张卷子扫一遍心里给每个模块排好时间优先级。通常来说算法题分值高但耗时大设计题分值高但切入点灵活基础题虽然分值低但往往是最容易拿分的点正常情况下不应该在这里丢太多分。那套卷子里基础题里真正拉开差距的并不是“会不会背定义”而是能不能快速识别出题目里的隐藏条件。例如一提到TCP就考察TIME_WAIT一提到线程就考察锁竞争一提到数据库就考察索引失效。这些东西不是孤立知识点它们在生产环境里都是环环相扣的。2. 算法与数据结构几道高频手写题背后的边界意识2.1 链表反转的一百种写法考官只看两种能力链表反转几乎是2016年技术笔试出现率最高的一道题酷狗这套卷子里也有类似题目而且不是直接给你一个单链表让你反转而是加了点变化要求判断链表中是否有环有环则返回环的入口节点。这相当于是把两题合并成一题重点考察的是链表的基础操作、双指针思想以及边界条件处理。先说说最基础的单链表反转迭代法核心思路是准备三个指针prev、current、next。每次循环先把current的下一个节点保存下来然后把current的next指向prev最后三个指针整体向后移动。很多人在写的时候容易忘记处理头节点为空的场景也容易在循环结束前丢失next指针。我后来帮学弟review代码时发现最容易出错的地方是反转完成后newHead到底指向谁经常有人把prev和current搞混。如果题目增加“每K个一组反转”这个条件难度就上来了。这道题考的不仅是反转逻辑还是递归或者迭代分段的熟练度。我的建议是先把基础反转写顺再考虑分段场景。分段反转的核心是先找到本组的next指针在反转本组前把下一组的头保存下来否则链表会断掉。阅卷人非常看重这类操作是否严谨哪怕思路对只要指针丢了分数就会大打折扣。再说判断链表是否有环。常规做法是快慢指针慢指针每次走一步快指针每次走两步如果快慢指针在某一点相遇说明有环。注意这里有个细节快指针初始化时必须和慢指针指向同一个节点否则跳过相遇判断遍历过程中还要判断fast指针及其next是否为空防止空指针异常。至于寻找环入口经典解法是相遇后让一个指针从头节点重新出发两个指针都一次走一步它们再次相遇的位置就是环入口。这个结论背后有数学推导笔试时能把推导说清楚面试官对你的好感度会明显提高。2.2 Top K、二叉树与字符串套路可以积累边界必须自己推2016年的笔试题里还有个常客是Top K问题。常见问法一个文件里有几亿个数字找出最大的前100个。大多数学生第一反应是先把所有数字排序但放在海量数据的背景下全量排序显然不可行这时候需要用最小堆维护大小为K的堆每来一个新元素就和堆顶比较如果比堆顶大就替换并调整堆。这样时间复杂度是O(N log K)空间复杂度O(K)。不过这道题还有一层坑如果内存放不下所有数据怎么办答案是分治把大文件切分成若干小文件分别算出每份文件里的Top K再合并结果。当时有个同学笔试的时候就只写了堆解法没有写数据量超内存的处理被扣了不少分。这提醒我们写Top K相关代码时一定要先和题目确认数据规模再决定要不要提分治方案。二叉树题目里最典型的是最近公共祖先LCA。递归写法思路很简洁如果当前节点是空返回空如果当前节点等于p或q返回当前节点否则分别去左子树和右子树递归查找。如果左右返回值都非空说明p和q位于两侧当前节点就是LCA如果只有一侧非空说明两个节点都在同一侧返回那一侧的结果。这个递归过程看起来很简短但很多人会忽略一种情况p或q可能根本不在树里。标准解法里如果LCA返回null说明没找到但如果是p在树里、q不在树里递归逻辑会返回p而非null严格来说需要在最外层再做一次存在性校验。字符串题里高频的是最长公共子序列、最长公共子串、KMP匹配。KMP的next数组推导对很多人来说是噩梦但笔试更可能考“能不能写出一个暴力匹配并说清缺陷”。如果你对KMP不熟我建议至少掌握next数组的手算方法知道它如何避免主串指针回溯。实际工作里文本匹配虽然常用现成库但理解这个思想对写日志解析、敏感词过滤、协议解析都有帮助。2.3 复杂度分析写出答案只是第一步阅卷人对算法题的评判分两层第一层是你有没有给出可运行的解法第二层是你有没有分析清解法的复杂度和适用场景。很多人只做到了第一层每次写完代码就急着做下一题忽略了对复杂度的说明这是非常可惜的失分点。以链表反转为例时间复杂度是O(n)空间复杂度是O(1)迭代法不额外占用内存。但如果题目追问反转过程中是否可能栈溢出递归写法就会暴露风险当链表长度达到十万级的时候递归深度可能导致栈溢出。所以如果条件允许优先写迭代。对Top K问题堆解法复杂度是O(N log K)快排partition思路是O(N)平均复杂度但要考虑最坏情况退化到O(N^2)。如果面试官追问要能说出什么时候退化、如何通过随机化选择基准元素规避。以及复杂度分析不能只看时间还要看空间。很多系统设计题里时间复杂度过关但空间无法承受照样不是好方案。笔试时把“时间换空间”和“空间换时间”的权衡说清楚往往比给出一个完美算法更让面试官认可。3. 操作系统与计算机网络理论题里埋着的线上故障隐患3.1 进程、线程与协程概念题不能只背定义操作系统部分的选择题和简答题其实是在变相考察你平时有没有处理过并发问题。典型的题目包括进程和线程的区别是什么多线程程序为什么会比多进程更容易出现数据竞争协程和线程相比优势在哪我在笔试答题时把区别总结成了三层资源分配角度进程是系统资源分配的基本单位有自己的地址空间线程是CPU调度的基本单位共享进程的地址空间协程则是用户态控制的并发单元切换成本更低。但光是列出这些还不行阅卷人更想听的是你如何用这些区别解释实际问题。比如为什么进程间通信比线程间通信复杂因为进程地址空间隔离你需要通过管道、消息队列、共享内存等方式交换数据而线程则天然共享堆内存代价是你必须引入锁来保护共享数据。这个回答方式放在线上故障排查里也适用遇到线上线程阻塞你得能想到锁竞争、死锁、上下文切换开销这些点。另外2016年已经有人开始追问协程了。如果当时你把协程理解成“轻量级线程”回答就算到位了。协程是靠用户态调度实现协作式并发遇到IO操作时主动让出CPU等IO完成后再回来继续执行不需要内核态切换。这个特性对高并发网络服务特别关键后来的Go语言、云原生时代大量采用类似思路也能反过来说明当时出题方向的合理性。3.2 TCP握手与挥手TIME_WAIT为什么是高频考点网络部分的必考题基本绕不开TCP三次握手、四次挥手。三次握手是为了确认双方的发送和接收能力SYN、SYNACK、ACK这个顺序不能乱四次挥手则是因为TCP连接是全双工的每一方向都要独立关闭所以需要FIN、ACK、FIN、ACK两轮交互。酷狗这套题里让我印象最深的是问了大量TIME_WAIT的问题。问题是主动关闭连接的一方在收到对方的FIN并回复ACK后为什么还要进入TIME_WAIT状态而不是立刻关闭这个状态至少保留两个MSL时长为什么答案有两个层面第一确保最后一个ACK能够到达对方如果对方没收到ACK会重发FIN自己还能有机会再次回复第二防止旧连接中延迟到达的数据包干扰新连接。对在线音乐这种短连接请求高频的业务来说TIME_WAIT连接过多会导致本地端口和文件描述符被占满后续请求无法建立连接。面试官听到这个层面基本就能确认你不是只会背状态迁移图。我从这套卷子之后才真正养成习惯分析线上连接问题不看表象先抓状态。如果客户端大量报连接超时第一步就看服务端有多少TIME_WAIT连接再查有没有打开SO_REUSEADDR、SO_KEEPALIVE这些配置在这里能直接对应笔试知识点。3.3 内存与IO答记忆题时顺手建立的工程直觉操作系统内存管理是另一个容易拉开差距的考点。虚拟内存、分页、缺页中断这些概念如果只看书非常抽象但一旦联系到服务端进程OOM、GC频繁、Redis缓存命中率就能立刻变得直观。我记得考卷里有一道填空题虚拟内存的主要作用是什么标准答案是提供地址空间隔离、逻辑上扩充内存、提高多道程序并发度。但后面跟了一道简答题为什么物理内存足够时也可能发生缺页这个问题的答案是分页机制按需加载进程运行时只把当前需要的页装入物理内存其他页留在磁盘交换区访问到未加载的页时就会触发缺页中断。实际工程里如果你设置了过大的JVM堆却把物理内存也占满就会导致操作系统频繁进行页面换入换出程序性能反而下降。这种题目答到第二层面试官会觉得你有实际排查问题的经验。IO模型也是重点尤其要分清阻塞IO、非阻塞IO、IO多路复用、异步IO。笔试题常见问法是select、poll、epoll的区别以及为什么epoll适合高并发。本质原因是epoll使用事件驱动和红黑树管理fd用回调替代遍历避免了select对所有fd进行线性扫描的性能损耗。这个知识点放到在线音乐场景里非常贴切一个热门歌手页面可能要同时维护几十万条长连接如果IO模型选不对服务器很快就会被无谓的CPU占用拖垮。4. C/C、Java与数据库细节题决定薪资档位4.1 语言基础虚函数、内存布局、引用与指针语言题是技术笔试里最容易让基础不扎实的人暴露的一类。酷狗当时以C和Java为主两种语言都会考内存、对象生命周期、并发相关的东西。C里必考的是虚函数。多态的实现基础是虚函数表vtable和虚指针vptr。每个包含虚函数的类都有一张虚函数表每张虚函数表里指向的是该类实际调用的函数地址构造函数执行过程中会初始化vptr所以不要在构造函数里调用虚函数原因是你调的很可能不是子类版本而是当前正在构造的这个版本的实现。这个细节后来成了我排查过的一个线上神坑某个基类构造函数里调了虚函数子类成员尚未初始化导致拿到半初始化状态的对象。C还爱考指针和引用的区别、深拷贝与浅拷贝、智能指针。深拷贝浅拷贝的经典陷阱是如果类里面有指针成员默认拷贝构造函数只是复制指针值两个对象会指向同一块内存析构时会double free。正确的做法是重写拷贝构造函数和赋值运算符或者改用shared_ptr、unique_ptr。笔试时能把这个场景描述清楚比只背一句“深拷贝拷贝对象指向的内容浅拷贝只拷贝指针”要加分得多。Java部分主要考JVM内存区域、垃圾回收机制、集合类的线程安全。有一道题我印象很深ArrayList和Vector区别是什么很多人只回答“Vector是线程安全的”但进一步追问就会露馅。Vector线程安全是因为方法用synchronized修饰但这种粗粒度锁在并发场景下效率并不高所以后来出现了CopyOnWriteArrayList这类并发容器。能谈到这一层说明你不仅懂语法还懂并发设计演进。4.2 数据库索引失效、事务隔离级别与慢查询数据库题比重不小因为在线音乐业务几乎绕不开用户数据、歌单数据、评论数据。那套卷子里的数据库题没有出复杂存储过程重点反而在几个最实用的方向上索引、SQL优化、事务特性。索引部分B树为什么适合作为关系型数据库索引结构这是因为B树把数据集中在叶子节点叶子之间通过指针相连范围查询时能顺序扫描同时树的高度较低磁盘IO次数少。但索引不是建得越多越好因为写入时要同时维护索引会导致性能下降。很多考生在“最左前缀原则”上丢分。一道常见的题是表里有联合索引(a, b, c)以下哪些查询能用到这个索引对where a1 and b2 and c3的查询能用到对where b2 and c3的查询用不到。我当时把这条规律记成联合索引就像一个电话簿先按姓氏排序再按名字排序如果你不知道姓氏光知道名字是没办法直接通过电话簿快速定位的。事务隔离级别也是高频考点。读未提交会有脏读、不可重复读、幻读问题读已提交解决了脏读但不可重复读仍可能发生可重复读解决了不可重复读但幻读仍可能发生串行化级别最高但并发性能最差。MySQL默认是可重复读但这是InnoDB存储引擎的实现选择不同的数据库默认隔离级别可能不同。理解这个层级后还要能结合实际场景比如统计歌曲播放量如果事务隔离级别设置太低可能读到正在回滚的数据导致统计结果异常。5. 开放设计题音乐App服务端最想看到的思路5.1 从“设计一个音乐下载系统”说起开放设计题一般在试卷最后也是区分度最高的一块。酷狗那套题里有一道让我记忆犹新的题如何设计一个音乐下载系统保证下载速度、下载成功率和用户体验。这道题看似开放其实出题人已经把在线音乐核心场景摆出来了。音乐下载不是简单的文件读取它涉及客户端请求、CDN调度、源站存储、网络传输、断点续传、防盗链等一系列问题。答题时要先明确用户视角的需求再逐步拆分系统模块而不是一上来就给技术方案。我的答题结构是这样首先定义场景音乐文件有大有小热门歌曲下载量集中冷门歌曲下载量分散然后设计整体链路客户端发起下载请求服务端返回一个带签名的下载URL客户端通过CDN边缘节点拉取文件如果CDN没有缓存则回源到对象存储或文件服务器接着要考虑断点续传HTTP Range头可以支持从指定字节位置继续下载客户端本地记录已下载的字节数最后要考虑失败重试和流量控制避免下载失败时客户端无限重试打爆服务端。这样答完基本框架已经出来了。很多人忽略的是防盗链和鉴权部分。音乐资源是有版权成本的下载URL通常要带时效性签名签名过期就失效。这个点一旦写出来面试官就知道你具备商业产品思维不是纯技术宅。5.2 缓存、分片与一致性哈希答出层次感开放设计题里缓存策略几乎是必答项。设计音乐播放系统时用户播放某首热门歌曲请求先打到缓存层缓存不存在才回源数据库或文件系统。这里考察的是缓存淘汰策略最简单的答案是LRU但如果要做分布式缓存需要考虑数据一致性、缓存穿透、缓存击穿、缓存雪崩。答题时我建议分三步展开。第一步说清楚缓存什么热门歌曲的播放地址、歌曲元数据、用户播放记录。第二步说清楚缓存放在哪本地内存缓存、分布式缓存集群、CDN边缘缓存。第三步说清楚缓存失效怎么处理缓存穿透时用布隆过滤器拦截不存在的数据缓存击穿时用互斥锁或逻辑过期避免热点key直接打到DB缓存雪崩时给过期时间加随机值防止大量key同时过期。在分布式存储部分一致性哈希是高频考点。为什么不能直接用取模做分片因为节点增减时会导致大量key重新映射一致性哈希通过哈希环让每次增减影响控制在相邻节点范围内同时通过虚拟节点让数据分布更均匀。这个知识点几乎成了服务端设计题的通关口诀但你要能解释出环形哈希空间的构建过程、节点查找路径以及虚拟节点如何解决数据倾斜才算真正掌握。5.3 面试官追问的套路从单机到集群再到跨机房笔试虽然不能像面试那样交互式追问设计题里也会通过多小问的方式模拟这个递进过程。常见套路是先问单机如何实现再问如果QPS上来了如何扩展最后问跨机房部署怎么保证可用性。我当时在做下载系统设计题时也按这个层次组织答案。单机阶段重点考虑内存、磁盘、网络消耗集群阶段考虑负载均衡、无状态化、水平扩展跨机房阶段考虑数据同步延迟、故障切换、多活架构。以音乐下载为例下载请求本身就是高带宽操作单机带宽很容易打满所以更需要借助CDN让流量在边缘消化源站只负责更新内容和签发URL。如果两个机房同时提供服务不同机房之间要不要同步用户下载记录如果用户从机房A下载到一半切换到机房B断点续传记录能从B机房拿到吗思考到这一层答题格局就出来了。我记得同场有个同学写的是单机文件存储加定时任务批量处理逻辑对但完全没有体现分布式意识。笔试阅卷时这种答案会被归入“可行但平庸”的档位。你要做的不是给出完美可落地的生产方案而是证明自己思考过系统在边界条件下如何运作。哪怕有些细节不确定也可以用“这里可以通过xx方式解决”来展示思路而不是直接跳过。6. 我自己踩过的坑和一套可复用的备考节奏6.1 时间分配与答题顺序不要死在第一道题上我当年参加笔试时有个很蠢的失误前面有一道关于TCP状态迁移的选择题犹豫了很久反复权衡结果浪费了将近十分钟。其实那道题只有两分选错了也不影响后面的展示。那次之后我总结了笔试时间分配的固定节奏先花5分钟扫卷把题目按“秒杀题、常规题、硬骨头”分成三类先把秒杀题快速做完再按分值从高到低做常规题硬骨头放到最后用剩余时间写想法和关键步骤。笔试题本质是限时博弈不是竞赛满分挑战。如果你想冲击高分更重要的是确保大面积拿分而不是把某一道难题做得尽善尽美。我当时见过一位同学在算法题上写满了一整页逻辑清晰但最后设计题完全空白。这种情况下就算算法题拿满分总分上限也变低了。6.2 手写代码的规范阅卷人一眼就看到的东西手写代码题的隐性评分标准比很多考生想的多。阅卷人第一眼看的是函数签名、变量命名、缩进和注释而不是你的逻辑有多巧妙。因为纸面代码没法运行他们只能通过静态阅读判断你平时写代码的习惯。我建议代码题开头先写一句话的算法思路然后写出函数签名比如public ListNode reverseList(ListNode head)变量命名用有意义的单词不要用a、b、temp敷衍。循环和条件分支的括号对齐要清晰关键步骤旁加一行注释说明意图。写到边界情况时顺手加一个if判断处理空输入然后在注释里写明“处理链表为空或长度为1的边界情况”。这些细节不会增加太多时间但在阅卷人那里的印象分会差出一档。还有一点如果时间紧张写完代码后至少留出两分钟在脑海里跑一个简单用例。很多人写的链表情景题反转前头节点后意外丢了尾节点指针这种问题用一个小测试用例就能发现。笔试时能主动去验证代码正确性本身就是工程素养的体现。6.3 工作后回头看这些题目对应了哪些线上问题我在参加这套笔试后没太当回事直到工作后排查了几次线上故障才发现当年那些题目全变成了生产环境里的真实剧本。TCP的TIME_WAIT问题变成了上线时看到服务端连接数飙升大量请求失败。内存管理的缺页问题变成了JVM堆设置不当导致机器load飙升GC日志里出现大量Full GC。数据库的索引失效问题变成了慢查询直接把主库CPU打满导致整个服务不可用。链表和二叉树那些算法题则无形中训练了我调试复杂数据结构、排查并发环境下状态不一致的能力。所以如果你现在正在准备技术笔试不要焦虑题目偏不偏、难不难。2016年和今天最大的不同是技术栈更迭了但底层原理没有变。一套好的笔试题本质上是在帮助你建立工程师的基本盘算法、操作系统、网络、语言、数据库、系统设计每一块都是在为未来的线上问题做准备。哪怕你最终不进这家公司把类似的试卷认真拆解一遍也远比刷几十道浮于表面的题库更有价值。我自己现在看到任何一套技术笔试题第一反应不是背标准答案而是想它对应生产环境中的哪一类故障以及如果让我重新设计这个系统我会怎么回答。这套思维方式的养成正是当年那场笔试给我的最大财富。