
我当年刷这题的时候说实话第一反应是“这考察范围也太杂了”。但实际做完再对着答案复盘你会发现它本质上不是考某几道题而是在给你画一张计算机基础知识的雷达图。语言功底、数据结构、算法设计、操作系统、网络原理甚至连概率和逻辑推理都揉进去了。这篇我不想简单给答案而是把每一类的考察意图和你应该怎么准备说清楚顺便把当年踩过的坑也一并交代了。1. 整体考察思路与知识结构拆解1.1 为什么实习生笔试要考得这么广很多人不理解我应聘的是Java开发或者算法岗为什么要考C内存布局为什么还要看TCP状态码其实站在出题人的角度就很好理解实习生不像社招那样有明确的项目经验可以挖掘笔试是筛选“基本功是否扎实”效率最高的方式。阿里这种体量的公司内部的技术栈极其复杂今天你可能在写Java明天一个故障排查就涉及JVM甚至操作系统底层交互。所以笔试不看你写过什么框架而是看你对“计算机这台机器”本身的认知程度。2017年这套题里语言侧的题目C/Java语法、内存、多态和系统侧题目进程线程、网络状态比例差不多这不是偶然它反映的是研发岗位的通用底色不管你做业务还是做中间件底层逻辑是通的。1.2 考察模块的占比逻辑从当时流传出来的版本看大致可以分为四块语言基础、数据结构与算法、操作系统与网络、逻辑与概率。其中语言基础占比最大差不多四成数据结构和算法占三成左右剩下的是系统和逻辑。这个占比其实很有讲究。语言基础考的是你“能不能立刻干活”算法题考的是“遇到新问题能不能拆解”操作系统网络考的是“线上出问题能不能排查”。三个维度正好对应三个层次会写、能解、懂排障。很多同学只刷算法不补系统知识结果笔试里栽在“进程和线程的区别”这种看似简单但需要深度解释的题目上非常可惜。1.3 对现在备战面试的参考价值虽然这是2017年的题但我建议现在准备大厂实习的同学也认真过一遍。不是说题还会原封不动出现而是它的考察骨架和出题品位后来几年基本没有变。公司可能会在题目里加入新的技术热点比如容器、云原生、分布式一致性但底层那些关于内存、并发、数据结构复杂度的考察点永远都在。我的理解是这套题更像一份“体检清单”你哪里薄弱做一遍就暴露了。与其到处找面经背答案不如拿它做一次全面的自我排查。2. 语言基础题解析内存、多态与语法陷阱2.1 C虚函数与多态的底层原理这类题几乎是必考的。典型的问法有“以下关于虚函数的说法正确的是”、“基类指针指向派生类对象后调用虚函数会发生什么”、“析构函数为什么要声明为虚函数”。我当时看到“虚函数表是每个对象一份还是每个类一份”这类选项时都容易犹豫。这里的关键在于理解对象的内存布局。每个含有虚函数的类编译期会生成一张虚函数表vtable存放该类所有虚函数的地址每个对象内部会有一个vptr指针指向这张表。所以虚函数表是每个类一份而vptr是每个对象一份。为什么不把虚函数表直接放进对象里因为那样每个对象都要复制所有函数指针内存浪费太大。用一个指针间接引用对象只需要多一个指针大小的空间。这是典型的空间换时间与时间换空间的权衡思维笔试未必直接考但解析里一定要明白。2.2 构造函数与析构函数的执行流这类题喜欢这样出“定义一个派生类对象时构造函数的调用顺序是什么”。答案大家都知道是先基类后派生类但我觉得更值得关注的是为什么。因为派生类对象包含基类的子对象如果基类部分没有被正确初始化派生类自己的初始化逻辑就失去了依赖基础比如基类里维护的内部状态就没有建立。场景上我记得有一道题是“基类析构函数不是虚函数时delete基类指针会怎样”答案是未定义行为因为不会调用派生类析构函数派生类的资源就没人释放。这里有个明显的坑如果你在项目中把基类指针当作通用句柄析构函数一定要加virtual。笔试只是考选择题线上的事故却是实实在在的内存泄漏。我建议记一种内存心理模型构造从根开始一层层向外析构恰好相反从叶子开始一层层向内。这样无论题目怎么变你都能推出来。2.3 static关键字的多重身份C语言里static修饰函数和全局变量意味着“内部链接”限定在当前编译单元可用。C里static成员变量就是属于类而不是属于对象所有对象共享一个副本。static成员函数没有this指针只能访问static成员。有一道印象深刻的题目大概是“static成员函数能否访问非static成员变量”答案是绝对不能。原因是静态成员函数没有this指针它连“当前对象是谁”都不知道怎么访问对象的成员要访问也可以只能通过外部传入的对象引用。这里的核心思想是static把“实例维度”提升到了“类维度”。实例维度下每个对象是独立王国类维度下所有对象共享同一个数据仓库。理解了这个后续并发编程里“类级别锁”和“对象级别锁”的区别也会顺畅很多。2.4 Java方向的对照复习做这套题的时候如果你是Java方向不能用“我不看C”来逃避但可以有侧重地对照理解。比如C的虚函数对应Java的普通方法重写C的static成员变量对应Java的static字段。Java没有指针但引用传递和值传递的区分也是经典考点。还有一道典型的Java题“String、StringBuilder、StringBuffer的区别”。String不可变每次拼接会产生新对象StringBuffer线程安全但慢StringBuilder快但线程不安全。单线程环境用StringBuilder多线程共享可变字符串才用StringBuffer。选择依据就一句话是否有跨线程共享修改的可能。没有就追求性能有就求稳。我的建议是准备一张“语言特性对照表”把C和Java里类似的概念列在一起比如虚函数/方法重写、析构函数/finalize、static成员/static字段。这样笔试遇到任何一个语言的题你都能在脑中快速找到“另一个语言里对应的东西”理解速度至少快一倍。3. 数据结构与算法题不只是写代码3.1 复杂度分析是算法题的隐形考点笔试里的算法选择题很多不是让你手写代码而是给出几段代码问时间复杂度。比如双重循环嵌套内层j从i开始遍历这个复杂度其实是O(n^2/2)但渐进意义下还是O(n^2)。有一道题印象很深是关于递归计算斐波那契数列的复杂度。直接用递归写法每次调用产生两个分支深度为n复杂度是O(2^n)。这不是因为递归本身慢而是它把同一个子问题反复计算了很多遍。如果用一个数组做动态规划自底向上算复杂度直接降到O(n)。我当时的感受是复杂度不是靠背的是靠“数次数”数出来的。遇到循环就数迭代次数遇到递归就画调用树遇到二分就关心每次缩小的比例。面试官真正想看的是你有没有这种“成本意识”而不是答案本身。3.2 二叉树的遍历与重建“已知前序和中序遍历求后序遍历”这是数据结构的老古董题但2017年的卷子里依然出现了。它的本质是递归分治前序第一个元素是根在中序里找到根的位置左边是左子树右边是右子树然后递归处理左右部分。我记得有个常见的错误是只想着“背代码”结果题目变成“已知后序和中序求前序”就不会了。其实思路完全一样只是根的定位方式变了。后序最后一个元素是根从中序里一分为二递归即可。不要记题型要记“如何定位根”的逻辑。如果觉得递归绕可以拿一棵三层的具体二叉树在草稿纸上手动模拟一遍。把每一步的“当前根是谁”“子树范围是什么”写下来十遍之后你就能在脑子里递归了。3.3 排序算法的稳定性和适用场景排序是笔试选择题的重灾区“以下哪个排序算法是稳定的”、“堆排序和快速排序平均复杂度一样吗”、“数据基本有序时用什么排序合适”。2017年的卷子里有一道“以下排序算法中平均时间复杂度为O(n log n)的是冒泡、插入、快排、堆排序”。答案是快排和堆排序。我更想多说一句为什么快排在实际场景里通常比堆排序表现更好即使两者平均复杂度一样因为快排的局部性更好CPU缓存命中率高堆排序的访问模式是跳跃式的缓存不命中率高。这已经不仅仅是笔试知识点而是真刀真枪写高性能代码时的工程考量。关于稳定性我的记忆技巧是稳定的排序本质上不会“跨过相等元素”。冒泡和插入排序只在相邻元素之间交换所以稳定快排的partition会把元素移动到很远的位置可能跨过相等元素所以不稳定。记住“相邻交换稳定”这个直觉大部分题目就能推出来。3.4 哈希表解决冲突的两种策略哈希表的考题通常不是让你实现而是问“开放定址法和链地址法的优劣”。链地址法把冲突的元素挂在同一个桶的链表上实现简单负载因子可以大于1但缓存不友好。开放定址法把所有元素都存在表内缓存友好但负载因子不能太高否则冲突急剧增加。有一道题问“线性探测法在删除元素时为什么不能直接置空”因为如果直接置空后续查找某个元素时探测链可能在这里中断明明元素存在却找不到了。要用“标记删除”的方式而不是直接清空。这类细节没有自己实现过一次哈希表的人很容易忽略。我的建议是这一块别只背结论动手实现一个分离链接法的哈希表再实现一个线性探测版的感受一下两者在插入和查找时的区别。笔试里遇到“哈希表扩容为什么开销大”这类题你脑子里自然能浮现出“所有元素重新计算哈希位置”的画面。4. 操作系统与网络原理排查问题的底气4.1 进程、线程与协程的本质区别这个考点几乎是所有大厂笔试的保留节目。进程是资源分配的基本单位线程是CPU调度的基本单位同一个进程内的线程共享地址空间和文件描述符而进程之间是隔离的。协程则是用户态自行调度的轻量级执行流切换成本比线程更低。2017年的题里有道选项是“线程之间可以共享什么”答案是堆和全局变量。为什么栈不能共享因为栈是每个线程私有的运行上下文记录着函数调用关系。如果两个线程共用一个栈函数返回地址就会乱套。我的理解是线程共享的是“堆上的数据”独享的是“执行流的状态”。堆是房子里的公共区域栈是每个房间的私人空间。如果面试官追问“协程为什么轻量”核心原因是协程切换不需要陷入内核态只需要在用户态保存/恢复寄存器上下文这比内核线程切换成本低一个数量级。4.2 死锁产生的四个必要条件互斥、持有并等待、不可剥夺、循环等待这四个条件缺一不可。笔试里经常让你判断“破坏哪个条件可以预防死锁”。比如资源一次性分配就是破坏“持有并等待”允许抢占就是破坏“不可剥夺”按序分配资源就是破坏“循环等待”。我曾经天真地以为“避免死锁”就是“预防死锁”其实是两码事。预防是把四个必要条件之一直接否定掉策略是静态的避免是在运行时动态判断是否进入不安全状态典型算法是银行家算法。笔试里如果问“Dijkstra的银行家算法属于哪一类”答案不是预防而是避免。做题时的建议是看到“死锁”两个字先在草稿纸上把四个必要条件默写出来然后逐项对照。大多数多选题的答案就是“哪个选项对应破坏哪个条件”的排列组合。4.3 TCP三次握手和四次挥手的状态变化网络题里TCP状态机是重中之重。三次握手客户端发SYN服务端回SYNACK客户端再回ACK连接建立。四次挥手主动关闭方发FIN对端回ACK对端再发FIN主动方回ACK连接关闭。有一道题问“TIME_WAIT状态出现在哪一端”。答案是主动关闭连接的一端。因为主动方要确保最后一个ACK能被对端收到如果丢了对端会重发FIN所以主动方需要等待2MSL最大报文段生存时间才能彻底关闭。这个设计是为了防止旧连接上的迟到报文干扰新连接。我记得很多同学在这道题上栽过因为只记了“四次挥手”没记状态属于谁。我的技巧是画时间序列图把每一端的状态变化写上去。什么时候SYN_SENT、什么时候ESTABLISHED、什么时候FIN_WAIT_1、什么时候TIME_WAIT画完一遍你对TCP的整个生命周期会有一个立体认识。4.4 HTTP状态码与常见故障排查笔试里考察的HTTP状态码不算深但非常实用。200是成功301永久重定向302临时重定向403禁止访问404不存在500服务器内部错误502网关错误504网关超时。2017年的题目里有一道“用户在浏览器访问某页面时返回503最可能的原因是什么”。503的意思是服务不可用通常不是代码bug而是服务器过载或者正在维护。这类题对有线上排查经验的人来说很简单但对纯刷题的学生来说容易和500混淆。我的建议是不要只背状态码数字而是把每个状态码背后可能的物理原因也记一下。502是上游没收到有效响应504是上游响应超时看到这两个码就应该去查后端服务和网络链路。这才是笔试题目真正希望你具备的能力。5. 错题复盘与备考策略避免低效刷题5.1 建立错题背后的“知识盲区”单纯订正答案是最低效的复盘方式。拿到错题后我会问自己三个问题我为什么选错正确答案的解释里哪一步是我没想到的这个知识点在真实项目中会以什么形式出现比如我做过一道关于“Java中HashMap扩容时是否需要重新计算hash”的题。当时我选错了后来才明白JDK 1.8对这个问题做了优化扩容后元素要么在原位置要么在原位置原容量的位置不需要重新计算hash只凭高位bit判断。这个知识点在什么场景下有用如果线上HashMap容量设置不当引发频繁扩容涉及到的就是这类底层行为。所以做题的意义不在于“做对”而在于“暴露”。一道错题背后是一个知识盲区你花20分钟把它补上比刷五道已经会做的题更有价值。5.2 时间分配与答题顺序笔试的时间通常比较紧张我建议的原则是先做会做的再做能推的最后做要蒙的。不要在一道题上死磕超过3分钟。因为选择题的知识点之间往往是独立的你卡在C多态上不代表后面的网络题不会。如果一道题完全没有思路但选项里有两个明显是干扰项、两个看起来合理可以结合常识猜一个。这不是鼓励蒙题而是说在可控范围内不应让低分值的题消耗高分值题的时间。整套卷子做完后如果还有时间再回头琢磨卡住的题也不迟。5.3 针对性复习路线如果你的目标是阿里或者同类公司的实习建议按这个顺序复习先语言基础重点是多态、内存、集合类再数据结构重点是二叉树、哈希、排序、复杂度然后操作系统重点是进程线程、死锁、内存管理最后网络重点是TCP、HTTP、DNS。每复习完一个模块不要急着进入下一个模块先做十道对应模块的选择题巩固。题目不需要太偏把常见考点吃透就好。我的感受是技术笔试比面试更“标准化”它考的不是灵感而是你是否系统地把核心知识过了一遍。5.4 从笔试到面试的延伸准备笔试里出现的知识点面试里大概率会被追问。比如你笔试做了“HashMap的扩容机制”面试官可能会问“如果多个线程同时触发扩容会发生什么”这就涉及并发安全问题了。所以每一道笔试错题都要准备一个“如果再往深挖一层我会怎么回答”的预案。我会在自己整理的复习文档里给每个知识点加一栏“延伸问题”比如虚函数问题后面记“构造函数里调用虚函数会发生什么答案是只会调用当前类的版本因为动态类型还没完成”。这样笔试和面试就能打通复习而不是割裂地准备两种考试。6. 最后的几点实操心得马上要参加这类笔试的同学我在实际刷题中有几个很具体的建议。第一草稿纸一定要用起来。别以为选择题不需要演算复杂度的推导、二叉树的重建、TCP状态的流转在纸上画一遍和脑子里空想效果完全不同。我当年准备的时候桌上永远放一沓A4纸题目还没读完手已经开始画了。第二不要只刷“面经”里的题目。面经只能帮你了解出题风格但刷题的重点应该放在系统复习知识点上。因为题目会变考点不变。今天考虚函数表明年可能考lambda表达式的捕获列表但它们都归在“语言特性底层机制”这个大类下。第三适当的“恐惧”是有用的。我第一次做这类笔试模拟题时正确率大概只有六成心里确实慌。但换个角度想好在这是练习题不是真正的面试我还有时间把漏洞补上。备考阶段暴露问题是成本最低的时候。第四如果你时间有限优先保语言和数据结构。这两块占比最大而且是确定性最强的题目不像系统网络偶尔会出现偏题。把语言基础和数据结构练到九成正确率你已经超过了大多数竞争者。做这套题最大的价值不是让你记住某道题的答案而是让你发现自己对计算机这座大厦的地基理解得够不够深。地基不牢后面聊什么分布式、高并发都是空中楼阁。认真刷一遍把每个模糊的知识点都搞清楚你会发现面试时底气都不一样了。