人人网2015研发笔试卷A复盘:Java并发、算法与数据库考点精讲
前阵子整理移动硬盘翻到一份扫描版的《人人网2015研发笔试卷A》文件名后面还带着“实习生招聘”几个字。盯着题目看了半天当年考场上的紧张感又回来了。这份试卷谈不上有多难但它代表了一类特别典型的互联网研发岗笔试风格Java基础扎实、算法题不偏不怪、网络和数据库的问题很接地气最后还要靠几道场景题来拉开差距。对正在准备校招、或者想跳槽去社交/内容类互联网公司的朋友来说与其盲目刷一堆偏题怪题不如先把这类经典卷吃透。这篇文章不是原卷的完整抄录而是我凭记忆把里面印象比较深的几道题拆开揉碎讲清楚再补上一些后来面试别人时用到的观察。无论你处于哪个阶段只要能跟着思路走一遍至少能减少相当一部分笔试踩坑的可能。1. 这份人人网2015研发笔试卷A到底考什么1.1 拿到A卷的第一印象模块构成2015年的人人网正处于移动端产品转型的关键阶段社交产品赛道上对手又多所以他们对研发候选人的要求很明确既要有独立写代码的能力又要能解释清楚常见的线上问题。A卷的模块基本是固定的客观题、简答题、编码题三大块。客观题以选择题和填空题为主覆盖Java语法、数据结构、操作系统、计算机网络。简答题通常会有1到2道场景设计比如“用户量很大时消息系统怎么设计”。编码题一般在两道左右一道偏算法一道偏工程实现。整体来看试卷不是单纯考“背诵”而是想看你能不能把基础概念用起来。我当时拿到卷子先翻到最后看编码题发现算法题很眼熟是“求数组中三个数之和等于目标值的所有不重复组合”也就是LeetCode上的3Sum变体。另一道工程题跟多线程交替打印有关。看到题目之后心里稍微稳了一点因为这类题只要平时练过思路是能很快建立起来的。1.2 难度定位与考察基调从难度上说这份试卷属于中等偏上但它的“区分度”不在难题本身而在基础题是否足够仔细。比如选择题里出现过“HashMap在并发put时可能出现什么现象”这种很基础的题但选项里混杂了“死循环”“数据丢失”“扩容后key丢失”等接近真实场景的描述。如果你只是背过“线程不安全”这句话看到这道题会犹豫很久。另一个基调是特别在意“为什么”。简答题里有一道“TCP四次挥手后主动关闭方为什么要进入TIME_WAIT状态”这种题在不少公司的笔试卷里都能看到但答法有深有浅。大多数人能答出“保证最后一个ACK能到达对方”但很少有人能进一步补充“让旧连接上的延迟数据包在网络中消失”而这恰恰是阅卷时拉开差距的地方。所以这份试卷透露出来的选人逻辑很简单基础知识不能有短板重要知识点要能说出底层原因代码题要能写出边界处理完整的实现。这也是为什么哪怕到了今天我还是建议准备笔试的人把这类老卷当模板来研究。2. 先把考点地图铺开每个模块都在考察什么能力2.1 一张表看清考点分布在复盘具体题目之前我习惯先把考点分布整理成一张表。这样做的好处是能快速看出整份试卷的能力模型复习时不会东一榔头西一棒子。考察模块常见题型核心知识点大概占比Java基础选择、填空HashMap、JVM内存、异常、泛型、线程安全20%数据结构与算法填空、编码链表、二叉树、双指针、动态规划、复杂度30%操作系统与网络选择、简答进程线程、死锁、TCP/IP、TIME_WAIT20%数据库与SQL简答、编码索引原理、事务ACID、SQL编写、避免回表20%设计与场景简答消息系统、缓存、接口幂等、设计模式10%我在整理这种表的时候通常会把“占比”看作复习时间的分配权重。Java和算法加起来占了半张卷说明笔试最看重的仍然是代码硬功夫。网络和数据库属于“不问则已问就问到细节”的类型需要单独花时间背熟原理。2.2 这些高频考点背后的选人逻辑很多人会问为什么每家公司笔试题都长得差不多背题库不就行了我的看法是考点可以重合但考察深度不一样。人人网这套A卷里的Java题并不是问你“HashMap和Hashtable有什么区别”而是让你分析并发场景下的具体现象。这种问题能直接反映出你有没有踩过生产环境的坑。数据结构与算法部分更明显。二叉树的遍历、链表的反转这些题几乎人人都会背但卷子里故意加了一些边界条件比如“链表长度为1时”“数组里有重复元素时”。如果你只看过标准解法没在手写代码时考虑边界很容易因为小错失分。这其实就是研发岗位的基本功不是“会做”而是“做对且防错”。网络和数据库题虽然占比不高但它们承担着筛选“工程素养”的功能。一个能解释清楚B树为什么适合做索引的人通常也能理解查询优化的一些基本手段。这类知识在实际工作中直接关系到接口响应慢、数据库抖动等问题所以笔试考它们不是走过场而是提前筛选一批具备排障意识的人。3. 经典题目复盘四道题吃透一套卷的精华3.1 Java并发场景题HashMap为什么会出问题原题大致是“在多线程环境下多个线程同时往一个HashMap中put数据可能出现什么问题为什么”这道题在2015年是高频题放到现在仍然是很好的基础题。答案分成两个层面。第一层是“线程不安全”。HashMap的内部结构是数组加链表put数据时要通过hash找到桶位再在链表上插入或查找。如果两个线程同时对同一个桶位进行操作有可能互相覆盖导致元素数量少了。更严重的是在扩容阶段JDK 7的扩容会采用头插法并发扩容时多个线程同时操作一条链表可能形成环形链表之后一旦get到这个桶位就会陷入死循环CPU直接打满。第二层是“怎么解决”。最简单的方式是使用Hashtable但这玩意全局加锁并发能力很差。更好的选择是ConcurrentHashMap它通过分段锁或者CAS加局部锁来保证并发安全。2015年的时候JDK 8已经发布了所以如果答出“JDK 8的ConcurrentHashMap改用CAS加synchronized锁住桶头节点”会是比较加分的点。这道题真正的坑在于很多人只写“线程不安全会造成数据覆盖”没有解释扩容成环的细节。阅卷人想看到的其实是“你是否理解HashMap的底层结构”而不是一句简单的结论。所以我建议准备笔试时遇到Java集合类都要养成画结构图、讲清流程的习惯。3.2 算法题三数之和的双指针解法编码题里考了三数之和原题描述是“给定一个整数数组nums和一个目标值target找出数组中所有不重复的三元组使得三个元素之和等于target。”这里假设target为0就是LeetCode 15的经典版本。解法思路很清晰先排序然后固定第一个数剩下两个数用双指针在数组的剩余区间里夹逼。时间复杂度是O(n^2)空间复杂度是O(1)不计结果集。我当时在考场上写的代码核心步骤大致是public ListListInteger threeSum(int[] nums, int target) { ListListInteger res new ArrayList(); if (nums null || nums.length 3) return res; Arrays.sort(nums); int n nums.length; for (int i 0; i n - 2; i) { if (i 0 nums[i] nums[i - 1]) continue; int left i 1; int right n - 1; while (left right) { int sum nums[i] nums[left] nums[right]; if (sum target) { res.add(Arrays.asList(nums[i], nums[left], nums[right])); while (left right nums[left] nums[left 1]) left; while (left right nums[right] nums[right - 1]) right--; left; right--; } else if (sum target) { left; } else { right--; } } } return res; }写这道题时最容易丢分的地方有三处。第一忘记跳过重复元素导致结果里有重复三元组。第二固定第一个数时没有做“当前元素和前一个元素相等就跳过”的去重会多出很多无效计算。第三数组长度小于3的边界没有提前判断虽然代码里能跑通但以后做工程时这种防御性判断缺失会很危险。阅卷人看编码题时不会只看最终结果还会看代码里有没有考虑边界和异常输入。3.3 网络题TIME_WAIT为什么是2MSL简答题里有一道网络基础题“TCP四次挥手后主动关闭方为什么要保持TIME_WAIT状态而不是立刻关闭为什么要等2MSL”这个问题我见过至少几百次但能答完整的候选人不多。完整的答案包含两个原因。第一个原因保证最后一个ACK能够到达被动关闭方。四次挥手中主动关闭方发送最后一次ACK之后并不能确定对方一定收到了。如果这个ACK在网络中丢失被动方会超时重发FIN。如果主动方直接关闭就没有办法响应这个重发的FIN被动方会一直处于等待状态无法正常关闭。保持TIME_WAIT状态可以让主动方有机会重发最后的ACK确保连接正常终止。第二个原因让旧连接中残留的数据包在网络中消失。如果立刻创建一条相同IP和端口的连接旧连接中延迟到达的数据包可能被新连接错误接收造成数据混乱。MSL是报文最大生存时间2MSL可以确保一个报文在网络中存活的时间上限内所有旧连接的重复报文段都消失。在纸上写答案时我习惯先画一个四次挥手的状态图再把两个原因分点写出。网络题的重点不是背书而是要把“为什么需要这个状态”讲清楚。如果你只是答“为了保证可靠传输”阅卷人会认为你概念不清因为可靠传输不是简单一句话能概括的。3.4 数据库题从B树聊到覆盖索引数据库部分的题目是这样的“InnoDB为什么选择B树作为索引的底层结构什么是覆盖索引它为什么能优化查询”B树的答案可以从几方面展开。对比哈希索引B树能支持范围查询和排序哈希索引不行。对比二叉搜索树树高太高磁盘IO次数太多。对比B树B树非叶子节点不存数据因此每个节点能存储更多key树更矮更宽IO次数更少同时B树的叶子节点用链表串联非常适合范围扫描而B树叶子节点之间没有这种连续访问的便利。覆盖索引指的是一个索引中包含查询所需要的所有列查询时只需要扫描索引不需要回表到聚簇索引。举例来说表里有联合索引(a, b)如果执行查询“select a, b from t where a 1”这时候索引本身就够了不需要通过主键去聚簇索引里取其他列所以查询速度更快。把这道题答好就能在阅卷人面前展示出你对数据库底层机制的理解。只答“B树查询快”是不够的要能说出“磁盘IO次数”“树高”“范围查询友好”这几个具体原因。覆盖率方面如果能写出“联合索引可以减少回表次数”这个结论说明你确实写过业务SQL而不是只会背概念。4. 实战答题策略时间与踩分点怎么拿捏4.1 拿到试卷后的前3分钟做什么笔试最忌讳的是从第一题开始闷头写。我三次参加校招笔试两次是这么干的结果后面时间不够用。后来再考试我拿到卷子会先花两三分钟做一次“卷面扫描”翻一遍所有题目把会做的快速做完把不会的先放一放同时给编码题预留足够的思考时间。扫描时我会在题目旁边做标记。一眼能看出答案的题直接写需要稍微推导的题先圈出来完全没思路的题最后再碰。这样做的好处是先把该拿的分稳稳拿住再集中精力攻克难题。笔试不像面试没人会在旁边给你提示合理分配注意力本身就是一项能力。编码题特别要注意题目给的时间提示。如果一道算法题你心里没有思路最多再花五分钟还是没思路就赶紧做下一道别让一道题毁了整个节奏。这点我在考场上吃过亏当时在一道链表题上死磕太久导致后面的SQL题没写完整现在想想非常不划算。4.2 编程题怎么答才不丢冤枉分编程题不只是“代码能跑就行”尤其是手写代码时阅卷人会看你的代码风格、边界处理、复杂度和思路描述。我第一次给别人批卷的时候才明白原来很多同学题目做对了但因为代码缩进混乱、命名毫无意义导致阅卷人很难看出你真正的思路。我的建议是写代码之前先用两三行文字描述思路再开始写。这样即使代码有小bug阅卷人也能看到你的分析过程。代码内部尽量用有意义的变量名不要写a、b、c这种容易让阅卷人反感。写完代码之后务必补充一下边界条件的处理输入为空、长度为0、只有一个元素、数组里有重复值、数值溢出。如果时间充裕还可以在代码旁边写一下主要步骤的注释。注释不是给机器看的是给阅卷人看的。你多写一句话可能就多体现一分工程素养。很多笔试不是绝对按对错判分而是按“是否有工程思维”在加分。4.3 推荐的时间分配参考表给出一份基于A卷经历总结的建议时间分配但不代表适合每个人。核心原则是基础题控制在15到20分钟内解决简答题每题不超过10分钟两道编码题至少预留25分钟。题目模块建议用时说明选择题填空题15分钟拿不准的先跳过别纠结简答题网络/数据库/场景25分钟每题控制在8-10分钟第1道编码题15分钟先写思路再实现第2道编码题20分钟重点保证边界处理检查与补漏5-10分钟检查是否有空题、明显笔误这个时间表执行起来并不容易因为实战中很容易在某道题上钻牛角尖。我的办法是心里默念“现在花了多少时间”如果超了预算就强制放下。笔试的通过率不是看你能不能满分而是看你的整体表现把能做的做对就已经能超过很多人了。5. 复盘中的避坑指南这些细节决定了你能不能进面5.1 算法与代码实现中的经典大坑复盘这套A卷时我特别注意到算法题里有几个高频坑这些年我自己面试别人也反复遇到。第一个坑是排序后忘记去重。三数之和一旦遇到重复元素直接导致结果集出现重复三元组。正确做法是固定第一个数时跳过与前一个数相同的元素双指针移动时也要跳过连续重复值。很多人在这个点上丢分不是因为不会算法而是因为代码习惯不好。第二个坑是整型溢出。2015年那会儿的题目还不会刻意让你处理非常大的整数但如果你对数值范围敏感比如在二分查找里写int mid (left right) / 2当left和right接近Integer.MAX_VALUE时求和就会溢出。更保险的写法是left (right - left) / 2。这种细节往往能反映出你对数据范围的意识。第三个坑是链表的边界。任何涉及链表的题都要先判断head是否为null还要考虑链表只有一个节点、两个节点的情况。比如反转链表只写循环不处理尾节点空指针代码一运行就崩。手写代码没有IDE帮忙唯独靠自己在设计阶段就把边界补全。5.2 网络与操作系统的概念辨析网络题和操作系统题最容易被概念混淆。我见过很多候选人把“进程和线程的区别”答成“进程是程序的一次执行线程是进程的一部分”然后就没有下文了。实际上阅卷人更想听到的是“进程是资源分配的基本单位线程是CPU调度的基本单位”以及“同一进程的线程共享地址空间而进程之间拥有独立地址空间”。TCP三次握手和四次挥手也是重灾区。很多人能画图但一问“为什么连接是三次断开是四次”就卡住。连接时只要确认双方收发能力各一次三次就够了断开时因为TCP是全双工的每一方都需要单独关闭自己的发送通道所以需要四个报文。这个概念辨析如果不够清晰简答题很难拿高分。死锁部分也值得注意。四个必要条件分别是互斥、请求与保持、不可剥夺、循环等待缺一不可。题目如果问“如何避免死锁”不能只说“有序加锁”还要解释“破坏循环等待条件”这种方法。笔试时多写一层“这对应破坏了哪个必要条件”会让阅卷人觉得你真的理解了死锁模型而不是背了四个名词。5.3 数据库题里的隐藏扣分点数据库题看似简单但想拿高分不容易。我曾经批到过一份卷子SQL写得很漂亮但用了select *在覆盖索引那一问里就被扣分了。笔试题里所有SQL编写场景最好只选择需要的列这不仅是性能问题也直接关系到你是否理解索引回表。另一个容易忽略的点是隐式类型转换。如果表的索引列是varchar类型而查询条件里用了整型数字MySQL会自动把varchar转成数字导致索引失效全表扫描。这种题放在简答题里考察的是候选人有没有线上慢查询排查经验。如果能在答案里写出“我会用explain查看type字段如果出现ALL就要关注隐式转换”这种话会比单纯答“索引会失效”好很多。事务隔离级别也值得展开。至少要知道读未提交、读已提交、可重复读、串行化四个级别以及它们分别解决什么问题。2015年的笔试通常只要求说清楚脏读、不可重复读、幻读的区别但如果能补一句“InnoDB默认级别是可重复读通过MVCC和间隙锁避免幻读”就会非常有竞争力。5.4 面试官角度他们想从笔试卷里看到什么我后来参与过几次校招笔试的阅卷发现面试官看卷子的重点不完全一样。有人只看算法题有没有通过有人更看重新题和场景题的思维过程但有一个共识是基础题不丢分是底线。一份合格的笔试卷通常不需要每一题都答对但要让人看出你有扎实的底子和清晰的思路。我最欣赏的卷子是那种代码有注释、解题有步骤、简答题分点作答的卷子。相反如果选择题全对但编码题空着大概率会进入“考虑一下”名单而不是直接通过。笔试本质上是筛选“能干活的人”不是筛选“会考试的人”。所以平时刷题不要只刷选择题和概念题一定要动笔写代码培养手写代码的熟练度和稳定性。如果条件允许用一张白纸模拟笔试环境不许复制粘贴不许编译运行写完以后对着参考答案检查边界和细节。这个方法虽然费时间但效果比在IDE里写一百道题都明显。6. 这份试卷给现在做题人的复习建议6.1 卷子是2015年的考点却没过期很多人看到“2015年”会觉得太老不值得看。但仔细分析一下这些考点在今天依然是主流面试的重点。Java集合类、并发、TCP/IP、数据库索引、基础算法哪一样不是后端研发的基础哪怕是现在流行微服务、云原生底层还是离不开这些知识。区别在于今天的笔试题目会更贴近分布式场景比如加一道“如何设计一个分布式锁”“如何保证消息不丢失”但底层的理论基础仍然是操作系统、网络和并发。把2015年这套A卷吃透相当于给自己打下一个通用地基再去延伸新技术会轻松很多。我甚至觉得这种“古早”试卷比那些新出的、堆砌偏题难题的题库更有参考价值因为它更接近真实工作中的技术需求。人人网当年需要处理大量用户动态、好友关系、实时消息他们的题目就是在为这些业务场景找“技术匹配的人”。现在做社交、内容、直播类产品考察逻辑依然是相通的。6.2 如何围绕这套卷子搭建自己的复习计划如果你想拿这套卷子作为复习主线我建议按三条线推进。第一线是“刷题线”。每天安排1到2道高频算法题重点题目类型包括双指针、链表、二叉树、动态规划、字符串处理。刷题时不要只求AC要把每道题的优化思路和边界条件写下来。尤其是双指针类的题几乎年年有吃透三数之和、四数之和、最长无重复子串这类经典题就够了。第二线是“原理线”。每周花两个晚上复习Java集合源码和并发包比如HashMap、ConcurrentHashMap、synchronized与ReentrantLock、volatile的应用场景。网络方面重点抓TCP握手挥手、TCP与UDP的区别、HTTP状态码。数据库方面把B树索引、事务隔离级别、索引失效场景、SQL优化“三板斧”过一遍。第三线是“实战线”。找一个小项目把消息推送、好友关注、缓存设计等场景融进去。笔试里的场景题虽然不会要求你写完整代码但如果你能结合自己做过的项目谈方案会更有说服力。比如问你“用户量很大时消息系统怎么设计”你可以从异步削峰、消息队列、离线消息存储、推送通道去拆解这些都是实际项目里会碰到的东西。6.3 我最后想多说一句的体会回过头看2015年这套A卷最让我感慨的不是哪道题难而是它恰好逼着我把很多基础概念重新嚼了一遍。做笔试题的时候可能觉得烦但工作三五年后再看那些原理性知识全都会在排障、优化、设计时遇到。就像当年考场上那道“HashMap并发问题”我后来真的在一个线上接口的报错里遇到了类似的循环依赖问题。技术世界一直在变但底层逻辑的权重从未降低。如果你正在准备笔试不要迷信什么“押题密卷”也不要因为一套老卷而不屑一顾。静下心来把自己不会的原理弄清楚把每一道手写题练到边界完整这比刷十套题都管用。这套人人网2015研发笔试卷A也许只是一张旧纸但它反映的研发基本功放到今天仍然是硬通货。