奇安信秋招Java试卷2解析:从集合并发到安全编码的备考指南 2020年奇安信秋招Java方向试卷2是我在准备秋招那会儿印象很深的一套题。当时投了好几家做安全业务的厂商奇安信的笔试风格算是比较有代表性的开头是Java基础和集合并发的题目中间会混进安全编码场景后面又回到算法和SQL整体难度不算特别高但覆盖面很宽稍不注意就会在埋了细节的题上翻车。这篇文章与其说是“试卷解析”不如说是我把当时复习这套题时踩过的坑、补过的知识点以及面试被追问的点重新梳理了一遍。如果你正在准备Java方向的秋招或者对安全厂商的Java笔试题型感兴趣这份内容应该比单纯刷题更有点参考价值。1. 秋招Java方向试卷到底在考什么——先看整体逻辑1.1 试卷结构和题量的一般判断2020年的奇安信秋招Java方向试卷我记得是分成了客观题和编程题两大部分。客观题里会有单选、多选和判断题考察对象很明确Java基础语法、集合源码、并发、JVM、数据库、网络协议以及一部分安全相关的常识。编程题一般不会太偏门集中在数组处理、字符串操作、排序和简单算法设计上。这个结构和很多互联网公司的Java笔试差异不大区别在于它会在客观题中嵌入安全编码的考点这是安全厂商笔试的明显特征。题量方面客观题大概在30到40道左右答题时间一个小时上下。如果想把题做完平均每题只有一分多钟。这里就有一个很现实的问题好多同学在集合和并发这类题目上纠结太久导致后面安全题和编程题没时间写。我当时复习这套卷子的时候给自己定的策略是“客观题控制在40分钟内编程题留足20分钟”。这个时间分配看起来简单但真的上了笔试环境会很管用因为集中精力做客观题时大脑的切换成本比想象中高。1.2 为什么安全公司也要考泛Java八股不少准备秋招的同学会有一个疑问我面的是安全公司的Java岗位为什么也要像面互联网大厂一样背HashMap、JVM和线程池这个问题的答案其实和安全公司的业务形态有关。奇安信这类企业虽然以安全产品为核心但产品底座依然是大量Java服务比如各类管理平台、扫描引擎的调度端、威胁分析系统的后端很多都是Java技术栈。安全能力最终要依赖稳定的后端服务来承载所以Java基础不扎实后续做安全产品研发一样会出问题。另外安全公司对代码的理解深度是有额外要求的。常规业务开发可能只需要知道怎么写而安全方向的开发人员经常需要知道“这段代码为什么能被打穿”“这个输入为什么不能直接拼接进SQL”。要达到这个理解层次对Java底层机制、序列化、类加载、反射、集合扩容等机制就不能只停留在API层面。所以这类试卷里出现“泛Java八股”不是简单的凑题而是在筛选候选人的技术底层是否扎实。1.3 试卷2和试卷1的定位差异严格来说奇安信在不同批次的秋招里会出多套卷子试卷1和试卷2在题库上有一部分重叠但侧重点会有差别。从我自己和身边同学复盘的情况看试卷1更偏数据结构和基础算法像链表反转、二叉树遍历这类题目出现频率更高试卷2则会明显加重安全编码和Java Web方向的比重比如输入校验、路径遍历、加密算法选择、认证授权逻辑。如果只刷了一套题就上考场遇到另一套卷子时很容易出现手感不对的情况。所以准备这类笔试不能只盯着某一份卷子上的原题。最好按“Java核心 安全编码 基础算法 数据库”四个板块去铺开复习。下面这些内容就是我当时从试卷2的考察方向上拆出来的重点也是我认为最值得展开聊的部分。2. Java基础与集合考点从“背八股”到“有逻辑地答”2.1 HashMap的put流程和扩容细节奇安信这套卷子里HashMap几乎是必考的。常见问法是给出一段代码问某个操作的时间复杂度或者问两个相同key插入后会发生什么。更深一点会问JDK 1.7和1.8在扩容上的差异以及为什么在JDK 1.8中引入红黑树。很多人在背HashMap时只记得“数组加链表”但一旦被问到细节就乱了。我把HashMap的put流程整理成了一个记忆链条先计算key的hash做扰动处理然后定位到数组下标如果当前位置为空就直接放入不为空就遍历链表或树找到相同key就替换找不到就新增节点链表长度超过阈值默认是8并且数组长度达到64就转红黑树添加完成后检查size是否超过扩容阈值超过就按两倍扩容并重新计算元素位置。这里最容易被考的点是“为什么链表转红黑树的阈值是8”。这其实来自泊松分布在随机hash的情况下链表节点数达到8的概率已经极低约千万分之一所以8是一个安全和性能的折中值。面试官追问的时候如果你能说出这层统计含义会显得你真看过源码而不只是在背图。JDK 1.7和1.8的区别也经常出成多选题。比如1.7先扩容再插入1.8先插入再扩容1.7用头插法1.8用尾插法1.7扩容后需要重新hash1.8通过高位运算判断元素留在原位置还是移动到“原位置加旧容量”的位置1.7扩容时多线程环境可能出现环形链表1.8里这种问题依然不能完全靠数据结构避免还是要用ConcurrentHashMap。这些点如果用表格来记会清晰很多。对比项JDK 1.7 HashMapJDK 1.8 HashMap数据结构数组 链表数组 链表 红黑树插入顺序头插法尾插法扩容时机先扩容再插入先插入再扩容节点位置计算重新hash通过高位运算判断原位置或原位置旧容量多线程安全不安全可能出现环形链表线程仍不安全需用ConcurrentHashMap2.2 并发题synchronized、volatile与线程池参数计算并发是另一个重头戏试卷2里我记得出现了synchronized和ReentrantLock对比、volatile的可见性、线程池参数计算这些题。synchronized和ReentrantLock的考点相对固定synchronized是JVM层面实现的ReentrantLock是JDK层面实现的前者不需要手动释放锁后者必须在finally块中unlock前者不可中断后者可以通过lockInterruptibly实现可中断等待ReentrantLock还支持公平锁、非公平锁和多个条件队列。volatile则在热词里反复出现因为Java并发面试题里它很难绕开。volatile的核心是可见性和禁止指令重排但它不保证原子性。试卷里常见的陷阱是用volatile修饰一个int变量然后多个线程执行i结果依然不正确。原因很简单i是“读-改-写”三步操作volatile只能保证读和写对其它线程可见无法把三步合成一步原子操作。想要原子性要么用AtomicInteger要么用synchronized或者LongAdder。线程池参数计算也是容易丢分的地方。核心线程数、最大线程数、空闲存活时间、阻塞队列容量、拒绝策略这五个参数要能看懂还要会算。笔试里常见的是“给定CPU核数和任务类型估算核心线程数”。如果是CPU密集型任务核心线程数一般设置为CPU核数加1如果是IO密集型任务可以设置为CPU核数乘以2再加1当然这只是一个经验值具体还要结合任务阻塞比例来调整。我当时总结了一套快速判断思路先看任务类型CPU密集就少开线程IO密集就多开再看队列类型是有界队列还是无界队列最后看拒绝策略AbortPolicy是抛异常CallerRunsPolicy是让提交任务的线程自己执行DiscardPolicy是直接丢弃DiscardOldestPolicy是丢弃最老的任务。笔试里只要把这四点想清楚线程池的题基本不会错。2.3 JVM内存区域与OOM问题的处理思路JVM的题在试卷2里属于“中规中矩但必须会”的类型。考察最多的是内存区域划分、类加载过程、垃圾收集算法和OOM的排查。热词里出现了“java: outofmemoryerror: insufficient memory”这类问题在实际开发里确实很常见笔试也会把它包装成场景题来问。内存区域划分比较简单堆、栈、方法区在JDK 8后是元空间、程序计数器、本地方法栈。堆里再分新生代和老年代新生代里又分Eden区和两个Survivor区。默认比例一般是Eden:Survivor1:Survivor2 8:1:1对象优先在Eden分配Minor GC后存活的对象进入Survivor年龄足够大的对象进入老年代。如果大对象直接进入老年代可以通过-XX:PretenureSizeThreshold参数设置。OOM的排查是我面试时被追问比较多的点。笔试里一般会问堆内存溢出和栈溢出有什么区别前者通常是对象太多或内存泄漏报java.lang.OutOfMemoryError: Java heap space后者通常是递归调用太深或栈帧太大报java.lang.StackOverflowError。如果是元空间溢出会报Metaspace。实际的排查思路是先用jps找到进程ID再用jstat看GC情况用jmap导出堆dump最后用MAT或者VisualVM分析是哪个对象占用了大量内存。在这个环节不要只背命令要把排查链路说完整面试官会顺着往下追问。3. 奇安信Java方向笔试题中的安全底色3.1 输入验证和路径遍历这道题其实在考代码审计思维热词里有一条“奇安信 输入验证路径遍历”这个点和试卷2里安全编码题的出题方向高度一致。Java Web开发里路径遍历是一个典型的高危漏洞攻击者通过构造../这样的序列让程序读取到意料之外的文件。这套卷子里很可能有类似这样的代码片段String fileName request.getParameter(fileName); String basePath /data/upload/; File file new File(basePath fileName);乍一看好像没什么问题但如果fileName被传成../../etc/passwd那么最终拼出来的路径就是/data/upload/../../etc/passwd在Linux系统里会解析到/etc/passwd。这类题考的不是复杂的漏洞利用而是你有没有在写代码时意识到“用户输入是不可信的”。修复方案也常作为问答题出现。最简单的做法是使用Java 7之后的Paths和Files接口做路径规范化处理String fileName request.getParameter(fileName); Path basePath Paths.get(/data/upload/).toAbsolutePath().normalize(); Path targetPath basePath.resolve(fileName).normalize(); if (!targetPath.startsWith(basePath)) { throw new IOException(非法路径); }这样先把目标路径规范化再判断是否仍然位于基础目录之内能从根上规避路径遍历问题。笔试答题时如果能写出这个方案并且解释清楚normalize和startsWith的作用得分会明显高于只写“要过滤../”这种一句话答案。3.2 加密算法选择题对称、非对称和哈希如何选安全公司笔试里加密算法的选择题几乎必出。常见考点包括DES和AES是对称加密RSA和ECC是非对称加密MD5和SHA是哈希算法对称加密加密速度快但密钥分发困难非对称加密安全性更高但性能开销大哈希算法不可逆适合做密码存储和完整性校验。这里容易踩坑的是把Base64当成加密算法。Base64只是一种编码方式它是可逆的并且没有密钥概念所以不能用于保护数据。试卷里如果问“以下哪个适合用于密码存储”正确答案通常不是MD5而是加盐的哈希算法比如BCrypt、PBKDF2或者scrypt。如果只答MD5会被认为安全意识不够因为MD5在彩虹表面前基本上等于裸奔。另外还会出现“HTTPS使用了什么加密方案”这类题目。答案是“混合加密”握手阶段使用非对称加密协商对称密钥通信阶段使用对称加密传输数据。这个设计的原因也经常被追问——如果全程用非对称加密性能太差如果全程用对称加密密钥无法安全传输。混合加密是性能和安全性折中后的通用方案。3.3 Java Web安全漏洞SQL注入、XSS、反序列化安全编码题不会只停留在理论试卷2里很可能把SQL注入和XSS放到代码片段里考察。比如给一条MyBatis的SQL问怎么写法会导致注入。最常见的错误用法是${}拼接参数select idqueryUser resultTypeUser SELECT * FROM user WHERE name ${name} /select正确的做法是使用#{}占位符由预编译来处理参数这样即使传入的内容包含单引号或SQL关键字也不会被当作SQL结构执行。这个点对安全方向尤其重要因为SQL注入已经存在了二十多年但很多Java项目里还是能看到类似写法原因无非是图省事或者对框架的预编译机制理解不透彻。XSS的题目相对简单一般会给一个直接把用户输入拼到HTML页面的示例问怎么防止。答案围绕在输出编码和输入过滤上比如使用OWASP Java HTML Sanitizer或者在前端对特殊字符做编码。反序列化则是Java安全里比较进阶的考点问到的话会要求你说出反序列化漏洞的原理Java在反序列化时会调用readObject方法如果类路径中存在可利用的Gadget链攻击者构造恶意数据就能触发任意代码执行。这类题目很难彻底展开但至少要知道“ObjectInputStream.readObject处理不可信数据是危险的”。4. 高频算法与数据库实操题如何快速拿分4.1 排序题冒泡排序和快速排序为什么年年必考在热词里“冒泡排序java”和“快速排序java实现”都出现了这说明它们在2020年前后的Java笔试里是刷屏级的存在。奇安信这套试卷里编程题如果考排序通常不会只要求写出来还会问时间复杂度和优化思路。冒泡排序是最容易上手但效率最低的排序之一时间复杂度O(n^2)。笔试里考它更多是看基础代码能力是否稳定。快速排序在试卷里出现的概率更高因为它综合考察了递归、分治和数组操作。我自己当时默写过很多遍的快排实现是这样的public 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 int partition(int[] arr, int left, int right) { int base arr[left]; int i left; int j right; while (i j) { while (i j arr[j] base) { j--; } while (i j arr[i] base) { i; } if (i j) { swap(arr, i, j); } } swap(arr, left, i); return i; }重点是要能说清楚为什么这个版本在数组本身有序时会退化到O(n^2)以及怎么用“随机选择基准元素”或者“三数取中”来优化。笔试里代码写对只是及格能讲清优化方向才是亮点。4.2 SQL题索引、事务隔离级别和慢查询优化数据库题在试卷2里一般占一到两道题常见考法包括看一段SQL判断索引是否生效设计一条SQL查出某张表里成绩排名前十的学生事务隔离级别有哪些分别解决什么问题InnoDB的锁机制是什么。这些内容在奇安信的笔试里不算难但很考察基本功。索引失效的场景是高频失分点。比如对索引列使用函数或者对索引列做隐式类型转换都会导致索引失效。我在复习时列过一个表格来加强记忆。索引失效场景示例原因对列使用函数WHERE YEAR(create_time)2020索引存储的是原始值无法直接用于函数计算后的结果匹配隐式类型转换WHERE phone13800138000字符串列与数字比较时发生转换索引失效左模糊查询WHERE name LIKE %java%B树索引走不了前缀匹配OR连接非索引列WHERE age20 OR status1优化器可能选择全表扫描事务隔离级别的题也容易和Spring事务注解结合起来考。四个级别读未提交、读已提交、可重复读、串行化。MySQL InnoDB默认是可重复读但通过MVCC实现所以不会出现不可重复读的问题还会有当前读和快照读的区别。如果笔试题目问“在可重复读隔离级别下两个事务同时更新同一条记录会发生什么”答案不是“都成功”而是“后一个更新操作会被阻塞直到前一个事务提交”。4.3 场景设计题登录鉴权、日志脱敏怎么答不跑偏有些笔试环节会有一两道场景设计题比如“设计一个登录鉴权方案”或者“如何对系统日志做敏感信息脱敏”。这种题看起来开放但安全公司的评分标准里其实有一条隐藏的逻辑要体现对安全风险的理解。登录鉴权方案常规答案是Session、Token或者JWT但安全视角下要补充细节。比如密码不能明文存储需要用BCrypt加盐哈希登录接口要加验证码或者速率限制防止暴力破解Token要设置过期时间刷新Token要绑定设备信息敏感操作要校验二次认证。能把这些点说出来说明你不是只会调框架API而是真的想过系统被人攻击时的应对方式。日志脱敏是安全公司很看重的实操能力。简单说就是不能把身份证号、银行卡号、手机号、密码明文写进日志。实现方式可以在日志框架层面做自定义Converter也可以在JSON序列化时对字段做脱敏处理比如手机号只保留前3位和后4位中间用星号代替。笔试里遇到这类题不要只写思路最好直接给出一个简单的脱敏函数逻辑让阅卷人看到你落地的能力。5. 一套可复用的备考清单与刷题策略5.1 按优先级分配复习时间别在冷门知识点上耗太久经过这套卷子的洗礼我对秋招Java方向复习优先级的判断是比较明确的。第一优先级是Java基础语法、集合框架、并发、JVM这些几乎每场笔试都会遇到属于送分题和拉分题的交集。第二优先级是Spring、MyBatis、MySQL、Redis这是Java后端日常开发的核心栈笔试和面试都会频繁出现。第三优先级才是安全编码、网络协议、Linux命令、设计模式这些偏场景化的知识。时间分配上我建议按 5:3:2 的比例来安排。五成时间放在Java基础和并发JVM上因为这部分重复出题的概率最高三成时间放在数据库和框架上配合项目经验一起复习两成时间留给安全编码和算法练习特别是你目标公司是安全厂商时这部分的性价比其实很高。很多人备考时花大量时间刷冷门排序算法的变体结果连HashMap的红黑树扩容条件都说不清楚这在秋招里是很不划算的。5.2 推荐资料和工具尽量把时间用在刀刃上技术书籍方面《Java核心技术卷I》适合快速过一遍基础重点看集合和并发章节。《深入理解Java虚拟机》是JVM题目的答案来源但不建议整本通读可以只看内存区域、垃圾收集、类加载三个章节。《Java并发编程的艺术》这本书对线程池和锁的讲解很清晰适合当工具书查阅。如果基础比较好直接看官方文档和源码就够了源码优先看HashMap、ArrayList、ConcurrentHashMap、ThreadPoolExecutor这几个类。刷题平台可以用牛客网上面有很多Java笔试真题和面经能提前适应在线笔试题的答题形式。算法题用LeetCode也没问题但不用追求题量把数组、字符串、链表、二叉树、排序这些高频题型练熟比刷三百道偏题有用得多。SQL题可以去LeetCode的数据库题库做几道中级难度的题大多数笔试的SQL题不会超过这个范围。5.3 应试技巧遇到不会的题如何尽可能拿分笔试和面试不一样笔试的评分往往更机械对就是对错就是错。但有些题即使不能完全做对也可以通过答题技巧拿部分分。比如程序填空题先根据上下文推断空位需要的类型和逻辑然后把整体结构补全哪怕有一点小错阅卷人如果按步骤给分也能拿到不少分。编程题一定要先写核心逻辑再处理边界条件。很多同学喜欢先把读输入和输出的代码写完最后才写算法主体结果时间不够只交了一个空壳。我的习惯是先花一分钟看清题目要求然后用最暴力的方法把功能实现出来之后再考虑优化。这样至少能过一部分测试用例不会整题挂零。还有一个容易被忽略的点是笔试环境里的编译器和本地的IDE有差异。Java版本不同某些语法可能不兼容比如JDK 8和JDK 11在局部变量类型推断上就有区别。考前看清楚笔试系统支持的Java版本尽量用兼容性高的写法避免因为语法问题导致编译失败。最后再分享一点个人体会我后来复盘这套2020年奇安信秋招Java方向试卷2时最大的感受是它更像是一面镜子照出你在Java这条路上到底走了多深。如果只是平时写业务代码HashMap的扩容细节、线程池参数的组合逻辑、路径遍历的修复方式都是不太会主动接触的东西。但秋招笔试偏偏会把这些内容摊开来考因为大厂和安全厂商都需要筛选出有主动钻研能力的人。当时我为了搞懂ConcurrentHashMap在JDK 1.8里为什么舍弃了分段锁硬是把源码看了三遍后来面试官追问put流程的细节时我反而不紧张了。建议你现在开始就把手头的项目代码往回翻一翻看看集合有没有用对、线程池参数是怎么设置的、文件上传的路径校验是否安全这些都是比死记硬背更高效的复习方式。