十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

阿里实习生笔试题深度解析:从HashMap到分布式核心考点

阿里实习生笔试题深度解析:从HashMap到分布式核心考点 每年三月底四月初都是实习生招聘最热闹的时候。2017年那阵我正读研二投了阿里巴巴的实习生岗位想着能提前感受一下大厂面试的节奏。笔试是在线上做的全程摄像头监控题目分单选、多选和编程题时间是九十分钟。说实话那套题做完之后我最大的感受不是“难”而是“广”——从Java基础到JVM从并发编程到网络协议从数据库索引到分布式一致性基本把后端开发要用的核心知识全扫了一遍。后来我把这套题反复看了好几遍发现它其实藏着一条很清晰的考察逻辑不追求你把某个冷门API背得多熟而是看你能不能把底层原理讲清楚能不能在多个知识点之间建立联系。这篇文章我就以“阿里巴巴2017实习生笔试题二”为线索把里面涉及的几个核心考点拆开揉碎讲一遍。我会还原题目考察的知识点补充原理推导和踩坑经验并给出实际笔试时的答题策略。无论你是准备实习生招聘还是想系统梳理后端基础这篇应该都能帮上忙。1. 整体设计与思路拆解1.1 这套题到底在考什么先搞清楚一件事实习生笔试不像社招面试那样考察项目深度也不像竞赛题那样死磕算法优化。它更像是“基础能力筛子”——公司需要确认你具备扎实的计算机功底进来之后能快速上手干活不用从零补课。阿里巴巴的实习生笔试题在这方面做得相当典型覆盖面广难度梯度明显而且非常喜欢把两个知识点揉在一起考。以这套题为例我赛后复盘时把考点大致分成了五类模块典型考点出题风格Java基础集合类、String、异常处理给出代码片段判断输出JVM内存区域、GC机制、类加载概念辨析 参数调优并发编程synchronized、volatile、线程池多线程并发场景分析网络与OSTCP/UDP、进程线程、零拷贝协议状态 Linux命令数据库与分布式索引、事务隔离级别、CAP理论场景题 方案选型这个分布不是随意的。Java是阿里巴巴后端的主力语言所以Java基础和JVM占比最高分布式是业务规模到了一定程度之后的必然挑战所以一致性、缓存、消息队列这些概念必考。换句话说这套题本质上是在模拟“一个后端开发每天都会遇到的思考场景”只是把它压缩到了九十分钟的试卷里。1.2 为什么这些题目值得反复研究我见过不少同学刷题只看答案对不对对完就丢。其实笔试题目最有价值的不是答案而是它背后暴露出的知识盲区。比如有一道题考了HashMap在多线程环境下的问题如果你只知道“HashMap线程不安全”这个结论却说不清是扩容时形成循环链表还是数据覆盖那面试官一眼就能看出你是背的答案。另一个原因是这套题里的很多知识点在后续面试中会被反复追问。你今天在笔试题里遇到“JVM内存溢出有哪几种”明天面试官就可能在项目经历里问你“线上Full GC频繁你怎么排查”。基本概念是地基地基建牢了高阶问题才能接得住。所以我的建议是不要把这套题当考试而是当一张知识地图。每道题都至少往深挖两层直到你能用自己的话把一个概念讲给完全不懂的人听。2. 核心细节解析与实操要点2.1 Java集合类HashMap的线程安全问题先看一道很有代表性的选择题在JDK 1.7环境下多个线程同时往HashMap中put数据可能会出现什么后果选项包括数据丢失、死循环导致CPU飙升、Node链表成环、以上都有可能。正确答案是“以上都有可能”但很多人只记得“死循环”却说不清为什么。这里的关键在于JDK 1.7的HashMap在扩容时采用头插法新元素插入链表头部当两个线程同时触发resize它们会同时操作同一个桶位上的链表导致链表节点的next指针互相指来指去最终形成一个环形链表。之后再对这个桶位执行get操作就会在链表上无限循环CPU迅速被打满。如果你在笔试中选择了“数据丢失”这个选项其实也没错。多个线程同时put时后写入的值可能覆盖先写入的值或者两个线程同时判断key不存在然后各自new了一个节点插入另一个就被覆盖了。但如果你只选了“数据丢失”而没选“死循环”说明你对扩容机制的理解还停留在表面。从1.8开始HashMap改用尾插法解决了链表成环问题但数据覆盖的问题依然存在所以多线程场景下老老实实用ConcurrentHashMap。这里我想多说一句面试官特别喜欢在这个知识点上做文章比如追问“ConcurrentHashMap在1.7和1.8的实现有什么区别”——1.7是分段锁粒度是Segment1.8抛弃了Segment改用CAS synchronized锁桶头节点的方式锁粒度更细并发度更高。你如果能把这条演进路线讲清楚比背十道题的答案都有用。2.2 JVM内存模型一段代码的内存分配过程JVM相关的题目几乎每套笔试都有这套也不例外。有一道题给出了类似下面的代码public class Test { private int x 1; public static void main(String[] args) { Test t new Test(); int y 2; } }问这段代码中哪些变量存储在堆上哪些存储在栈上哪些存储在方法区。很多人会答错“x存储在堆上”——因为x是成员变量它属于对象的一部分对象在堆上所以x确实在堆上。t这个引用本身在栈上main方法局部变量但它指向的对象在堆上。y是main方法的局部变量也在栈上。类信息包括方法字节码、静态变量等存放在方法区。这种题目的考察点是JVM运行时数据区的划分。很多人背了“堆、栈、方法区”七个字但真给一段代码就分不清了。我的经验是记两条判断准则一是“对象实例一定在堆上除非被逃逸分析优化为栈上分配”二是“局部变量的基本类型和引用类型在栈上而成员变量随着对象在堆上”。这里还经常搭配一个延伸考点String对象的存储位置。new出来的String在堆上直接赋值的字符串字面量如果命中常量池则引用指向常量池中的对象从JDK 1.7开始常量池移到了堆中。所以“字符串常量池在堆里还是在方法区里”这个问题答案区分了JDK版本笔试时要注意题干是否标注了JDK版本。2.3 并发编程volatile的可见性与有序性并发编程模块有一道经典题定义一个volatile修饰的boolean变量flag线程A修改flag为true线程B读取flag问B能否立刻感知到修改。答案是“能但不保证原子性”。volatile只能保证可见性和有序性不能保证原子性。i这种复合操作即使在volatile变量上进行依然不是线程安全的因为i在字节码层面是“读取-修改-写入”三步。还有一个容易忽略的点volatile可以禁止指令重排这个特性在单例模式的双重检查锁DCL中至关重要。如果不加volatile由于指令重排的存在某个线程可能拿到一个“已经分配内存但尚未执行构造函数”的半初始化对象。我在笔试时遇到一道题就是问DCL中为什么需要volatile选项里有一个是“防止指令重排序导致拿到半初始化对象”这就是正确项。实际操作中我建议你不仅要记住volatile的语义还要能说出它的底层实现通过内存屏障Memory Barrier来禁止重排序并通过缓存一致性协议如MESI来保证变量修改后能立即同步到主内存。面试官如果追问“什么是内存屏障”你可以说它是一条CPU指令用于保证屏障前后的指令不会被重排并强制刷新缓存或失效缓存。能说到这个深度基本上就把这个考点吃透了。2.4 网络与操作系统零拷贝是如何减少开销的网络协议部分有一道题考察read和send系统调用在传输文件时产生几次上下文切换和几次数据拷贝。如果你没接触过“零拷贝”这个概念这题基本只能靠猜。传统I/O流程中一次文件发送经历了read()将数据从磁盘DMA拷贝到内核缓冲区再由CPU拷贝到用户态缓冲区然后write()再将数据从用户态缓冲区拷回内核socket缓冲区最终由DMA拷贝到网卡。整个过程是4次上下文切换 4次数据拷贝其中2次DMA拷贝、2次CPU拷贝。Java NIO中的FileChannel.transferTo()可以利用sendfile系统调用实现零拷贝数据从磁盘DMA到内核缓冲区后直接DMA到网卡不再经过用户态省去了2次CPU拷贝和2次上下文切换。这里“零拷贝”指的是CPU拷贝次数为零而不是数据拷贝次数为零这个细节很多面试官会特意纠正。这类题看起来像操作系统知识但它们在实际工作中会直接影响高并发文件服务的性能。比如你做文件上传下载服务用传统IO在单机千兆网卡下可能只能跑到几百Mbps换成零拷贝后轻松打满带宽。所以在笔试里考这个知识点本质上是在考察你对“真实业务场景中性能瓶颈在哪”的感知。3. 实操过程与核心环节实现3.1 笔试环境与答题节奏说回笔试本身的实操经验。阿里巴巴的实习生笔试用的是在线代码编辑器支持Java、C、Python等语言但是不能本地编译——这意味着你写完代码之后只能靠肉眼检查所以平时的编码习惯非常重要。首先每道题的时间分配要有策略。选择题和填空题控制在45到60秒一题遇到卡壳的先标记跳过别在一道题上耗太多时间。编程题通常有两到三道难度从简单到中等建议先花五分钟通读所有编程题优先做自己最有把握的那道。因为编程题按用例通过率给分通过一部分用例也能拿到部分分数所以哪怕思路不完整也要把暴力解法写上去不要留空。其次要注意编译环境差异。国内在线笔试系统很多默认使用JDK 1.8但有些系统会使用OpenJDK在个别API行为上有细微差异。笔试前最好提前熟悉一下在线编辑器的使用方式比如自定义函数是否需要写成静态方法、输入输出用Scanner还是BufferedReader这些细节看似不起眼却会影响你的调试速度。还有一个大家容易忽略的点代码风格。在线笔试没有人工改代码但你的代码会作为面试参考材料给面试官看。变量命名是否有意义注释是否清晰逻辑分支是否容易理解这些都会成为面试官判断你代码水平依据的一部分。我建议笔试时用自己最熟悉的命名风格比如index、count、temp这类简单明确的命名并在函数开头加一两行注释说明解题思路。3.2 编程题实战实现一个线程安全的LRU缓存这套笔试题里有一道让我印象很深的编程题设计一个线程安全的LRU缓存。题目要求实现get和put两个方法容量固定超出容量时淘汰最久未使用的key。看到这种题第一反应是直接用LinkedHashMap重写removeEldestEntry方法判断size是否超过容量。这个做法在单线程下完全没问题但这道题明确要求线程安全所以需要额外处理。我的实现思路是在LinkedHashMap外面包一层ReentrantLock或者在get和put方法上加上synchronized关键字。但如果你是笔试环境里写代码建议用synchronized代码量少不容易出错。import java.util.LinkedHashMap; import java.util.Map; public class LRUCacheK, V { private final int capacity; private final MapK, V map; public LRUCache(int capacity) { this.capacity capacity; this.map new LinkedHashMapK, V(capacity, 0.75f, true) { Override protected boolean removeEldestEntry(Map.EntryK, V eldest) { return size() capacity; } }; } public synchronized V get(K key) { return map.getOrDefault(key, null); } public synchronized void put(K key, V value) { map.put(key, value); } }需要注意LinkedHashMap的构造函数第三个参数accessOrder必须传true才能让get操作触发重排实现“最久未使用”的淘汰策略。否则默认的是插入顺序就变成FIFO缓存了。这个解法能拿基础分但如果面试官继续追问“synchronized锁的粒度是不是太大了”你需要能想到更优的版本——用读写锁ReadWriteLock或ConcurrentHashMap 双向链表 原子操作来实现更精细化的并发控制。笔试时先写出正确版本面试时再聊优化方向这是最稳妥的策略。另外我建议你掌握一种最常见的替代方案使用ConcurrentHashMap 一个自定义双向链表 ReentrantLock。get操作从map中拿节点然后把节点移动到链表尾部put操作插入新节点到链表尾部并放入map如果容量超了就移除链表头节点并同步删除map中的key。这个方案的好处是get和put的并发度比全局锁高很多坏处是代码量明显增加笔试时不容易写对。3.3 数据库索引专项为什么最左前缀原则这么重要数据库索引几乎是阿里笔试题的保留项目。有一道题考察联合索引的命中条件表中有联合索引(a, b, c)问下面哪些查询能用到这个索引。选项包括where a 1where b 1where a 1 and b 2where b 1 and c 2where a 1 and c 3。正确答案是“where a 1”和“where a 1 and b 2”以及“where a 1 and c 3”——只要是包含最左列a的查询都能用到索引。可能有人会疑惑where a 1 and c 3没有b条件也能用索引吗能但只能用到索引的a列c列无法利用索引进行精度过滤因为联合索引的B树是先按a排序再按b排序最后按c排序。缺少b这个中间层级c在索引中的有序性就无从谈起。关于数据库索引我踩过一个比较深的坑在线上环境用一个组合索引(a, b, c)覆盖了大部分查询结果某天产品加了一个基于a和c的报表需求查询特别慢。一开始没想明白明明索引里就有a和c两个字段为什么还用不上。后来用explain一查才发现type是ref但key_len只用了a列的长度说明c列的条件是在回表之后才过滤的。解决方法是把联合索引调整顺序或者基于a和c新建一个索引。这类问题在笔试中容易考原则在实际工作中则直接决定SQL性能。如果你能举出自己的线上例子面试官会对你另眼相看。3.4 分布式基础从CAP理论到实际应用分布式相关的题目在这套题里也有一定比例。什么场景选AP什么场景选CP这是阿里巴巴业务中非常真实的权衡。比如商品库存扣减数据一致性要求极高必须选CP而用户浏览记录、点赞数量允许短暂不一致选AP更合适。笔试中关于CAP最常见的考法是给出一个分布式系统的描述判断它属于CA、CP还是AP。这里要特别注意CAP理论中CA在分布式系统中是不存在的除非单机因为网络分区无法避免在网络分区发生时你只能在一致性和可用性之间二选一。所以看到“CA系统”的选项基本可以直接排除。阿里巴巴的很多中间件产品在CAP上有自己的权衡。比如你打开一个交易页面商品的库存数量显示是已付款的实时库存还是下单未支付的预占库存这背后很可能就是AP和CP策略的折中。理解了这一点你就能明白为什么分布式系统中会存在缓存和消息队列这种东西——它们都是为了在一致性要求不那么苛刻的场景下提升可用性和性能。我建议你在准备这部分时把“BASE理论”和“最终一致性”配合CAP一起复习。BASE理论是Basically Available基本可用、Soft state软状态、Eventually consistent最终一致性的缩写它是AP场景下的实践指导。笔试题常问“最终一致性有哪些实现方式”常见答案包括异步消息队列、事务消息、本地消息表、基于版本号的重试机制。每个方案都有优缺点你不需要全背但至少能说出两种以上方案的核心思想。4. 常见问题与排查技巧实录4.1 笔试中的高频错误与避坑指南这里整理一些我在做这套题以及帮学弟学妹复盘时发现的高频错误都是实际踩过的坑希望能帮你避开。第一分不清HashTable、HashMap、ConcurrentHashMap三者的线程安全性。HashTable是早期的线程安全集合所有方法用synchronized修饰并发效率极低。HashMap是非线程安全的。ConcurrentHashMap是Java并发包的线程安全集合采用分段锁或CAS synchronized实现高并发。笔试中如果看到“HashTable比ConcurrentHashMap并发性能更好”这种选项直接排除。第二对TCP三次握手和四次挥手的状态转换不够熟悉。有一道题问“TIME_WAIT状态出现在哪一方、持续多久、为什么存在”答案分别是主动关闭方、2MSL最大报文段生存时间的两倍、为了防止迟到的报文段干扰新连接同时确保最后一个ACK能可靠到达。很多人只记得TIME_WAIT出现在主动关闭方却答不出MSL的含义。我建议你亲手画一遍TCP状态转换图把SYN_SENT、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSE_WAIT、LAST_ACK这些状态的触发条件全部写清楚。第三对数据库事务隔离级别与锁机制的理解停留在背诵层面。MySQL默认的隔离级别是可重复读REPEATABLE READInnoDB通过MVCC多版本并发控制实现快照读通过间隙锁Gap Lock和下一键锁Next-Key Lock防止幻读。笔试中常考“可重复读是否解决了幻读”——答案是InnoDB在可重复读级别下通过间隙锁解决了快照读下的幻读问题但在当前读如SELECT FOR UPDATE下依然可能发生幻读。如果你能把这个差异讲明白比单纯背隔离级别表格要高级得多。第四Linux相关命令的基础不牢。阿里的笔试题偶尔会加入一道与线上排查相关的题目。比如“查看Java进程的线程数”“查看端口被哪个进程占用”“查找某段时间内修改过的文件”。对应的答案是ps -Lf、netstat -tunlp | grep、find /path -mtime -1。这些看起来很简单但平时不接触Linux的同学容易在这里翻车。建议至少把ps、netstat、top、free、df、find、grep、awk这些命令的常见用法练熟。4.2 编程题的隐藏问题编程题除了算法本身还隐藏着输入输出处理和边界条件的坑。比如很多题目要求输入多组测试用例如果没写while(scanner.hasNext())循环只能通过第一组用例得分会非常低。再比如题目明确说明n的取值范围是1到10^9时用int存储可能会溢出必须用long。这些边界条件虽然不涉及算法核心却决定了能否拿满分。阿里巴巴的在线笔试系统对代码的时限通常比较严格Java在相同算法下比C要慢所以尽量别用嵌套循环遍历10^6级别的数据。如果你的解法时间复杂度是O(n²)建议提前准备好优化思路比如用HashMap把查找从O(n)降到O(1)或者用双指针把两层循环降到一层。另一个常见问题是递归深度。有些题你用DFS递归实现但在数据量较大时栈溢出。这时候可以考虑用显式栈模拟递归或者改用BFS。笔试系统一般会把错误信息打印给你但如果你在紧张状态下看到StackOverflowError容易慌。我的经验是写递归前先估算递归深度超过10^4就考虑迭代实现。4.3 复盘方法如何把一套题的价值榨干做完笔试题不意味着结束复盘才是真正拉开差距的环节。我的复盘方法是建一张表格每个错题记录四列考察知识点、我的错误答案、正确答案、错误原因。错误原因细分为“概念不清”“理解偏差”“粗心大意”“时间不足”四类。一周后重新再做一遍错题如果仍然做错就说明这个知识点需要系统性复习而不是单题订正。举个例子。我当时做错了一道关于JVM类加载过程的题错选了“解析发生在验证之前”。复盘后才发现类加载的五个阶段是加载、验证、准备、解析、初始化其中解析阶段在Java虚拟机规范中允许在初始化之后发生以支持动态绑定。这个点非常冷门但一旦考到基本就是区分度所在。如果没有复盘我根本不会注意到自己对这个知识点的理解竟然是错的。这套方法我后来也用在了准备其他公司的笔试上效果非常好。笔试不只是知识的检验更是应试策略的练习。通过复盘摸清自己的知识边界比盲目刷一百道题更有价值。5. 延伸思考这套题的借鉴意义如果你不是2017年参加笔试的学生这套题是不是就没用了我觉得恰恰相反正因为它是好几年前的题反而更能看出技术考察的“稳定性”。Java基础、并发编程、JVM内存模型、网络协议、数据库索引、分布式一致性这些知识点在今天依然是后端面试的核心。技术在变框架在换但底层的原理始终是那些东西。比如阿里巴巴开源镜像站这个事现在很多开发者都会用它本质上解决的是“依赖下载慢、版本同步难”的问题。这背后涉及的是网络传输、CDN加速、静态文件存储等基础设施知识。你去看看阿里云服务器的架构、分布式存储的选型最后还是会回到操作系统、网络、分布式理论这些基础上来。我看过一些同学花大量时间刷最新的框架题反而忽视了基础。在笔试和面试面前框架可以突击学但基础太弱很难临时抱佛脚。与其追着热点跑不如把一套经典笔试题吃透。正是因为技术迭代快那些“不变的东西”才更值钱。如果你正在准备实习生招聘我的建议是找近三年的笔试题合集按知识点分类整理每道题至少做到能独立讲清楚“为什么选这个答案”编程题必须亲手敲一遍不能只看别人的解法每天花一点时间手写链表反转、LRU缓存、二分查找这类高频题保持手感和对边界条件的敏感度。面试本质上是一场持久战笔试只是第一关但它决定了你能不能拿到后续展示自己的机会。我个人在实际操作中有一个习惯每次笔试完都会把题目中涉及的所有知识点列成一个清单然后在搜索引擎里逐个搜索这些知识点的深入解析比如“阿里巴巴开源镜像站架构解析”这类文章我也会看虽然和笔试题不直接相关但能帮助我理解同样是阿里技术体系内他们关心什么、解决什么问题。这种顺藤摸瓜的学习方式比死磕题库要高效得多。最后再分享一个小技巧笔试前一天晚上做一套模拟题掐时间、用在线编译器、模拟摄像头环境。如果你只有十分钟时间准备优先复习HashMap、JVM内存模型、TCP三次握手、MySQL索引、线程池参数这五个点。它们出现在笔试题里的概率极高而且即使笔试过了面试也会接着问。把这五个点吃透你就能超过大部分对手了。
返回列表