
那年秋招我在很长一段时间里都在投客户端开发方向的岗位。点我达2019届校招这场笔试是我印象比较深的一次。倒不是说题目有多偏多难而是它的考察范围非常典型几乎把客户端开发校招笔试该覆盖的考点都过了一遍兼具广度和深度。如果你正准备客户端开发相关岗位的校招笔试这篇文章可以当作一份复盘笔记来看我会把当时这场笔试的题型分布、核心考点、易错点和编程题的解题思路都拆开讲清楚也会聊一些在普通面经里不太会写到的临场细节。先说结论这类笔试整体难度属于中上但真正拉开差距的往往不是最后那道压轴算法题而是前面选择题里那些“好像会、一选就错”的基础概念。基础不牢后面编程题写得再顺手总分也可能被拖下去。1. 笔试题型拆解一份典型的客户端开发笔试拿到手先看什么1.1 题型分布与分值结构我参加的那场笔试是线上限时答题总共120分钟题量不算小。整体分为三个部分客观题单选多选、简答题、编程题。具体的分值分布大概是这样的题型数量分值占比考察侧重单选20题左右约30%数据结构、操作系统、网络、语言基础多选10题左右约15%概念辨析、边界条件、易混淆知识点简答2~3题约15%客户端机制理解、方案设计编程2题约40%算法实现、代码规范性、复杂度意识客观题一共占了近一半的分数这其实是非常常见的校招笔试结构。很多准备笔试的同学容易把精力全压在编程题上进了考场才发现前面那些选择题做起来并不轻松——每个选项都在“对与不对”的边缘反复横跳特别是多选题少选漏选都拿不到分。建议拿到试卷后先快速浏览一遍所有题目对编程题的难度心里有个数然后倒过来从客观题开始逐项推进。不要一上来就死磕某道选择题任何一道题超过三分钟没有明确思路先标记跳过。1.2 为什么笔试要考这些内容作为客户端开发岗位的候选人你得先想清楚一个问题公司为什么要在笔试环节考这些内容点我达这类业务形态的公司核心产品是一个高频使用的移动应用一端连着配送员一端连着商户和用户。客户端开发工程师要面对的远不只是“写页面”这件事——地图定位的持续回调、订单状态的实时推送、弱网环境下的接口请求重试、长列表的滑动性能、App崩溃率的控制每一样都是真实的线上问题。所以笔试不会只考API怎么调用它会通过基础题看你有没有扎实的计算机功底通过客户端机制题看你是不是真懂Android或iOS的底层运行原理通过编程题看你能不能写出健壮、高效、可维护的代码。换句话说笔试筛选的不只是“会写代码的人”而是“能在这个业务场景里把代码写稳的人”。理解到这一层你在准备笔试时的侧重点就会不一样单纯刷LeetCode不够还得把操作系统、网络、客户端机制这些“地基”补牢。2. 核心考点深度解析高频知识点与出题意图2.1 数据结构与算法不只是刷题数量的问题这部分是客观题和编程题的重叠区。客观题里考察的数据结构知识点主要集中在数组和链表的操作复杂度对比、栈和队列的应用场景、二叉树的遍历方式、哈希表的冲突处理、排序算法的稳定性和复杂度。其中“排序算法在不同场景下的选择”是出现频率非常高的考点。比如题目会给出一组近乎有序的数据问你用哪种排序效率最高——这就是在考察插入排序对近乎有序数据集的适应能力而不是让你直接背快排的时间复杂度。只有真正理解每种排序的“数据敏感度”现场才能快速判断。算法题的考察方向则集中在字符串处理、链表操作、二叉树相关的递归、动态规划入门级问题以及一些基于业务场景的模拟题。点我达的业务和LBS强相关所以笔试里出现过类似“计算两个坐标点之间的距离”“按照距离对一批点排序”这类题目。这类题本身不复杂但它提醒你准备客户端笔试时几何计算和空间数据处理的基本思路要有一点概念这些知识在真实业务里很常用。字符串处理的题是校招笔试的常客几乎不会缺席。常见的有字符串去重、字符统计、最长公共前缀、反转字符串等。这类题难度不大但非常考验编码基本功边界条件处理、空指针判断、字符编码的处理、时间复杂度的控制全是踩分点。我的建议是字符串相关的题型不要只看解题思路一定要自己手写一遍完整代码因为这类题最容易出现“思路对、代码编译不过”的尴尬局面。2.2 操作系统与计算机网络客户端开发绕不开的两座山很多科班同学在大学里学过操作系统和计算机网络但备考客户端岗位时容易轻视这两门课觉得“客户端开发用不到那么底层的东西”。这个想法在校招笔试里会很吃亏。操作系统的高频考点包括进程和线程的区别、线程同步的几种方式互斥锁、信号量、条件变量、死锁产生的四个必要条件、虚拟内存和页面置换算法、进程间通信的方式。选择题往往会给出几个容易混淆的说法让你判断正误比如“线程是资源分配的基本单位进程是调度的基本单位”这种经典的错误表述。而简答题可能会让你设计一个“多线程下载”的方案任务怎么拆、线程池大小怎么定、下载失败怎么重试、多个线程写同一个文件如何保证数据不冲突。这已经不只是考概念而是考你能否用操作系统知识解决实际工程问题。计算机网络的重点则集中在TCP和UDP的区别、TCP三次握手和四次挥手的过程、HTTP和HTTPS的差异、HTTP常见状态码的含义、DNS解析过程。由于客户端的主要工作是网络请求的发送和数据的解析所以HTTP协议相关的内容考察得非常细。比如“HTTP的长连接和短连接分别适用于什么场景”“TCP拥塞控制在弱网环境下对App请求有什么影响”这些都属于客户端开发的高频面试题在笔试里同样会出现。准备这部分内容推荐拿着Charles或Wireshark实际抓包看几次请求和响应把协议头字段对应到真实流量上理解深度会好很多。2.3 客户端平台基础Android与iOS的核心机制这部分是真正区分“认真准备过客户端开发”和“海投碰运气”的考点的环节。如果投的是Android方向Activity生命周期是必考内容而且往往会结合场景出题比如屏幕旋转时Activity会经历哪些回调、A页面跳转到B页面时两个页面的生命周期回调顺序是什么、App退到后台再回来时会发生什么。这些看似基础但细节非常多不同场景下的执行顺序很容易混淆。我的复习方法是把几个常见场景的生命周期调用顺序自己手写一遍整理成表格反复对照直到完全形成条件反射。Android的消息机制、Handler和Looper的关系、主线程和子线程的通信方式也是考察重点。还有内存优化相关的内容内存泄漏的常见场景Handler持有Activity引用、静态Context引用、未注销的BroadcastReceiver、内存抖动和GC机制、ANR的原因分析和避免方式。这些都是客户端开发日常工作中一定会遇到的问题笔试考察它们非常合理。如果投的是iOS方向运行时机制、消息传递机制、内存管理引用计数、循环引用、多线程方案GCD、NSOperation是核心考察点。因为点我达的客户端是Android和iOS双端都在做笔试主体虽然以Android为主但也出现了少量iOS相关题目。我的建议是主攻一端但对另一端的基本概念至少做到“听说过、不陌生”避免出现整道题完全看不懂的情况。2.4 语言基础Java是Android开发的根虽然现在Kotlin在Android开发中的占比越来越高但很多笔试仍然默认考察Java基础。比如String、StringBuilder、StringBuffer的区别HashMap的底层实现原理数组链表红黑树ArrayList和LinkedList的性能对比Java内存区域的划分堆、栈、方法区、本地方法栈、程序计数器GC回收机制和常见的垃圾收集器多线程相关的synchronized、volatile、ThreadLocal等。这些知识点几乎每一场客户端笔试都会出现属于“送分题”和“送命题”之间的模糊地带——真正理解了是送分半懂不懂就是送命。还有一个小众但值得关注的知识点反射和注解。客观题里可能会问你“反射机制的性能问题”“注解的保留策略有哪些”这其实是在考察你对框架底层原理的了解程度。现在的Android开发大量依赖注解和反射框架比如ButterKnife、EventBus笔试里出现相关题目并不意外。3. 编程题实战解析手写代码的思路与细节3.1 字符串去重与排序一道典型的“基本功”题编程题中出现过一道这样的题目给定一个字符串去除其中重复的字符并按照字符ASCII码升序排序输出处理后的字符串。这道题LeetCode上有很多变体核心考察点在于你能否用合理的数据结构在O(n)时间内完成去重再以较小的空间开销完成排序。思路并不复杂用一个长度为256的布尔数组记录字符是否出现过第一遍遍历标记由于ASCII码本身有序第二次遍历时按数组顺序输出即可。这里有个容易出错的地方如果你只用一个HashSet去重后再转换成数组排序整体复杂度会变成O(n log n)在大数据量下可能超时而用布尔数组的方式可以把排序步骤也变成O(1)的常数时间。代码可以这样写public String removeDuplicateAndSort(String s) { if (s null || s.length() 0) { return ; } boolean[] seen new boolean[256]; for (int i 0; i s.length(); i) { seen[s.charAt(i)] true; } StringBuilder sb new StringBuilder(); for (int i 0; i 256; i) { if (seen[i]) { sb.append((char) i); } } return sb.toString(); }这道题想清楚了其实不难但我在笔试现场发现不少人在“排序”这一步绕了远路还有人忘了处理空字符串和null输入。面试官看的不只是你能不能解出来还看你有没有防御式编程的意识。写完代码之后多问自己一句如果输入是空串怎么办如果字符包含中文怎么办如果字符串特别长怎么办这些问题想清楚代码质量会明显上一个台阶。3.2 链表反转高频中的高频链表反转是客户端笔试中出现频率极高的题目没有之一。点我达的笔试题里同样出现了。题目本身很经典给定一个单链表的头节点将链表反转返回新链表的头节点。解题思路有三种迭代法、递归法、头插法。迭代法最直观用一个prev指针记录前驱节点一个curr指针遍历当前节点每次迭代时先保存下一个节点再把当前节点的next指向前驱节点。代码大概长这样public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode nextTemp curr.next; curr.next prev; prev curr; curr nextTemp; } return prev; }这道题真正坑人的地方不在基本反转而在进阶变体反转链表的第m到第n个节点。点我达的笔试编程题里有一道就是这种变体需要先找到第m-1个节点然后反转中间的n-m1个节点最后把三段链表拼接起来。这里非常考验你对指针操作的熟练度稍不留神就会在拼接顺序上出错。我的建议是做链表相关题目时一定要在纸上画图把指针的指向变化一步一步画出来不要直接在脑子里做指针操作。3.3 场景模拟题订单列表分页加载出了数据结构题编程题的第二道往往是一道业务场景模拟题。这类题目不会直接告诉你“要用动态规划”或者“要用贪心算法”而是给你一个真实业务场景让你自己提炼问题、设计方案。我当时遇到的一道题大致是这样有一个订单列表接口服务端支持分页返回数据每页最多返回20条客户端需要实现一个加载函数。给定一个订单ID数组代表当前已有的订单再给定一个整数n代表需要加载的条数要求返回需要请求的页码列表页码从1开始。如果加载过程中遇到已存在的订单需要跳过继续加载直到凑够n条为止。这道题本质上是考察分页逻辑的边界处理和对“已有数据”的理解。解决思路的关键点在于当某一页的20条订单全部已存在时不能只跳过这一页就结束要继续请求下一页直到凑满n条新订单。这里非常容易出错很多人会忽略“整页都重复”的情况导致返回结果不足n条。我给的建议是场景模拟题一般没有唯一答案但你一定要在代码里体现出对边界条件的周全考虑。写完代码后主动用几个测试用例验证一下n为0时、已有订单数为0时、某一页全部重复时、最后一页不足20条时。把这些情况都想到了代码才算是合格的。4. 客观题高频易错点与排查技巧这些坑你必须提前踩过4.1 易混淆知识点对照表备考客观题时最让人头疼的就是那些“看起来差不多实际上差很多”的知识点。我整理了几个高频易错点的对照表非常适合在考前快速过一遍易混淆点正确理解常见错误HashMap vs HashtableHashMap允许null键和null值线程不安全Hashtable不允许null线程安全但性能差以为HashMap线程安全TCP vs UDPTCP面向连接、可靠、有序UDP无连接、不可靠、高效认为UDP比TCP更稳定Activity的onStart和onResumeonStart表示页面可见但不可交互onResume表示可交互将两者混为一谈进程 vs 线程进程是资源分配的基本单位线程是CPU调度的基本单位互换两者的定义static方法能否被重写static方法可以被隐藏但不能被重写认为static方法可以正常重写String的equals和比较引用地址equals比较内容String重写过用比较字符串内容数组和链表的区别数组随机访问快、插入删除慢链表反之忽略数组扩容的开销这类题目没有太多捷径只能靠反复记忆和刷题巩固。但有一点值得注意不要只看答案要理解每个选项“为什么对”和“为什么错”。因为多选题经常把几个容易混淆的知识点放在一起你只有真正理解它们的区别才能在多个选项中做出准确判断。4.2 多选题的答题策略多选题是校招笔试里失分最严重的题型很多同学不是不会而是不敢选。我的经验是多选题的计分规则一般是多选、少选、错选都不得分所以如果你对某个选项只有五六成的把握建议不选。虽然这样拿不到满分但至少有机会拿到部分分数——有些笔试的计分规则是按照正确选项数量给部分分的少选比错选强。另外多选题的选项设计往往有一个规律四个选项中至少有一个是“明显正确”的至少有一个是“明显错误”的剩下的两个属于“边缘选项”。先把明显正确和明显错误的选项标记出来对边缘选项做分析和判断而不是凭感觉选。这个方法听起来没什么技术含量但在高压的笔试环境下非常实用能有效降低失误率。4.3 编程题提交前的自查清单编程题写完之后千万不要急着提交。我在实际笔试中总结了一个自查清单每次提交前都强制自己过一遍第一检查输入的边界情况空数组、空字符串、null、长度为1的数组、最大值和最小值。第二检查主要变量在循环中的更新逻辑避免出现死循环或数组越界。第三检查时间复杂度和空间复杂度是否满足题目约束如果数据量是10^5级别O(n^2)的算法大概率会超时。第四检查是否有输出格式要求有些题目要求输出顺序、换行、大小写等细节。第五如果时间允许在本地或脑海中选择一个能覆盖边界条件的测试用例手动运行一遍代码逻辑。这个流程看起来会多花几分钟但对正确率的提升非常明显。校招笔试的编程题往往不是一道题定胜负而是几道题的综合得分决定你是否进入下一轮。确保已提交的题目稳定得分比冒险冲击难题更重要。5. 时间分配与答题顺序决定最终成绩的关键细节5.1 分阶段的答题时间规划120分钟的笔试时间说长不长说短不短。如果在前面的客观题上耗时太久后面编程题就只能草草收场如果飞速掠过客观题又容易在一些基础概念题上白白丢分。我自己的时间分配方案是前60分钟做完全部客观题和简答题留下60分钟给编程题。如果客观题做得顺利还能把节省下来的时间匀给编程题做更充分的测试。具体到每道题的时间控制单选题平均每题不超过1.5分钟多选题每题不超过2.5分钟简答题每题不超过10分钟。如果在某道题上超过这个时间还没有明确思路立即标记并跳到下一题。笔试系统一般支持题号跳转回头再看完全来得及。很多同学在时间压力下容易陷入“不甘心跳过”的心理状态但这在笔试里是大忌。一道两分的选择题花十分钟死磕就算做对了从收益上看也是亏的。5.2 简答题的答题结构与表达技巧简答题是容易被忽视的失分点。这类题目往往考察你对某个机制或方案的理解深度比如“请说明Android中Handler机制的工作原理”“如何优化App的启动速度”。很多同学的答案是“想到哪写到哪”缺乏结构导致阅卷人难以快速抓到你的核心观点。我在分享一个比较实用的答题框架先给出核心结论再展开分点解释最后补充一个实际案例或踩坑经验。比如回答“如何优化App启动速度”可以先说“启动优化主要从三个方面入手减少主线程任务、延迟初始化非必要组件、优化布局加载”然后分别展开解释具体手段最后结合自己项目中遇到的问题举一个实例。这样的回答结构清晰信息密度高阅卷人一眼就能看出你有实际项目经验而不是在背面经。简答题写得有层次感实际是一种成本极低的加分方式。它不需要你掌握额外的知识只需要你把已经会的知识点组织得更清晰。5.3 编程题的取舍原则编程题通常有两道分值相同。遇到两道题难度不均的情况时我的建议是先做自己更有把握的那一道把确定性分数先拿到手。不要在偏难的那道题上花掉全部时间最后两道题都没做完。这个取舍原则在笔试界有一个很形象的类比这就像搞开发时做技术选型稳定可用的方案优先级永远高于花哨但不成熟的方案。校招笔试不是竞赛它的目的是筛选出基础扎实、代码能力合格的人而不是寻找能在45分钟内做出算法难题的天才。把自己会的题目做对、做完整、做得干净已经能超过大部分候选人了。6. 笔试后的复盘与下一步准备6.1 笔试后的复盘方法笔试结束并不代表整个流程结束。无论结果如何认真复盘这场笔试都是提高自己竞争力的关键一步。复盘不是“对一下答案看看自己得了多少分”而是要追溯每道错题背后的知识盲区。我在每一次笔试后都会做一件事把客观题中所有拿不准的选项全部整理到错题本里无论这道题最终是否答对。因为“拿不准但蒙对了”和“做错了”本质上没有区别都说明对应的知识点没有彻底掌握。对编程题复盘时要把自己的解法改写一遍并尝试用更优的思路重新实现。比如一道用双循环暴力解决的题复盘时能否用HashMap优化到O(n)一道用递归解决的题复盘时能否用迭代实现来规避栈溢出的风险这种“一题多解”的复盘方式对算法能力的提升效果立竿见影。我认识的一个朋友就是靠三轮笔试复盘把算法题的解题速度提升了将近一倍。6.2 为后续面试做知识储备笔试只是校招的第一关通过笔试后还有面试环节。在准备面试时笔试中出现过的知识点往往具有很强的参考价值——因为面试官很可能会就这些知识点继续追问。比如笔试考了Handler机制面试时就可能让你手写一个简化版的Handler笔试考了自定义View的测量和绘制流程面试时就可能让你现场讲一下View的绘制流程在缓存优化时怎么把控甚至给出一段代码让你分析性能问题。所以笔试复盘整理的错题本和知识清单要一直保留到整个校招季结束随时补充新的内容。我个人的习惯是把面试中被追问到的知识点加入同一份清单标注“笔试已考”“面试已问”和“掌握程度”三个字段形成自己的知识图谱。这样复习起来非常有针对性不会漫无目的。6.3 保持心态与节奏最后想聊聊心态。校招笔试和面试密集安排的那段时间几乎每个人都会经历被拒、拿到面试机会、等待结果这种情绪上的起伏。有一些笔试你感觉自己发挥得不错结果却没有进下一轮有一些笔试你觉得考砸了反而意外通过了。这是非常正常的现象因为公司在筛选候选人时会参考多项指标笔试成绩只是其中之一。我的经验是不要试图从单场笔试的结果中过度解读自己。把每一场笔试当作一次模拟练习考完之后客观记录自己的表现找到薄弱环节然后投入到下一阶段的准备中去。计算机基础这门课没有捷径但也没有天花板——你把一个知识点弄懂之后它就会一直长在你身上。保持节奏稳步推进最后一定能拿到心仪的offer。