
又到了一年校招季很多准备投游戏公司的同学都会做一件功课去找目标公司往年的笔试题。说实话游戏公司的Java后端岗位笔试题和其他互联网公司有挺大差别它往往不会问“你怎么设计一个秒杀系统”这种偏业务的题反而会在Java基础、数据结构、算法底层、网络通信这些硬核知识点上反复打磨。今天这篇就专门聊一份我手里收藏了很久的试卷——搜狐畅游2018年游戏开发工程师Java笔试中非游戏基础题部分。这份试卷虽然年份有点久但里面的考点设计逻辑、题目套路放到今天依然是游戏公司Java岗笔试的参考样本。无论你是刚打算投游戏公司的新人还是已经面过几轮想针对性补短板的老手这篇文章都值得仔细看完。先简单交代下这份试卷的背景。搜狐畅游是做端游、手游起家的老牌厂商旗下有《天龙八部》《刀剑英雄》等产品技术团队对服务端的要求向来不低。这类公司招聘Java开发工程师笔试阶段不太可能直接让你写游戏逻辑更多是先拿“非游戏基础题”筛掉一批底层能力不过关的候选人。所谓“非游戏基础题”其实就是通用的编程基础、Java语言特性、并发、JVM、网络、数据库这些常规考点只不过它的筛选标准比普通业务线要严格得多。1. 内容整体设计与思路拆解1.1 游戏公司笔试为何偏重“非游戏基础题”先聊一个很多同学会困惑的问题我投的是游戏开发工程师笔试试卷里却大半是Java基础题这是不是说明公司不重视游戏业务能力恰恰相反游戏公司把基础题放在前面本身就是一种效率极高的筛选策略。游戏服务端的开发场景和传统Web后端有明显差异。传统Web后端很多是CRUD加业务状态流转而游戏服务端的核心是大量玩家的实时并发操作比如同步位置、处理战斗指令、维护排行榜、管理背包道具等。这些场景对并发控制、内存占用、网络I/O的要求都很高。一个连HashMap线程安全问题都答不上来的人很难让人相信他能驾驭服务器端上千人同时在线的状态同步。所以笔试里那些看似和游戏无关的Java基础题其实都指向了游戏服务端开发最需要的能力维度。我个人把2018年这版试卷的目标拆成了三块第一确认候选人Java语法和核心API够不够扎实因为后续不管写业务逻辑还是框架代码这些都会高频使用第二确认候选人懂不懂并发和JVM底层因为游戏服是高并发、高内存压力的典型场景第三确认候选人有没有算法和数据结构基本功这部分其实是通用能力但游戏开发里不少模块比如AOI兴趣区域管理、寻路算法、技能伤害结算背后都依赖扎实的算法功底。1.2 非游戏基础题的整体结构推演虽然没有拿到一份原版完整打印卷但按当年游戏公司Java岗笔试的通行模式我可以负责任地告诉大家非游戏基础题部分基本会围绕下面几条线展开。第一块是Java语言基础包括数据类型、运算符优先级、面向对象三大特性、抽象类和接口的区别、String和StringBuilder的用法、异常体系等一般以单选、多选、判断的形式出现偶有简答题。第二块是Java集合框架涉及ArrayList、LinkedList、HashMap、HashSet、TreeMap等的底层结构和适用场景部分公司还会让你手写一个简单的LRU缓存或者基于HashMap实现的小工具。第三块是并发编程考察synchronized和ReentrantLock的区别、volatile的语义、线程池参数、ThreadLocal原理、CAS操作这块是游戏公司最看重的经常出代码分析题。第四块是JVM包括内存区域划分、GC算法、类加载过程、OOM场景分析题干常常以一段线上故障或一段有问题的代码为背景。第五块是网络与I/O重点考察TCP握手、Socket编程、BIO/NIO/AIO区别以及Netty的基本使用和线程模型因为游戏服务端底层通信基本离不开这些。把这些模块组合起来你会发现试卷信息量已经很大了。更关键的是这类试卷通常会在每道题里埋两到三个“坑”比如表面是问集合类的用法实际考察的是你对底层数组扩容机制和哈希冲突处理的理解。所以看这篇文章的时候不要只把里面的考点当知识清单要当“面试官视角的考察逻辑”来看。2. 核心细节解析与实操要点2.1 高频考点从一道典型的程序输出题说起这种试卷里最恶心人的一题就是“以下代码输出什么”。这类题目看着简单实际上能区分出到底人是背过答案还是真的理解Java的执行机制。比如下面这种public class Test { public static void main(String[] args) { Integer a 128; Integer b 128; System.out.println(a b); System.out.println(a.equals(b)); String s1 new String(abc); String s2 abc; System.out.println(s1 s2); int x 1; int y 2; System.out.println(x y 3); } }我见过不少候选人第一眼就能答出true、false、false之类但再追问一句“为什么”就卡壳了。实际上Integer这里考察的就是JVM的Integer缓存机制缓存范围是-128到127超过这个范围的Integer对象不会走缓存所以128这个值用比较比较的是引用地址结果必然是false。而String那行new出来的对象在堆上不在常量池里所以和字符串字面量用比较同样返回false。第三行那个输出考察的是字符串拼接时操作符优先级答案其实是“33”因为xy先算完结果是3再和字符串拼接。这种题目在试卷里出现的目的不是为了难为人而是直接检验候选人是不是在用自己的脑子写代码。如果你对Integer的缓存范围、对String常量池机制只是“听说过”但没有真正验证过笔试现场大概率会在这种题上失分。我的建议是准备笔试前一定要亲自把这些知识点在本地IDE里跑一遍并且能够从字节码或源码层面解释清楚。2.2 集合框架别只会背“线程安全”和“非线程安全”集合框架在游戏开发笔试里的出镜率极高但你会发现面试官很少直接问“ArrayList和LinkedList的区别”而是会把区别放到一个具体场景里比如“如果服务端要让玩家背包物品频繁增删应该用哪个集合”。这一类问题背后有一个不变的原则数据结构的选择必须贴合使用场景。我在分析这份试卷时梳理了一个自认为很实用的集合考点速查表大家在复习时可以对照自查集合类型底层结构适用场景常见考点ArrayList动态数组随机访问多、尾插多扩容机制、modCount、subList陷阱LinkedList双向链表头尾插入删除多随机访问慢、内存占用HashMap数组链表红黑树按键查值hash扰动、扩容、死循环JDK7、红黑树化条件ConcurrentHashMap分段锁/CASsynchronized并发场景读操作为何不加锁、size()实现变化TreeMap红黑树有序键值对比较器规则、自然排序这里我额外提一个笔试中容易踩的坑ConcurrentHashMap的size()方法。早期JDK版本里这个方法是先无锁遍历两次如果不一致再加锁重试所以严格说它并不是一个“强一致”的计数方法。有些公司会出这类判断题把“ConcurrentHashMap的size()方法返回精确值”当成正确说法等你选。如果你没看过源码很容易被表面上的线程安全名头带偏。2.3 并发编程从线程池参数到锁的选择游戏公司笔试最愿意考并发这个我可以说是百分之百确定。2018年的试卷虽然我没法逐字复原但从同类型题库的共性出发并发题一般聚焦在几个核心模型上。第一类是synchronized和ReentrantLock的对比。这两个都能实现锁但ReentrantLock多了可中断、可超时、公平锁等能力。笔试里常有这么一道题“多个线程争抢同一个资源要求等待时间不能超过3秒否则放弃操作你会选择哪种方案”如果你把答案写成synchronized说明你根本没考虑锁获取的超时控制因为在synchronized里线程一旦进入等待队列除了被唤醒或中断没有别的退出路径。而ReentrantLock的tryLock(long timeout, TimeUnit unit)方法正好能解决这个需求。第二类是线程池参数。一个典型的笔试场景是“你负责设计一个游戏服务器的主业务线程池玩家在线峰值约5000人每个玩家每天平均发送100条指令请你估算核心线程数和队列容量”。这个题考的就是ThreadPoolExecutor的七个参数以及你对gameserver实际请求量级的判断。我个人的经验是游戏服务器线程池不宜过大因为业务线程往往需要共享一些热数据线程一多锁竞争和上下文切换反而会拖垮性能。常见的做法是核心线程数设为CPU核数的两到四倍队列用有界队列并且明确设置拒绝策略防止任务无限积压导致OOM。第三类是volatile和内存可见性。这个考点经常结合单例模式的双重检查锁一起来考比如让你分析“为什么双重检查锁的单例需要加volatile”。答案是对象创建过程在字节码层面不是原子操作可能会发生“分配内存空间、将引用指向内存、初始化对象”三个步骤的重排序不加volatile就可能让另一个线程拿到一个未完成初始化的对象引用。这种题既考线程知识又考JMM属于典型的综合题。2.4 JVM与内存以“OOM场景分析”为代表性的题型游戏公司在笔试里对JVM的考察通常不会像专业JVM调优岗位那样考到非常底层的GC日志分析但基本的内存区域和GC算法一定会涉及。2018年那批试卷的常见操作是给出一段代码问这个操作会导致哪个内存区域抛出OutOfMemoryError或者让你判断一个堆内存持续上涨的线上问题可能由哪些原因导致。举个例子下面这道风格非常典型的题目Listbyte[] list new ArrayList(); while (true) { list.add(new byte[1024 * 1024]); }这题本身没什么深度但换个问法就有意思了“如果这段代码运行一段时间后抛出java.lang.OutOfMemoryError: Java heap space请问最直观的排查思路是什么”很多候选人会直接说“把堆调大”。但笔试想考察的不是这个而是你有没有排查问题的路径。我给的参考思路是先通过jmap或者jstat确认堆使用率再用jmap -histo查看对象分布如果发现byte[]实例数量异常需要进一步看引用链找到是谁在持有这些对象。若是在实际业务环境里还要优先判断是不是某个玩家缓存、排行榜数据没有清理而不是第一时间就调JVM参数因为很多内存问题根源在代码不在配置。另外JVM还有一个容易被忽视的考点对象在堆外的分配也就是直接内存。Netty默认使用堆外内存做缓冲区如果服务端用了Netty但不注意释放可能会报OutOfMemoryError: Direct buffer memory。这类问题在游戏服务端的推送、网关模块里很常见笔试如果出现“直接内存溢出该怎么排查”这类题你能答出“用-XX:MaxDirectMemorySize参数限制并检查是否有未释放的ByteBuffer”就说明你确实踩过坑。3. 实操过程与核心环节实现3.1 笔试现场的时间分配与答题顺序我帮不少同学做过笔试复盘发现一个很普遍的问题在非游戏基础题部分耗时过多导致后面的编程题或游戏基础题草草收场。2018年这套试卷给我的感觉是题量中等偏大综合性很强如果前面选择题每道都花两三分钟抠细节后面基本就危险了。所以时间分配很关键。我建议的通用策略是先花三到五分钟浏览整个非游戏基础题部分的题型分布把分数高、答题快的题目先做掉。比如选择题和判断题中那些直接考概念、你一眼能确定答案的果断先画答案需要推演的程序输出题放在第二轮做最后集中精力对付简答题和编程题。编程题就算没完全写对也一定要把思路写清楚很多阅卷人会看答题思路给步骤分留空白是最大的浪费。另外一个小技巧如果笔试平台支持在线运行代码务必在提交前把编译错误全部清掉。我自己见过不少候选人思路正确但代码里有一个分号错误导致编译失败直接零分。笔试环境下不要追求写得多华丽稳定、可运行比什么设计模式都重要。3.2 一份接近真题风格的编程题手把手演示非游戏基础题的编程题部分最常出现的题型是“设计一个支持LRU缓存的类”。这道题几乎是游戏公司Java岗笔试的常青树因为缓存设计本身就贴近实际游戏业务。玩家的角色信息、公会列表、邮件数据都需要做缓存。我拿这道题做一个完整的实操演示。要求一般是实现一个固定容量的LRU缓存支持get和put操作get或put都算一次访问访问过的数据要放在最前面超出容量时淘汰最久未使用的数据。要求get和put的时间复杂度都是O(1)。如果用LinkedHashMap那是最取巧的实现笔试时未必能拿高分因为面试官想看你对哈希表和双向链表的综合掌控力。这里我给出一个手写版本import java.util.HashMap; import java.util.Map; public class LRUCacheK, V { private static class NodeK, V { K key; V value; NodeK, V prev; NodeK, V next; Node(K key, V value) { this.key key; this.value value; } } private final int capacity; private final MapK, NodeK, V map new HashMap(); private final NodeK, V head new Node(null, null); private final NodeK, V tail new Node(null, null); public LRUCache(int capacity) { this.capacity capacity; head.next tail; tail.prev head; } public V get(K key) { NodeK, V node map.get(key); if (node null) { return null; } moveToHead(node); return node.value; } public void put(K key, V value) { NodeK, V node map.get(key); if (node ! null) { node.value value; moveToHead(node); return; } if (map.size() capacity) { NodeK, V removed removeTail(); map.remove(removed.key); } NodeK, V newNode new Node(key, value); map.put(key, newNode); addToHead(newNode); } private void addToHead(NodeK, V node) { node.next head.next; node.prev head; head.next.prev node; head.next node; } private void moveToHead(NodeK, V node) { node.prev.next node.next; node.next.prev node.prev; addToHead(node); } private NodeK, V removeTail() { NodeK, V node tail.prev; node.prev.next tail; tail.prev node.prev; return node; } }写这个题的时候有几点要注意。第一head和tail两个哨兵节点能省去大量判空逻辑这是链表题里很实用的技巧第二map里存的是key到节点的映射所以即使value变化引用关系仍然有效第三删除尾节点时一定要先拿到被删除节点的key否则无法从map中移除对应映射。很多候选人栽在最后一点上因为只更新了链表结构忘了同步map。这道题如果在笔试现场能15分钟内写完并且和面试官讲清楚“为什么是O(1)”、以及“哪些地方容易出错”几乎就是保送进下一轮的分数。3.3 网络编程题的实际解答思路游戏公司Java服务端离不开Socket和Netty所以笔试中网络部分通常不会太浅。2018年的试卷里这类题的典型形态是“请描述TCP三次握手和四次挥手的过程并说明TIME_WAIT状态产生的原因和影响。”这个题看着像基础八股文但游戏公司会把场景包装得很具体。比如“某游戏服务器有大量短连接请求在高峰期有大量连接处于TIME_WAIT状态导致端口资源被占用。你如何分析和解决”如果你的回答只停留在“调整TIME_WAIT等待时间”这一个方案肯定不够。更完整的思路是先确认到底是不是客户端主动关闭连接导致的TIME_WAIT聚集如果是就看能否改成连接复用如果短连接确实无法避免再考虑调整内核参数比如tcp_tw_reuse和tcp_tw_recycle但要注意tcp_tw_recycle在NAT环境下会有兼容性问题今天很多内核版本已经废弃了这个参数。另一个网络编程常考点是BIO和NIO的区别。这个一般结合游戏服务器场景提问“服务器需要支撑一万个客户端的长连接每个连接每秒钟可能只发一条心跳包你会选择BIO还是NIO”答案肯定是NIO因为BIO是“一线程一连接”一万个连接就要一万个线程线程切换成本会直接把CPU拖垮。NIO基于Selector事件驱动一个线程可以同时处理大量连接的读写事件。如果你能进一步提到Netty的Reactor线程模型说明你真的做过高并发网络编程。4. 常见问题与排查技巧实录4.1 笔试题里的“语言陷阱”到底怎么躲这类试卷中经常有文字游戏式的选择题比如“以下关于final关键字说法正确的是”这种。你看着是在考final一个点实际上同时考了final修饰变量、修饰方法、修饰类还要区分“编译期常量”和“运行期赋值”的差异。举个例子final修饰基本类型变量且赋值是字面量那它就是编译期常量可以直接参与编译期运算如果final修饰的是对象引用那只是引用不能变对象本身的属性是可以修改的。这种细节如果没试过很容易选错。再比如static final String拼接和普通final String拼接结果在字节码层面会不一样。这种题没有速成办法必须靠平时敲代码积累。我总结了一个很笨但有效的方法把所有一知半解的知识点写成“反例”代码在本地跑一遍看报错信息或者输出结果。比如String和Integer的混合比较如果你自己跑一遍见过真实输出考场上就不会凭感觉猜答案。4.2 编程题失分的六个高频原因编程题是笔试的重头戏也是失分最集中的地方。我自己梳理了六个高频失分点大家备考时可以重点自查。第一不读清楚输入输出格式。有些候选人代码逻辑完全正确但方法签名和题目要求不一致导致跑测试用例直接失败。第二边界条件处理不完整。数组为空、参数为null、容量为0或负数这些边界情况经常被出题人单独设置测试用例。第三复杂度不达标。题目明确要求O(n)结果你写了个O(n²)就算答案对分数也会被扣掉一截。第四没有写注释也没理清思路导致阅卷人看不清你的逻辑层次特别是复杂题建议先写核心思路再写代码。第五用了不熟悉的高级API结果API参数记错运行时报错。笔试时尽量用你最有把握的写法不要炫技。第六没有提前自测。如果平台允许本地运行哪怕只是简单跑几个用例也能帮你拦住明显错误。4.3 结合游戏场景的JVM排查题解法这种题最典型的形态是“某款游戏上线后玩家反馈偶尔出现卡顿查看监控发现Full GC频繁请问怎么定位”很多候选人一上来就聊G1、ZGC但实际上笔试官想听的是排错路径。我建议的答题框架是先收集GC日志确认Full GC的触发频率和持续时间然后用jstat看老年代和元空间的使用率如果老年代持续增长再用jmap -histo:live看看存活对象的大头是哪几个类。游戏场景中Full GC频繁往往是缓存了太多玩家数据比如玩家对象、邮件列表、排行榜快照。最常见的解决办法是给缓存加过期策略或者把大的缓存拆分成多个小的降低单次GC扫描压力。切忌一上来就调整垃圾收集器那不是工程解法而是配置赌博。这种题我在准备时会反复问自己一个问题如果线上出故障我按这个排查步骤能最快找到根因吗如果答案是“能”那说明思路是通的。笔试时把每一步排查的目的、使用工具、可能结论都写出来基本就能拿全分数。4.4 数据库题目的游戏化包装2018年的试卷还有一个有意思的特征就是数据库题往往被包装成游戏运营场景。比如“玩家充值日志表有上亿条数据现在需要按角色ID查询充值总额请你设计索引和SQL”。这道题看似普通实际考了索引最左前缀原则、覆盖索引、聚合查询优化等多个知识点。正确做法是优先建立(role_id, amount)的联合索引这样查询时只需要扫描索引树不需要回表速度更快。SQL大概是“SELECT role_id, SUM(amount) FROM recharge_log WHERE role_id ? GROUP BY role_id”——如果你已经按role_id过滤了甚至可以直接去掉GROUP BY。如果你还能想到“充值流水表按天分表只保留近期热数据历史归档到冷表”那这道题的答题层次就完全不一样了面试官会觉得你是有线上经验的。数据库题在游戏公司笔试中不算最核心但绝对不能完全放弃因为游戏后端始终绕不开玩家数据存储。掌握分库分表、索引优化、慢查询排查这些基础能让你在候选人中立刻拉开差距。5. 备考路线与经验心得5.1 针对笔试的Java基础复习顺序建议如果你现在才开始准备游戏公司的Java岗笔试我建议你不要一头扎进“八股文清单”里死记硬背而是按下面这个顺序来复习。第一阶段是Java语法和核心类库重点看集合框架、String/StringBuilder/StringBuffer、异常体系、泛型、反射时间大概花三到五天目标是看到一道代码题能快速判断输出。第二阶段是并发编程重点看JMM、synchronized、ReentrantLock、volatile、CAS、ThreadLocal、线程池原理这个阶段建议配合源码一起看至少把ThreadPoolExecutor的execute方法流程走通。第三阶段是JVM重点看内存区域、对象创建过程、GC算法、类加载机制、常用排查命令。第四阶段是网络和I/O重点看TCP/UDP、Socket、BIO/NIO/AIO、Netty基础。第五阶段是数据库与数据结构算法数据库看索引和事务隔离级别算法刷二叉树、链表、动态规划、回溯到能独立实现LRU、快排、二分查找的程度。这套顺序也不是我拍脑袋定的。游戏公司笔试题的特点是“广而不深”每块都考但没有哪一块要求你达到专家的深度。所以复习时以广度优先、重点突破为主线反而性价比最高。5.2 笔试之后的面试追问点最后提醒你一点笔试只是第一关真正决定你能不能拿Offer的往往是面试时的追问。2018年这套试卷的很多题目如果你在笔试时只写了结论面官大概率会在面试时追问一层“为什么”。比如笔试考了HashMap的put流程面试官就会追问“什么时候会转为红黑树”“为什么阈值是8”“为什么红黑树节点在小于6时会退化成链表”这些问题不是八股文背诵而是在考察你有没有真正思考过HashMap的工程权衡。8和6这两个数字之间保留了缓冲余地防止元素在阈值附近频繁增删导致链表和红黑树反复切换。如果你能理解到这一层面官就会觉得你确实研究过源码。再比如考到线程池面试官很容易追问“线程池的核心线程数怎么设置最合理”这时要结合CPU密集型和I/O密集型的区分来回答并且给出“N1”和“2N”这类常用经验公式。这些内容笔试时未必能写进去但面试时一定要能补充出来。5.3 一套历年高频题自查清单为了帮助大家做最终自查我整理了一份游戏公司Java岗笔试高频题清单。你可以逐条自测如果有一半以上能脱口而出答案那说明准备得比较充分了。HashMap在JDK7和JDK8中的实现差异是什么ConcurrentHashMap在JDK8中如何保证线程安全volatile和synchronized的区别以及各自的使用场景是什么ThreadPoolExecutor的核心参数有哪些拒绝策略有哪几种逃逸分析和栈上分配是什么它们如何影响GC强引用、软引用、弱引用、虚引用的区别是什么TCP的TIME_WAIT为什么存在大量TIME_WAIT如何处理NIO中Selector的底层实现机制是什么数据库的索引为什么使用B树而不是红黑树如何设计一个支撑海量玩家在线状态维护的系统这份清单看起来不难但要每题都答得很有深度没有几个月持续积累是做不到的。如果你已经把大部分题目都理解到位那这份2018年的试卷对你来说应该只是一个“热身”。事实上越是这种有点年份的试卷越能看出一个公司在选拔工程师时最本质的诉求不看你背了多少热门框架只看你的基本功能不能扛住真实业务场景的打磨。以我个人的经验准备这类笔试最忌浮于表面。你多写一行代码、多跑一次测试、多问一个为什么考场上就多一分笃定。游戏开发的世界从来不是靠灵光一现而是靠扎实底子支撑起来的。把这些基础题吃透再去琢磨游戏业务逻辑你会发现自己进入状态的速度远比想象中快。