饿了么终端岗笔试复盘:TCP、贪心算法与日志排查全解析
2024年秋招我投的是饿了么终端开发工程师岗。先给还没入行的同学提个醒互联网招聘里的“终端岗”通常指的是跑在手机上的客户端Android、iOS以及像饿了么这种业务规模下必然存在的跨端方案而不是你电脑上那个敲命令的终端窗口。但事情微妙就微妙在笔试真正动笔的时候又处处离不开终端功底Shell、日志排查、命令行工具链甚至编辑器里那个老是报错的终端都可能成为考场上的一道隐性筛选。那场笔试一共两小时在线测评全程开摄像头切屏超过一定次数会被警告。题型有单选、多选、三道编程题和一道简答。整体难度不算“变态”但非常务实题目背景普遍套了本地生活业务的壳比如配送调度、优惠券组合、订单日志解析。如果你以为客户端岗笔试就是纯刷 LeetCode可能会在选择题上栽跟头TCP 状态、内存管理、并发模型这些基础内容占了相当比例。这篇内容我想完整复盘一下那场笔试的流程、高频考点和解题思路也聊聊我后来总结出来的备战方式。不管你是明年才参加秋招还是已经拿到笔试通知正在突击都能从中找到可以直接落地的准备方向。1. 笔试全流程复盘从投递到在线测评的真实体验1.1 岗位投递与笔试节奏我是在2024年8月底通过内推投的简历。饿了么的秋招流程基本是网申、在线笔试、技术面试、HR面。简历投出去之后大概一周收到了笔试链接邮件里写明了需在48小时内完成考试时长两小时。我当时选了周六下午的场次原因是上午可以完整过一遍高频题和计算机网络脑子比较清醒。这里有一个容易被忽略的细节在线笔试千万别掐着最后一小时才开始很多人在群里反馈说中途会遇到摄像头校验失败、浏览器弹窗拦截等问题。我考前提前半小时到电脑前把浏览器缓存清了一遍关闭所有无关应用还专门检查了麦克风和摄像头权限。事实证明准备工作没白做笔试开始后同场有人因为浏览器权限问题被卡了五分钟那种情况下心态很容易崩。考试平台用的是常见的牛客网支持 Java、C、Python、Go 等主流语言。我选的是 Java因为平时客户端开发用得多而且 Java 在算法题写起来比 C 少一些边界上的心智负担。进入考场后页面会有一个在线编辑器左边题目、右边代码区代码区可以本地跑测试用例但不能访问外部网络。这个环境还是要提前适应的尤其是平时用惯了 IntelliJ IDEA 补全功能的人冷启动写代码手感会差不少。1.2 题型分布与时间分配如果用一个词总结饿了么这批笔试的基调就是务实。题型包括单选、多选、编程题和简答题具体分布和我的建议用时可以看下面这张表。题型题量建议用时主要考察方向单选10题左右15分钟计算机网络、操作系统、数据结构多选5题左右10分钟客户端专项、并发、内存、网络细节编程题3题90分钟贪心、动态规划、堆与哈希表简答题1题15分钟线上问题定位、系统设计意识选择题整体不算偏但多选的杀伤力很大因为多选、少选、错选都不得分。客户端岗位的多选尤其爱考“下列哪些方案可以降低卡顿”“哪些因素会导致 App 启动变慢”这类实际工程中需要综合考虑的题光靠背八股文很难拿全分得真正理解性能优化里每个方案背后的代价。时间分配是我笔试完觉得最值得说的一点。我身边的同学经常犯一个错误在选择题上花太多时间纠结结果编程题只剩四十分钟。我的策略是选择题快速过超过90秒没有把握的题先标记跳过等编程题做完再回头补。当时三道编程题我大概用了六十分钟剩下的时间用来查漏补缺和写简答题整体节奏比较从容。1.3 和普通后端笔试的差异我也做过几家互联网公司的后端笔试对比下来饿了么终端岗笔试最明显的差异是它不追求让你写出一个完美的红黑树而是特别看重“能不能用工程化思维解决一个具体问题”。比如编程题里的配送场景本质是经典贪心但套了真实业务壳之后很多人第一反应是去设计状态机反而把简单的思路想复杂了。另一个差异是简答题。后端笔试的简答常问“如何设计一个短链系统”终端岗则更倾向于问“线上用户反馈首页白屏率高你怎么排查”。这种题没有标准答案但很能看出一个人日常有没有真的处理过线上问题。我当时写的是先看崩溃报表和ANR日志再看网络成功率接着用抓包工具确认接口返回最后用终端抓取日志复现现场再结合不同机型、系统版本做聚合分析。这题我虽然没有标准答案但我把完整排查链路写出来了踩分点基本都能覆盖到。2. 选择题背后的基本功网络、系统、内存、并发2.1 从一道 TCP 状态题说起选择题里有一道题让我印象很深大意是服务端主动关闭一个 TCP 连接在四次挥手过程中服务端会依次经历哪些状态。选项里有 FIN_WAIT_1、CLOSE_WAIT、LAST_ACK、TIME_WAIT 这些状态混在一起。如果对 TCP 状态机不够熟很容易被绕进去。这里理一下主动关闭方先发送 FIN进入 FIN_WAIT_1收到对端 ACK 后进入 FIN_WAIT_2被动关闭方收到 FIN 后回复 ACK并进入 CLOSE_WAIT等自己的数据发送完毕后再发 FIN 给对方然后进入 LAST_ACK主动关闭方收到这个 FIN 后回复 ACK并进入 TIME_WAIT等待 2MSL 后关闭。所以服务端如果是主动关闭一方路径是 FIN_WAIT_1 - FIN_WAIT_2 - TIME_WAIT如果是被动关闭一方路径是 CLOSE_WAIT - LAST_ACK。这道题对客户端开发的意义不只是应付笔试。客户端和服务器之间的长连接非常常见推送服务、IM 消息、订单状态同步都依赖它。如果服务端频繁主动断开连接客户端这边会出现大量的 TIME_WAIT 连接导致端口资源被占用。实际排查线上问题时很多“消息收不到”的反馈最后都能在 TCP 连接状态上找到线索。2.2 内存与并发客户端高频重灾区多选里有一道关于内存抖动的题问哪些操作容易造成内存抖动。选项有在循环里创建大量短生命周期对象、频繁触发 GC、使用 StringBuilder 拼接长字符串、用 Matrix 等工具检测。前三个基本是送分项核心就是在 Java 层避免循环内创建对象尤其是 Android 的 ListView / RecyclerView 在滑动时会反复执行 onBindViewHolder如果这里 new 了太多对象GC 频率一高掉帧是必然的。还有一道并发题问死锁的四个必要条件这个也是老八股了互斥、持有并等待、不可剥夺、循环等待。但饿了么的题目会拐个弯问“下列哪种方式不能避免死锁”选项里可能包含“加大锁的粒度”。这个就是典型的迷惑项因为加锁粒度变大反而会增加锁竞争和等待不是避免死锁的手段只能减少“同时持有多个锁”的场景。客户端并发里我还想多说一句 Handler/Looper 机制。多选里有一道题是“在主线程里执行耗时操作会导致什么”除了ANR之外还可能引发界面卡顿、掉帧、系统回收等问题。但如果你只是背答案不理解 Handler 机制里的消息队列和 Looper 循环面试深挖时就会露馅。笔试结束后我重新画了一遍 Handler 工作机制的流程图才发现以前很多理解都是模糊的。2.3 网络与体验HTTP/HTTPS、弱网与缓存选择题里关于 HTTP 的题也有好几道主要集中在 HTTP/1.1 和 HTTP/2 的区别、HTTPS 握手流程、缓存头 Cache-Control 的作用。HTTP/2 的多路复用和头部压缩是高频考点很多人在“多路复用会不会导致队头阻塞”上卡住答案是 TCP 层面的队头阻塞依然存在因为底层还是 TCP丢包时整个连接的数据都会受影响。有一道题让我觉得饿了么很懂业务在一个弱网环境下用户下单请求超时客户端立刻重试可能导致什么后果。选项包括“服务端创建多个重复订单”“接口幂等性问题”“增加服务端压力”等。这道题考察的是对业务幂等和客户端重试策略的理解。真实场景里订单创建接口必须有幂等键客户端也要做指数退避重试否则秒杀场景下服务端很容易被重试流量打垮。缓存相关题目也值得展开。很多人只知道 Service Worker 或者 HTTP 缓存能加速但笔试考的是细节Cache-Control: no-cache 并不是“不缓存”而是“使用缓存前必须先校验资源是否新鲜”真正禁用缓存的是 no-store。这种细节在客户端排查资源更新失败时非常有用比如发版后用户还看到旧页面多半就是缓存头配置不对。3. 三道编程题的还原与解题思路这部分我要先说明一下以下题目是我根据同类型考点和记忆整理的近似题不是原卷内容。但出题思路和考察点是一致的可以当原题对待来准备。3.1 配送场景的贪心最多能完成多少订单第一题考的是一个带截止时间的任务调度问题题目大意是骑手手上有若干订单每个订单有一个预计制作时间和期望送达时间骑手同一时间只能送一单请问最多能完成多少个订单。这题我第一次看到时有点慌因为“订单制作时间”和“期望送达时间”两个维度混在一起我下意识以为是区间调度。后来冷静下来发现它其实是一个经典贪心先把所有订单按期望送达时间排序然后依次处理用一个最大堆维护已经接下的订单所需时间。当处理到某一个订单时如果当前累计时间超过这个订单的期望送达时间就把所有已接订单里耗时最长的一单放弃这样能让后续订单有更多时间余量。核心代码大致是这样的static class Order { int time; // 预计制作和配送耗时 int deadline; // 期望送达时间 } int maxOrders(Order[] orders) { Arrays.sort(orders, (a, b) - a.deadline - b.deadline); PriorityQueueInteger heap new PriorityQueue((a, b) - b - a); int total 0; for (Order o : orders) { total o.time; heap.offer(o.time); if (total o.deadline) { total - heap.poll(); } } return heap.size(); }这个做法的正确性在于按截止时间早的优先处理能保证决策空间最大当超时的时候去掉耗时最长的订单损失最小。时间复杂度是 O(n log n)排序加堆调整完全能过笔试。这道题给我的启发是本地生活场景里的调度问题表面千变万化底层大量是“排序 堆”的贪心模型。准备秋招时这类题一定要刷熟。3.2 优惠券组合支付背包变体第二题是优惠券相关给一个订单金额 M以及若干张优惠券的抵扣金额要求选出一组优惠券使得抵扣金额尽可能大但不能超过订单金额最终输出最低实付金额。题目里的优惠券没有门槛且可以任意组合。这题本质上是一个 0/1 背包。可以把“能不能凑出某个抵扣金额”作为状态用一个布尔数组 dp 标记当前金额能否被凑出。遍历每张券时从后往前更新避免一张券被重复使用。最后从订单金额往下找第一个能被凑出的抵扣金额就是最大优惠。示例代码如下double minPay(int[] coupons, double orderAmount) { int target (int) Math.round(orderAmount * 100); // 金额转成分避免浮点数误差 boolean[] dp new boolean[target 1]; dp[0] true; for (int c : coupons) { int value (int) Math.round(c * 100); for (int j target; j value; j--) { dp[j] dp[j] || dp[j - value]; } } int discount 0; for (int j target; j 0; j--) { if (dp[j]) { discount j; break; } } return Math.max(1, orderAmount - discount / 100.0); }这里我想重点提醒一句涉及金额的题先把元转成分再用整数算。浮点运算在笔试里可能不会立刻报错但如果你在线上系统里用 double 存金额总有一天会因为精度问题出事故。笔试时的代码虽然只跑用例但良好的工程习惯是可以被面试官看见的。另外优惠券叠加在真实业务里远比这个复杂可能存在门槛、不可叠加券、互斥券。笔试考的是建模能力你不需要真的懂业务规则只需要把它抽象成一个背包问题就行。3.3 大文件日志解析TopK 与内存边界第三题和终端的关系很大给定一个很大的日志文件每行记录一条错误日志格式大概是时间戳、错误码、错误信息要求统计出现次数最多的K个错误码并输出错误码和次数。文件大小可能超过内存容量不能一次性读入。这题显然在考察哈希表和堆。第一步用 BufferedReader 按行读取文件用 HashMap 统计每个错误码出现次数第二步用一个小顶堆维护当前出现次数最多的 K 个错误码堆顶是第 K 大的元素。当堆的大小超过 K 时弹出堆顶。这样内存占用被限制在 O(K)不会随文件无限增长。public ListString topErrorCodes(String filePath, int k) throws IOException { MapString, Integer countMap new HashMap(); try (BufferedReader reader new BufferedReader(new FileReader(filePath), 1 20)) { String line; while ((line reader.readLine()) ! null) { String code extractErrorCode(line); if (code ! null) { countMap.merge(code, 1, Integer::sum); } } } PriorityQueueMap.EntryString, Integer heap new PriorityQueue( (a, b) - a.getValue() - b.getValue() ); for (Map.EntryString, Integer entry : countMap.entrySet()) { heap.offer(entry); if (heap.size() k) { heap.poll(); } } ListString result new ArrayList(); while (!heap.isEmpty()) { result.add(heap.poll().getKey()); } Collections.reverse(result); return result; }这道题让我意识到客户端开发日常工作里大量跟日志打交道但很多人只会用 IDE 图形化工具打开日志文件。文件一旦超过几百 MB任何编辑器都会卡死真正高效的思路是先用 grep、awk、sort 等命令在终端里做粗筛再对结果做统计。笔试虽然不会让你真的去敲 Shell但背后的“数据规模意识”和“内存边界意识”是它真正想考的。4. 终端环境与工具链笔试之外的隐形考点4.1 为什么客户端笔试也离不开终端常识可能有人会问终端开发岗又不做后端为什么要懂终端命令其实互联网公司里的客户端开发每天都要跟构建系统、CI/CD、日志平台打交道。饿了么这种体量的 App发版流程一定不是“在 Android Studio 里点一下 Run”而是完整的流水线代码合并、单元测试、构建、签名、多渠道打包、灰度发布、监控回滚。这条链路上每一个环节都离不开命令行工具。笔试虽然没有直接考 Shell 命令但编程题里的“日志文件 TopK”就是终端场景的抽象。如果你平时用过grep -oE error_code[0-9] app.log | sort | uniq -c | sort -rn | head -10这串命令对这道题的理解会快很多因为你已经知道统计 TopK 的完整套路是什么了。4.2 那些年我们一起踩过的终端工具坑这里必须讲讲我实际踩过的终端工具坑尤其是 Windows 环境下使用 VSCode 时遇到的一个经典报错“终端进程启动失败: 启动期间发生本机异常无法启动 conpty已移除 winpty退出代码 -1”。这个问题的本质是 VSCode 内置终端依赖 Windows 的 ConPTY 接口当系统本身不支持、VSCode 版本过旧或者配置里残留了旧版 winpty 相关设置时终端进程就会崩溃。我当时的解决办法是打开 VSCode 的 settings.json 文件把已废弃的terminal.integrated.shell.windows配置项删掉改为terminal.integrated.defaultProfile.windows并指定为Command Prompt或PowerShell。如果还不行就把 VSCode 升级到最新版本并在系统设置里打开“开发者模式”。这个问题看似琐碎但在笔试或面试现场如果处理不好会直接影响你调试代码的时间。另一个非常常见的问题是在 PowerShell 里执行 npm 命令时报错“npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。”这其实不是 npm 坏了而是 PowerShell 的执行策略默认禁用了脚本运行。最简单的方式是换用 CMD 或 Git Bash 执行如果想继续用 PowerShell可以运行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser执行完再重新打开终端就正常了。这类问题本质上属于“开发环境的自我维护能力”很多笔试候选人在项目介绍里说自己会写脚本但基本命令都跑不起来面试官很难相信你真的独立完成过项目。再聊聊终端复用。tmux 是我个人非常依赖的工具。在同时处理多个任务时比如一边启动后端服务、一边看日志、一边写代码终端复用能省下大量来回切换的精力。tmux 里常用的会话管理命令比如tmux new -s mysession创建会话Ctrlb d分离会话tmux attach -t mysession重新连接这套操作长期用下来会变成肌肉记忆。如果你经常通过 SSH 登录开发机tmux 更是刚需因为网络一断没有 tmux 的进程可能就跟着断了。日常使用的终端工具里Tabby 和 iTerm2 也值得提一下。Tabby 是跨平台的Windows 和 Linux 都能用支持配置同步、主题、SFTP 管理界面也比 Windows 自带的终端好看不少。iTerm2 则是 macOS 上的老牌神器它的分屏和搜索体验很顺滑。笔试不考这些但面试聊到开发效率时随口说一句“我在终端里用 tmux 管理多会话用 Tabby 同步配置”是能给整体印象加分的。最近 AI 编程工具也很火我在准备笔试期间用过 Codex 和 Claude Code 的终端版本。Codex 有一个常见问题就是它说“对话没启用终端工具”实际上是因为当前会话没有被授予工具调用权限需要在授权时允许它执行命令或者重新开启一个启用了终端工具的新会话。Claude Code 也有类似的权限交互逻辑。这类工具在笔试准备阶段对我帮助很大比如快速解释一段看不懂的复杂日志或者生成一个排序算法的模板但前提是你自己得能看懂它给出的命令否则出了生产事故都不知道从哪开始修。4.3 日常怎么练终端从会用到底层明白想要把终端能力练起来不用刻意背命令最好的方式是把日常操作迁到终端里。我给自己定过几个小任务用find加xargs批量重命名文件用grep -C 5查看日志关键字前后的上下文用sed替换配置文件里的某个字段用tar和ssh打包并下载线上日志用curl调试接口的 GET 和 POST 请求在终端里启动 Jupyter Notebook、启动后端开发服务。这些任务做完一遍你会比死记十条命令理解得深得多。还有一个很容易被忽略的点终端不只是“敲命令”的工具它反应了你对操作系统、进程、文件系统的理解。比如启动一个服务后怎么查看它是否真的跑起来了用ps -ef | grep java能看到进程用lsof -i :8080能看到端口占用用kill -15优雅关闭进程。这一整套操作背后是信号、文件描述符、进程模型这些操作系统知识。把这些串起来了选择题里那些关于进程、端口、文件句柄的题也就不难了。5. 分数之外的复盘笔试真正想筛选什么人5.1 从笔试题反推岗位画像笔试结束不等于事情结束我习惯把每场笔试当作一次免费的能力体检。饿了么这套题做完我最大的感受是它想筛选的不是“题库刷得最多的人”而是“基础扎实、有工程意识、能解决实际问题的人”。这个判断可以从题目结构里看出来。选择题里占比最大的不是冷门八股而是网络、内存、并发这些客户端日常开发一定会碰到的东西编程题的三道题分别对应贪心、动态规划和堆与哈希表都是高频实用算法简答题则直接考察线上问题排查链路。如果你只是会背答案但从未真正处理过线上问题简答题一定能看出来。反之如果你平时有写技术笔记、复盘线上事故的习惯这套题会做得比较顺畅。我后来跟拿到面试机会的同学交流发现他们有一个共同点简历上有能讲清楚的项目笔试题里也能写出“思路清晰、边界处理完整”的代码。笔试成绩可能不会直接决定你能不能进面试但它会成为面试官提问的素材。你编程题里留下的变量名、注释、甚至时间复杂度的描述面试官都看得到。5.2 我的秋招备战清单如果你现在才开始准备不用慌我把自己当时一直用的备战清单整理到这里按优先级排的计算机网络和操作系统再过一遍重点TCP/UDP 状态机、HTTPS 握手、进程线程、死锁、内存管理、虚拟内存。LeetCode 刷熟高频题贪心、动态规划、二叉树、堆、哈希表、双指针、链表。客户端专项Handler/Looper、事件分发、布局渲染、卡顿优化、内存泄漏、启动优化、包体积优化。Linux 和终端命令grep、awk、sed、find、xargs、ps、lsof、netstat、ssh、scp、tmux。写技术笔记每解决一个线上问题把排查过程、根因、解决方案记录成文档面试和笔试的简答题都能用上。参加社区和开源项目我花两周时间读了一个开源客户端库的源码期间用终端跑通了整个项目的构建和测试收获比刷一百道题都大。这个清单看起来很多但秋招战线其实很长每天保证两到三小时的有效学习两个月下来覆盖所有考点完全来得及。5.3 笔试当天的策略与非技术建议最后聊聊笔试当天的策略。我要强调一个常被忽略的点编程题不要求“一次运行通过”但要求“思路能看”。我当时的习惯是拿到一道题先不急着写代码在代码区顶部用注释把思路写清楚比如“先按截止时间排序再用最大堆维护选中订单”然后才开始写。这样即使代码有小 Bug面试官也能看到你的解题方向是对的。时间分配上我给自己定了一个规则选择题超过90秒没思路就标记跳过编程题超过15分钟没有完整思路就先做下一道最后留10分钟回来补。笔试本质是在资源受限的情况下做取舍把时间花在能拿分的题上才是最优策略。我在准备那轮秋招时还把终端里的 shell 配置临时加了一段提示语内容是“先审题再写注释最后写代码选择题别超过90秒”。听起来很傻但它真的帮我稳住了心态尤其在遇到高压的限时考试时这种“自我提醒”比临时翻书管用得多。秋招这个过程拼的不只是知识量还有在有限时间内把知识调动出来的能力。希望这篇复盘能让你对饿了么终端岗笔试有具体的感知也能在准备其他大厂客户端岗位时少走一点弯路。