1. 关注这份2015年阿里巴巴实习生笔试题,其实是在关注什么
每年三四月份,大厂实习生招聘笔试一出来,论坛上、校招群里就会炸开锅。特别是阿里巴巴这种级别的公司,笔试题一旦流出,大家第一反应不是“这题我不会”,而是“这题我居然连题干都读不懂”。2015年的阿里实习生笔试题,放在当时看确实有门槛,放在今天回头看,反而更像一份“大厂技术岗基础能力图谱”。
当时的考察范围大致可以划分为几大块:Java基础与集合框架、并发与多线程、JVM与内存模型、操作系统与网络基础、数据结构与算法、数据库与SQL、海量数据处理、智力题与开放题。你没看错,一个实习生岗位都敢考这么多东西,而且每一块都不是蜻蜓点水,而是会深入到让你怀疑自己大学四年到底学没学过。
这份笔试题对现在准备大厂校招的同学依然有参考价值,因为大厂笔试的风格虽然每年都在迭代,但底层考察逻辑并没有变。它们考查的不是你会背多少 API,而是你在面对一个从未见过的复杂问题时,能不能用扎实的基础知识拆解它、推导它、解决它。如果你正准备暑期实习或秋季校招,花一个下午把这份题吃透,远比盲目刷几十道 LeetCode 更有战略价值。
2. 整体设计思路拆解:一套笔试题是如何筛选人的
2.1 各知识板块占比与考察意图
我把2015年阿里实习生笔试题按考察方向做了整理,表格里的比例是根据当年多个岗位(Java研发、测试开发、前端等)考生回忆版题目统计出来的平均值,不一定精准,但能反映整体方向:
| 考察方向 | 大致占比 | 侧重能力 |
|---|---|---|
| Java基础与集合源码 | 20% | 语言特性、源码功力 |
| 算法与数据结构 | 25% | 编码能力、复杂度分析 |
| 操作系统与网络 | 15% | 计算机基础功底 |
| 并发与JVM | 15% | 高并发场景意识 |
| 数据库与SQL | 10% | 数据建模与查询能力 |
| 逻辑与智力题 | 10% | 思维灵活性 |
| 开放性问题 | 5% | 项目经验与系统设计意识 |
从这个结构能看出,当时的大厂实习生笔试不是单纯考算法,而是综合评估“计算机基础是否扎实 + 代码能力是否过关 + 思维是否灵活”。算法题占比最高,但也没高到一题定生死,这就意味着如果你Java基础和网络基础好,同样能在笔试中拿到不错的分数。
2.2 为什么用这套结构来筛选校招生
站在出题人的角度想,实习生招聘不像社招那样有真实项目可以考察,那么怎么在短短两小时内判断一个人值不值得进下一轮?只能靠基础题+算法题+少量开放题组合出一个多维度的“画像”。
基础题筛掉“背题型选手”,算法题筛掉“代码手残党”,开放式问题则用来捞那种有项目经验、有系统思维的“潜力股”。2015年的题目里有一个很明显的特点:很多选择题的选项长得非常像,你如果只背了结论而没有理解底层原理,很容易在两个选项之间反复犹豫,最后选错。这种设计本质上就是在考察你对知识的“理解深度”,而不是“记忆广度”。
还有一点值得注意,当时的笔试题里频繁出现“下列说法错误的是”“以下哪个选项是错的”这类否定式提问。这种出题方式很狡猾,因为大部分人在复习时习惯正向记忆,一旦题目反着问,正确率立刻下降。应对方法只有一个:在复习时就刻意训练自己“找反例”的思维,每个知识点至少要能举出一个出错场景。
3. 核心知识点深度解析:那些年反复出现的“送命题”
3.1 Java集合框架:HashMap是怎么把一群人考懵的
2015年阿里的笔试题里,Java集合方向的题目几乎绕不开HashMap。当年考的是JDK 7及之前的版本,HashMap还是“数组+链表”的结构,还没有红黑树优化。题目通常会这样出:HashMap的默认初始容量是多少?加载因子为什么是0.75?什么时候触发扩容?扩容时链表会怎样?
先说默认初始容量,答案是16,这个数字不是拍脑袋定的,而是“一个16,一个0.75”的组合能让 HashMap 在时间和空间上达到相对平衡。0.75意味着当元素个数达到容量×0.75时就会扩容,如果加载因子太小,比如0.5,空间浪费严重;如果太大,比如1.0,链表过长,查询效率急剧下降。这个知识点到现在依然是高频考点,经典问题“为什么是0.75”一定要能答出“空间时间折中”这个核心逻辑。
还有一个高频陷阱题:HashMap允许null键吗?允许,而且只允许一个null键。但Hashtable不允许null键和null值,因为Hashtable的put方法会直接调用key.hashCode(),null会抛NullPointerException。很多人把HashMap和Hashtable的区别背得滚瓜烂熟,但题目一换场景就懵,这就是典型的“只会背结论,不懂原理”。
3.2 并发编程:synchronized和volatile的区别只是入门
并发这块,2015年的笔试题已经涉及到了比较高阶的内容——不只是问你synchronized和volatile的区别,而是会让你判断一段并发代码的输出结果,或者问你在某个场景下选择哪种并发工具最合适。
这里有个典型的例子:volatile关键字能保证可见性,但不能保证原子性。题目可能会给出一个多线程对volatile变量执行i++的代码,问最终结果是否等于预期值。答案是“不一定”,因为i++这个操作在字节码层面是三步:读取、加一、写回,volatile只能保证每一步的可见性,不能保证这三步的原子性。要解决这个问题,要么用AtomicInteger,要么用synchronized,要么用LongAdder,各有利弊,题目的陷阱就在于“volatile能保证可见性”这句话本身是对的,但很多人在考场上会脑补成“volatile能保证线程安全”。
再深入一点是JMM(Java内存模型)相关的问题。当年有一道题是关于“指令重排序”的,题干给出一段双检锁单例模式的代码,问这段代码是否有问题。答案是“在JDK 5之前有问题,在JDK 5之后如果加了volatile就没问题”。原因是new对象的过程不是原子的,可能先分配内存、再赋引用、最后初始化对象,如果不加volatile禁止重排序,另一个线程可能拿到一个“半初始化”的对象。这道题到现在依然是面试高频题,值得反复琢磨。
3.3 JVM内存区域与GC:什么对象会被回收
JVM相关的题目在2015年笔试中主要集中在这几个点:内存区域划分、对象存活判断、GC算法、常见垃圾收集器。选择题经常会给出一个内存区域的名字,让你选它的作用是存什么的,以及会不会抛OutOfMemoryError。
这里有一个高频易错点:程序计数器是JVM内存区域中唯一不会出现OutOfMemoryError的区域。其它区域,比如Java堆、方法区、虚拟机栈、本地方法栈,都有各自的OOM场景。这道题当年错的人很多,因为大部分同学复习时只关注堆和方法区,忽略了程序计数器这个细节。另外,虚拟机栈和本地方法栈的溢出是StackOverflowError,而不是OutOfMemoryError,这个区别也要注意。
再就是GC root判断,什么对象可以被回收?答案是不可达对象,但什么是“可达”得说清楚:GC Roots包括栈帧中的局部变量、静态变量、常量池引用、JNI引用等。只要对象从这些根出发不可达,就会被标记为可回收。但注意,被标记为可回收不等于马上被回收,还要经过两次标记,这也是软引用、弱引用、虚引用经常出现在题目里的原因。
3.4 数据结构与算法:再难也逃不出这几种套路
算法题在2015年的笔试中占比不小,考察的重心主要是:排序、二分、链表、二叉树、动态规划、字符串处理。题目难度适中,但有个特点——很吃“边界条件”。比如一道二分查找的题,如果你没处理left和right的边界,或者没有考虑mid的取值是否可能溢出,很容易在测试用例上栽跟头。
这里给一个最经典的例子:给定一个有序数组和一个目标值,找出目标值的第一个和最后一个位置。常规解法是二分查找找到目标然后左右扩展,但最坏情况下是O(n)。更优的解法是两次二分,第一次找左边界,第二次找右边界,时间复杂度稳定在O(log n)。这道题放在2015年算中等难度,放到今天依然是面试高频题。
链表相关的题目里,反转链表、判断是否有环、找环的入口、找倒数第K个节点这几道题几乎是必练的。2015年有一道题是“判断两个链表是否相交”,最直接的解法是先分别遍历两个链表拿到长度和尾节点,然后让长的链表先走差值步,再一起走并比较节点是否相同。这个思路其实也蕴含了“双指针”的思想,属于链表题里的基本功。
二叉树相关的高频题则集中在中序遍历、层序遍历、最大深度、最近公共祖先。其中最近公共祖先这道题,如果考察二叉搜索树可以用递归快速解决,如果考察普通二叉树就得用后序遍历或者存储父节点的方式。2015年笔试考的是普通二叉树的版本,题目描述很简短,但要想做对需要先明确“如果p是q的祖先,结果就是p”这个边界情况。
3.5 数据库与SQL:索引和事务是永远的主角
数据库方向的笔试题集中在索引、事务隔离级别、SQL优化、范式这几个主题。索引部分最常考的是“最左前缀原则”——联合索引(a, b, c)能命中哪些查询条件?答案是:a、a和b、a和b和c,以及a和c(a生效但b不生效)。为什么会这样?因为联合索引的底层结构是B+树,先按a排序,a相同再按b排序,所以查询条件里没有a时,索引就无法被利用。
事务隔离级别这块,2015年的题目把四个隔离级别及各自解决的问题列成表格让考生判断,脏读、不可重复读、幻读分别被哪个级别解决。读未提交什么都挡不住,读已提交防脏读,可重复读防脏读和不可重复读,串行化全防。MySQL默认是Repeatable Read(可重复读),Oracle默认是Read Committed,这个区别也是常考的点。
SQL编写题通常会让你写一条查询,比如“查出每门课成绩都大于80分的学生”。这类题的坑点在于“都大于”不能直接写成where score > 80,因为那会漏掉某门课低于80分的学生。正确的解题思路是先找出“存在某门课小于等于80分的学生”,然后用not in排除掉。这种反向思维在SQL题里特别重要,几乎是必考思路。
4. 当年几道经典真题的逐题拆解与答案解析
4.1 经典JVM题:下面哪段代码不会抛出OutOfMemoryError
这道题给了四个选项,分别涉及:无限创建线程、无限创建字符串对象、无限递归调用方法、无限往ArrayList里添加对象。答案选“无限递归调用方法”,因为它抛出的是StackOverflowError。这个题最容易错的点在于:无限递归可能也会导致OutOfMemoryError吗?严格来说不会,栈溢出是StackOverflowError,属于Error,不属于OOM。而无限创建线程确实可能OOM,因为在创建线程时虚拟机栈会分配内存,线程数超过操作系统限制后会报“Unable to create new native thread”。
这个题背后隐含的一个知识点是,JVM的异常体系里Error和Exception是两回事,StackOverflowError和OutOfMemoryError都是Error,但它们的触发区域不同。前者是栈空间不够,后者是堆空间或方法区不够。遇到这类题,先定位“问题出现在哪个内存区域”基本就能做对。
4.2 经典多线程题:这段代码的输出是什么
题目给出一段多线程代码,某个静态变量被多个线程同时执行i++操作,问最后的值可能是多少。答案通常是“小于等于预期值,但具体是多少不确定”。原因是i++不是原子操作,多个线程同时读同一个值,再同时写回,就会丢失更新。这道题的精髓不是让你算出精确数值,而是考察你对“非原子操作”的理解深度。
实操中,这种题目的代码片段在笔试里往往没有给出synchronized和volatile,也没有用AtomicInteger,所以答案基本可以确定为“不确定”。如果你在考场上看到类似题目,先问自己三个问题:这个变量是不是线程安全的?这个操作的原子性是否保证?可见性是否保证?只要有一个不满足,答案就是不确定的。多线程题掌握这三个判断标准,至少能搞定80%的常规题。
4.3 经典算法题:给定n个数的数组,找出第K大的数
这道题在2015年的笔试中既出过选择题(问复杂度),也出过编程题。常规解法是排序后取第K个,时间复杂度O(n log n)。更优秀的解法是快速选择算法,平均O(n),最坏O(n^2)。还有一个思路是用大小为K的最小堆维护前K个最大的数,时间复杂度O(n log K),适合数据量极大且不能一次性读入内存的场景。
从题目设置来看,2015年出现这道题的意义在于“海量数据处理”方向的启蒙。如果你以为笔试只会考简单的排序,那就会在这道题上吃亏。做题时先分析数据量:如果n是几千,随便怎么排都行;如果n是几千万,堆方案就很有必要;如果n是几十亿,那连堆都得优化,可以用分桶或布隆过滤器等大数据结构的思路。
4.4 经典智力题:100层楼扔鸡蛋问题
这道题即使在今天也是高频智力题,2015年阿里笔试就出现过简化版本:两个鸡蛋,100层楼,找到鸡蛋恰好碎掉的最小楼层,最坏情况下最少扔几次?答案是14次。思路是先确定第一次扔的楼层间隔,假设第一次从第x层扔,如果碎了就逐层往上试(x-1次),所以第一次扔的层数应该满足:x + (x-1) + (x-2) + ... + 1 >= 100,解得x = 14。
这道题的妙处在于它不是一个简单的二分问题,因为鸡蛋数量有限,你不能直接二分。它本质上是一个“动态规划”的压缩模型,核心是“用有限的资源在有限步数内覆盖尽可能多的场景”。笔试时如果你能写出递推公式,比直接背答案更能体现你的逻辑能力。
5. 实操总结:怎样高效准备这样一套笔试题
5.1 按板块制定优先级,别一上来就刷题
如果你是Java方向的学生,准备这类笔试时建议按这样的优先级安排时间:算法与数据结构最优先(每天保证2小时刷题),其次是Java集合和JVM(背熟源码并理解设计思路),然后是并发编程,再然后是网络和操作系统,最后是SQL和智力题。算法决定了你的下限,基础决定了你的上限。
每个板块至少准备一个“口袋笔记”,把高频考点、易错点、通用解题模板记下来。以算法为例,二分查找模板、双指针模板、回溯模板、动态规划状态转移方程模板,这些都要做到“肌肉记忆”级别。以Java为例,HashMap的put流程、ConcurrentHashMap的分段锁机制、ThreadLocal的内存泄漏风险,这些高频考点要做到能脱口而出。
5.2 真题的价值在“复盘”,不在“背答案”
刷真题最大的误区是记答案。2015年的题目放到今天可能已经过时,但考察的知识点没有过时,所以正确的复盘方式是:做题时只看题干,不看答案,先独立思考;做完之后对照解析,看自己的思路和标准解法差异在哪里;再把错题涉及的知识点翻书补一遍,写一篇60字以内的复盘笔记。
以“HashMap为什么线程不安全”为例,如果只是背下来“因为多线程put可能导致死循环”,那换一个JDK版本你就不会了。如果你理解了链表头部插入在扩容时可能形成环,你就能自然推导出JDK 8改成尾部插入是为了降低这个风险。这个“从原理到推导”的能力,恰恰是笔试最想看到的。
5.3 时间分配与心态调整,别让一题毁掉整场考试
两小时的笔试,建议按“5分钟浏览全部题目,先做会做的,再攻难题,留10分钟检查”的节奏来。选择题里如果有2分钟内完全没思路的,先标记跳过,不要死磕。因为笔试通常有几道题就是故意放在那里拉区分度的,大家都不会做,你只要保证其他题目的正确率就能赢。
我个人的经验是:遇到不会的算法题,先写出暴力解法,再尝试优化,哪怕只能拿到部分分数也比空着强。大厂笔试题经常是“部分通过”也有分,这跟OJ不一样。尤其编程题,测试用例是分点给分的,能跑通一部分算一部分。
6. 我个人复盘这份2015年题单后的几点心得
把当年的题单重新过一遍,最大的感受是:大厂笔试的“底层操作系统”十年来没有本质变化,变的只是形式——从纸质卷子变成在线编程,从单纯的选择题变成选择+编程+测评混合,但考察的知识图谱依然是“基础 + 算法 + 思维”三位一体。
如果你现在正处于准备阶段,我建议把2015年这种经典题单当作“照妖镜”用:拿一张白纸,不看任何参考,尝试凭记忆把HashMap的扩容流程写出来,把JVM的内存模型画出来,把二分查找的边界条件写清楚。能写出来的部分就是你真正的掌握程度,写不出来的就是接下来一个月的复习重点。
还有一个小技巧:把错题按“知识板块”归档,而不是按“来源年份”归档。你会发现同一个知识点的坑,在2015年的卷子和2024年的卷子里以不同形式反复出现。本质上,出题人换了一批又一批,但他们对“一个合格工程师应该具备的基础素养”的想象,始终是稳定的。把这套素养练好,你就不是在准备某一年的题目,而是在给自己打底层的地基,这才是真题最大的价值。