网易云音乐的实习生招聘,说到底考的是你有没有真正动手写过Java代码。这套2018年的题目我印象很深,它不搞偏题怪题,也不考什么特别冷门的框架,所有考点都扎在Java基本功上:运算符、集合、排序、JVM、多线程。这套题的筛选逻辑其实很简单——你不是来面试架构师的,你是来写业务代码的,所以你要证明自己能把基础语法写对、把常用类的原理说清、把简单的并发场景设计明白。
作为一个Java开发实习生岗位的笔试,云音乐这套题的价值到今天也不算过时,尤其是如果你正在准备秋招或者找Java实习,拿来练手和复盘非常合适。我尽量还原这套题的考察范围、易错点以及我的解题思路,顺便把当年踩过的一些坑也一并写出来。
1. 网易云音乐Java实习岗笔试:这套题到底想筛选什么样的人
很多同学复习笔试喜欢盯着“难偏怪”的东西刷,但网易这套实习生笔试题的导向非常明确:它要的是基础扎实、能直接干活的人。云音乐的Java开发实习生,业务上和推荐、歌单、评论、用户体系这些模块强相关,所以笔试不会考你分布式中间件,也不会考微服务治理,而是把精力放在你日常写代码一定会碰到的语言细节和常用类库上。
1.1 题型分布与真实考场节奏
整套卷子的题型构成大致是这样的:
- 单选和多选混合的选择题,数量在20到30道之间,覆盖Java语法、集合框架、JVM、多线程、网络编程等。
- 手写代码题,一般是1到2道,以排序、链表、字符串处理为主,需要在线上IDE里自己处理输入输出。
- 场景设计或逻辑分析题,通常会结合云音乐的业务场景,比如歌单并发操作、点赞计数、缓存设计等。
考试时长一般是120分钟。我当时的感受是,选择题部分如果你基础够扎实,40分钟以内能过完一遍;代码题一定要留足30分钟以上,因为线上IDE没有本地IDE那么顺手,调试速度会明显变慢。很多人前面选择题磨太久,最后代码题只写了一半就交卷,这是最亏的。
网易的笔试平台用的是类似于牛客网的那套OJ系统,代码题需要完整读取输入、输出结果,不是写个方法签名就能过。平时练习的时候不要只会在IDE里跑main方法,一定要习惯用Scanner或者BufferedReader去处理标准输入。
1.2 从题目风格反推岗位要求
云音乐这个部门的技术栈以Java为主,业务上对并发量有一定要求。笔试题目里凡是出现多线程、集合并发安全相关的考点,都不是随便考考,而是对应到真实业务里的歌单并发编辑、评论高并发写入、热歌榜计数更新这些场景。
另外,网易笔试有个特点:特别喜欢考“你以为你会了,但其实你没完全会”的知识点。比如HashMap的扩容条件、枚举能不能定义抽象方法、Lambda表达式里变量捕获的限制条件。这些考点在教科书里都有,但如果你只是背概念、没有亲手写过,很容易在选项里被干扰项带偏。
所以这套题筛选的画像很清晰:基础扎实、平时动手写代码多、对Java常用类库底层有一定好奇心的人。如果你的目标就是Java开发实习生,这套题是一份非常好的对照清单。
2. 选择题里的语法死角:运算符优先级、枚举与Lambda的易错点
选择题里占比最大的永远是Java基础语法。这个部分在很多人看来是送分题,但网易偏偏喜欢在最容易混淆的地方埋坑。我在复盘这套题的时候发现,有三类语法题几乎每年都会出现:运算符与表达式求值、枚举的高级用法、Lambda表达式与函数式接口。
2.1 运算符与表达式:自增自减和移位运算的翻车现场
运算符相关的题目,考察点通常不是“你知道+和-”,而是优先级和求值顺序。比如下面这道经典题:
int i = 0; i = i++ + ++i; System.out.println(i);这道题考察的是自增自减在表达式中的求值时机。i++是先取i的当前值0参与运算,然后i变为1;++i是先将i自增为2,再取2参与运算。所以表达式的值是2,最终i的值也是2。
但这类题真正的坑不在于结果,而在于你在考场上是否足够冷静。网易的题目里还经常混入三目运算符和赋值运算符的组合,比如:
int a = 5; int b = 10; boolean flag = false; System.out.println(flag ? a++ : ++b);简单分析一下:flag为false,走++b分支,b变为11,输出11,a不变。这类题本身不难,但如果你在做题时没有把运算优先级写在草稿纸上,凭感觉选答案,很容易在++a和a++上翻车。
还有一个高频点是位运算。云音乐这种音视频业务,在权限控制、状态位标记上经常会用到位运算。选择题里常见的考法是:
int x = 8; int y = x >> 1; int z = x >>> 1;>>是带符号右移,>>>是无符号右移。正数的情况下两者结果一样,但如果是负数,>>>会把最高位补0,结果会变成一个很大的正数。考场上看清楚题目给的是正数还是负数,这种题就是送分题。
2.2 枚举类型:不只是简单的常量列表
Java枚举是笔试的常客,但很多人对枚举的理解停留在“替代常量”的层面。网易的题目会考得更深一些。比如:
enum Color { RED(1), GREEN(2), BLUE(3); private int code; Color(int code) { this.code = code; } public int getCode() { return code; } }这个枚举的定义其实包含了很多隐藏知识点:枚举可以有构造器,但构造器必须是私有的;枚举可以有成员变量和方法;枚举可以实现接口,但不能继承类,因为它已经隐式继承了java.lang.Enum。
选择题还喜欢考values()和valueOf()方法。values()返回枚举数组,valueOf(String)根据名字字符串返回对应的枚举常量,如果找不到会抛IllegalArgumentException。
另外一个容易出错的地方是枚举和switch的组合。在Java 5以后,switch支持枚举类型,case语句里不能带枚举类名,直接写枚举常量名即可。这个细节很多人写代码的时候没注意,做题就暴露了。
2.3 Lambda表达式:函数式接口的语法糖
Lambda在Java 8之后就是面试必考了。网易笔试不太会直接问“Lambda的语法有哪些”,而是把它藏在集合操作的题目里。典型考法是:
List<String> list = Arrays.asList("b", "a", "c"); list.sort((s1, s2) -> s1.compareTo(s2));这里考的是List.sort()方法接收一个Comparator接口,而Comparator是一个函数式接口,所以可以用Lambda表达式实现。
更深一层的考点是“有效final”这个概念。Lambda表达式里如果引用了外部局部变量,这个变量必须是final或者是effectively final(初始化之后不再被修改)。比如:
int base = 10; Runnable r = () -> System.out.println(base); base = 20; // 这里会编译报错这个限制的原因是Java设计者为了保证Lambda捕获的变量是稳定的,避免多线程环境下数据不一致。笔试选择题经常给你一段代码,问哪一行会编译报错,这个点年年都有。
3. 集合框架送分题与送命题:HashMap的put流程你背对了吗
集合框架是Java笔试的重头戏,网易特别偏爱HashMap。我在复盘这套题的时候发现,光HashMap一个类就能延伸出七八个考点,从数据结构到hash算法,从扩容机制到线程安全性,每一层都能出题。
3.1 HashMap的存储结构:数组加链表加红黑树
JDK 8之后的HashMap采用数组+链表+红黑树的结构。数组的每一个位置叫一个桶(bucket),当多个key的hash索引落到同一个桶时,用链表把它们串起来;当链表长度超过阈值8,并且数组长度大于等于64时,链表会转换成红黑树。
这个结构在选择题里的考法通常是给你一段代码,问某个对象经过几次比较能找到。比如hash冲突严重时,链表查询是O(n),红黑树查询是O(log n),为什么到8才转红黑树?因为红黑树的节点占用空间是链表节点的两倍左右,只有冲突足够严重时,树化的空间和时间优势才划得来。
3.2 hash方法与put流程:从源码角度理解
HashMap的hash(Object key)方法不是直接用key.hashCode(),而是把hashCode的高16位和低16位做异或运算:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }这个做法的目的是让高16位也参与到索引计算中。因为数组的默认长度是16,计算索引时用(n - 1) & hash,低位相同的hashCode在数组长度较小时冲突概率特别高。把高16位异或进来,可以打散低位,减少碰撞。
put的流程大致是:先对key计算hash,然后通过(n - 1) & hash定位到桶。如果桶为空就直接插入;如果不为空,遍历链表或红黑树,如果找到了相同的key就替换value,找不到就追加到链表尾部或者树中。插入完成后检查元素个数是否超过阈值,超过就扩容。
这个流程在笔试里可以变成很多选择题,比如“HashMap什么时候会树化”“替换value时返回的是什么”“如果key是null,hash是多少”。
3.3 扩容机制与默认负载因子为什么是0.75
HashMap的默认容量是16,负载因子是0.75。当元素个数超过容量 * 负载因子 = 12时触发扩容,容量翻倍变为32。
为什么负载因子是0.75而不是0.5或1.0?从时间和空间两个维度看:负载因子太大,比如1.0,数组利用率高,但hash冲突会变多,链表变长,查询效率下降;负载因子太小,比如0.5,冲突少查询快,但空间浪费严重。0.75是JDK作者在大量测试基础上选出来的一个均衡值,大多数场景下空间和时间的综合表现最好。
扩容的细节里有个更容易考的关联点:多线程环境下HashMap扩容可能导致死循环。JDK 7里扩容时用头插法,并发扩容容易出现环形链表,导致get操作死循环。JDK 8改成了尾插法,死循环问题得到缓解,但多线程下数据丢失和覆盖的问题仍然存在,所以并发场景要用ConcurrentHashMap。
3.4 选择题里常见的Map实现横向对比
除了HashMap,网易笔试还会把Hashtable、LinkedHashMap、TreeMap、ConcurrentHashMap放在一起考。
| Map实现 | 是否线程安全 | 底层结构 | 迭代顺序 |
|---|---|---|---|
| HashMap | 否 | 数组+链表+红黑树 | 无序 |
| Hashtable | 是,方法加synchronized | 数组+链表 | 无序 |
| LinkedHashMap | 否 | 数组+链表+红黑树+双向链表 | 插入顺序或访问顺序 |
| TreeMap | 否 | 红黑树 | 按键排序 |
| ConcurrentHashMap | 是 | 数组+链表+红黑树,CAS+synchronized | 无序 |
选择题最喜欢问的就是“哪个Map可以按key排序”——答案是TreeMap;“哪个Map可以保持插入顺序”——答案是LinkedHashMap;“哪个Map不允许null key”——答案是Hashtable和ConcurrentHashMap。这些结论看起来零散,但它们背后都有清晰的实现逻辑。
4. 手写代码题:冒泡排序和快速排序在笔试里的正确打开方式
代码题是笔试里最拉分的一项。网易这套题里的代码题没有到LeetCode Hard的程度,但也不是简单到让你默写,排序算法是出现频率最高的一类。
4.1 冒泡排序:能写对不难,能写好很难
冒泡排序的基础写法大家都会:
public static void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }但这道题真正考察的是你有没有优化意识。如果在一轮遍历中没有任何交换发生,说明数组已经有序,可以提前结束。优化版:
public static void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { boolean swapped = false; for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; } } if (!swapped) { break; } } }如果数组原本就有序,优化后时间复杂度从O(n^2)降到O(n)。这种优化在笔试判卷时非常加分,因为说明你不只是背了代码,而是理解了排序的本质。
4.2 快速排序:分治思想与边界条件
快排在笔试里比冒泡更常考,因为它考察的不只是代码,而是你对递归和分治的理解。标准写法如下:
public static void quickSort(int[] arr, int left, int right) { if (left >= right) { return; } int pivot = partition(arr, left, right); quickSort(arr, left, pivot - 1); quickSort(arr, pivot + 1, right); } private static int partition(int[] arr, int left, int right) { int pivot = arr[right]; int i = left - 1; for (int j = left; j < right; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i + 1]; arr[i + 1] = arr[right]; arr[right] = temp; return i + 1; }快排手写题最容易出问题的地方在边界条件。很多人递归的时候把left >= right写成left == right,当数组只有两个元素时就会栈溢出;还有人在partition里把j < right写成j <= right,导致pivot自己被交换。这些细节平时写代码不觉得,考场上没有断点调试,只能一遍过,必须靠平时养成的肌肉记忆。
快排的时间复杂度平均是O(n log n),最坏情况下是O(n^2)。最坏情况发生在每次partition都选到最大或最小的元素作为pivot,比如数组已经完全有序时固定选最后一个元素。笔试选择题如果问“快排最坏情况发生在什么时候”,答案就是序列基本有序且pivot固定取一端的时候。
4.3 在线笔试的输入输出坑
网易的代码题在OJ系统上判分,输入输出处理不当会导致0分。Java在线笔试常用Scanner读入数据:
Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[] arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = sc.nextInt(); }这段代码本身没问题,但要注意:如果输入规模很大,Scanner的读取速度会比较慢,用BufferedReader会更稳妥:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] parts = br.readLine().split(" "); int n = Integer.parseInt(parts[0]);另外,如果题目要求输出排序后的数组,注意输出格式是不是要求用空格分隔、最后一个元素后面不能有空格。这些细节虽然不算算法核心,但往往是笔试里真正决定你是否能通过的那几分。
5. 内存与并发考点:OOM排查思路和云音乐场景设计题
JVM和多线程在网易这套题里占的比例不低,因为云音乐这种高并发业务非常依赖这两块。选择题通常会考JVM内存分区和OOM的类型,场景题则会把并发问题包装成业务场景来问。
5.1 OOM的几种常见类型
热搜词里出现过OutOfMemoryError: insufficient memory,这个其实是Java进程向操作系统申请内存时被拒绝时抛出的错误。笔试选择题更常考的是这几类:
java.lang.OutOfMemoryError: Java heap space:堆内存不足,创建对象时堆空间已满。java.lang.OutOfMemoryError: GC overhead limit exceeded:GC回收效率极低,连续多次GC都只回收不到2%的堆内存。java.lang.OutOfMemoryError: unable to create new native thread:无法创建新的线程,说明线程数已达上限或者操作系统内存不足。java.lang.StackOverflowError:栈溢出,常见于递归没有终止条件。
选择题的考法通常是给你一段代码,让你判断会抛出什么异常。比如无限递归调用的方法会抛出StackOverflowError,而不断往List里add对象会抛出OOM: Java heap space。
5.2 云音乐场景下的内存问题:缓存对象与内存泄漏
如果场景题结合云音乐,最常见的设计是:本地缓存歌单列表、用户会话信息等,突然有一天线上服务OOM了,让你排查。
合理的排查思路是:先用jstat -gcutil看堆内存各区域的使用率,再用jmap -dump:format=b导出堆快照,用MAT或者JProfiler分析哪个对象占用了大量内存。常见的元凶是:用HashMap当缓存但只往里放不清理,导致key越来越多;或者IO流没有关闭,底层DirectByteBuffer持续堆积。
5.3 多线程场景题:歌单并发编辑与点赞计数
云音乐的场景题最具代表性的就是点赞和计数器。比如设计一个歌曲的点赞数统计服务,要求不能超卖也不能计数不准。这道题考察点很明确:
- 用
synchronized或ReentrantLock保证原子性,但锁粒度要大,影响性能。 - 更好的方案是用
AtomicInteger或者LongAdder,利用CAS保证原子性,并发度高。 - 再进一步,如果点赞量非常大,可以把计数更新操作放到消息队列里异步处理,先更新缓存,再异步落库。
这种题的思路不是让你写出唯一正确答案,而是看你能不能分析出不同方案的取舍,能不能想到缓存、异步、原子类这些常见的并发优化手段。
6. 我的实际答题顺序与复盘经验
复盘这套题,我认为答题顺序对成绩的影响被很多人低估了。网易的笔试时间不算特别宽裕,如果没有策略地从头做到尾,很容易在某一道多选题上卡太久,导致后面的代码题没时间写。
6.1 成功率最高的答题顺序
我推荐的顺序是:先快速过一遍所有选择题,把有把握的先选完,拿不准的先标记跳过;然后立刻做代码题,因为代码题分值高、写起来耗时不确定,留到最后容易慌;代码题写完后再回头做那些被标记的选择题。
这样做有几个原因:第一,选择题如果卡在某道题上,每多花一分钟都是亏损,代码题可能因为这一分钟就少写一个边界条件;第二,代码题需要清醒的头脑和充裕的时间,先写代码可以在状态最好的时候拿到高分;第三,回头再看选择题时,通常会有新的思路,因为后面的代码题可能用到了相关的知识点。
6.2 一些考场细节
网易的笔试题是多选和单选混在一起,多选题目会明确标注。做多选题时最常见的丢分原因不是不会,而是选多了或者选少了。我的习惯是:按判断题来做,每个选项单独判断对错,再和题目要求对照,看到底是“正确的有”还是“错误的有”。
代码题如果第一遍没调试通过,不要反复提交。因为OJ系统有些是实时判分并扣分的,反复提交会拉低最终分数。最好把代码在本地IDE里或者脑子里仔细走一遍测试用例,确认无误后再提交。
代码题里还有一个经典坑:题目给的示例与真实测试用例可能差异很大。比如示例是5个元素的数组排序,真实测试用例可能是10万个元素的排序,这时候冒泡排序可能直接超时。所以在笔试前把快排、归并排序这种O(n log n)的排序算法练熟,是一个性价比极高的备考动作。
6.3 给后来人的三点经验
第一,不要把复习重点放在背诵所谓的“八股文”上,背得再熟,选择题换个问法就认不出来了。更好的方式是读源码,HashMap、ArrayList、String这些常用类的源码并不算长,读一遍之后你对API背后行为的理解会完全不同。
第二,调试代码要用自己的测试用例去验证边界条件。排序算法要测空数组、单个元素、逆序数组;链表题要测只有一个节点和两个节点的情况;字符串题要测空串和超长串。这些边界条件是笔试判分的重点,也是平时练习最容易忽略的。
第三,不要把网易云音乐的这套题当成一次性测试做过就丢。做完之后把你的错题整理成一个清单,每个知识点都追问一句“为什么”,比如为什么ConcurrentHashMap在JDK 8里改用CAS加synchronized、为什么枚举的构造器必须是private。这种追问式复盘的效果,比刷十套新题都强。
我在复盘这套题时最大的感受是:网易的考题其实一直都在暗示岗位日常要做的事——写正确的语法、用对集合类、处理高并发问题、排查内存故障。如果你能顺着这些考点把对应的源码和原理吃透,通过笔试只是起点,真正入职后面对云音乐这种量级的业务,你会发现笔试里考的东西,工作中每天都在用。