
1. 这套卷子到底在考什么岗位画像与五大知识模块2017年秋天我投了用友的Java开发岗在线笔试系统里一共分了五套卷子第五套就是这套“用友2017秋招笔试题五”。当时做完整个人是有点懵的倒不是题有多难而是它的出题风格和互联网大厂明显不一样。大厂爱考智力题、海量数据处理、场景设计用友这套更贴近“一个Java开发进公司后第一周就会遇到的事”。先说结论这套卷子主要筛的是两类能力。第一类是Java底子扎不扎实集合、并发、JVM这些基础题占了大头而且特别喜欢用多选和“下列说法正确的是”这种形式来考一不留神就多选、少选、错选。第二类是数据库和工程落地能力SQL大题的分值很高而且题目包装得特别像实际业务里会遇到的查询需求。算法题反而难度适中没到LeetCode Hard级别更看重你能不能写出边界完整、思路清晰的代码。为什么是这种风格因为用友是做ERP、财务软件、企业管理系统的核心产品链路是“业务数据录入 - 数据存储 - 报表统计 - 权限控制”。这个领域的技术特点决定了笔试重点数据一致性必须可靠SQL要写得溜Java集合和并发要理解到位因为你写的每一行代码都可能跑在客户的财务月结流程里。所以这套卷子的整体画像很清楚它不是要招一个只会刷题的人而是要招一个能直接扔进项目里干活的Java后端。再看题型结构。整套题大致可以分成四块单选和多选混合的Java基础题覆盖集合、String、异常、JVM基础数据库题包含几道SQL编写和索引、事务相关的选择/简答算法编程题一般一到两道需要在在线编辑器里手写完整代码最后是网络和Linux为主的基础题形式多为选择题偶尔会有一道“给出命令问输出结果”的填空题。时间上大概两个小时选择题如果卡住超过两分钟就果断标记跳过把时间留给后面的SQL和编程题这是我最想强调的策略。另外这套卷子是整个系列里的第五套。如果你前面做过前面几套会发现一个规律前几套更偏向单个知识点的记忆性考察第五套则明显更综合很多题目会跨模块串联。比如一道题里既考了HashMap的扩容机制又顺带考了多线程环境下HashMap会出什么问题。这种综合性题目单纯背面试题集是应付不来的必须真正理解底层原理才能答对。所以我建议大家做这套题之前最好先把Java基础、集合源码、SQL常用写法过一遍不要裸考。2. Java基础与集合框架多选与细节题最考验源码功底2.1 集合容器高频题HashMap的底层演进与ConcurrentHashMap的方案变化先讲讲这套卷子里Java集合部分的重点HashMap几乎是必考的。2017年的时候JDK 8已经普及好几年了所以题目默认考的都是JDK 8的实现。HashMap的底层结构是“数组 链表 红黑树”当链表长度超过8且数组长度大于等于64时链表会转成红黑树目的是把查询时间复杂度从O(n)降到O(logn)。这里有两个容易被问到的细节为什么阈值是8为什么数组长度要达到64才允许树化链表长度阈值取8是源码作者基于泊松分布算出来的一个概率平衡点。在负载因子0.75、随机哈希足够分散的理想情况下同一个桶里链表长度达到8的概率已经非常低如果真出现了说明哈希函数可能出了问题或者数据分布极为不均这时用红黑树来兜底才值得。至于数组长度必须大于等于64是因为如果数组太小扩容比树化更划算直接把桶分散开就行了。这些细节笔试不一定直接问但选择题的干扰项特别喜欢在这种地方做文章。再一个高频考点是HashMap的扩容机制。默认初始容量16负载因子0.75也就是说当元素个数超过16 * 0.75 12时就会触发扩容容量翻倍到32。扩容时元素要重新计算哈希索引这就是一个非常耗时的操作。JDK 7的原版实现在并发扩容时会出现链表成环的问题导致get操作死循环JDK 8重构后这个问题基本解决了但HashMap依然不是线程安全的容器多线程写入仍然会导致数据覆盖、丢失之类的问题。如果说HashMap是基础那么ConcurrentHashMap就是进阶。这道题在整套卷子里出现过不止一次问法通常是“以下关于ConcurrentHashMap的说法正确的是”。这里要特别注意版本差异JDK 7的ConcurrentHashMap采用Segment分段锁机制把整个Map分成16个Segment每个Segment是一把独立的锁写操作只锁自己那一段读操作不加锁。JDK 8彻底废弃了Segment改用CAS synchronized锁的粒度细化到单个桶的首节点并发度更高锁竞争更小。很多同学只记得“分段锁”三个字却不清楚版本变化做题时看到“JDK 8中使用了分段锁”这种选项就容易选错。还有一个比较实用的记忆方法把HashMap、Hashtable、ConcurrentHashMap放一起对比。Hashtable是给整个Map加一把全局锁并发性能极差HashMap不加锁线程不安全ConcurrentHashMap是分段/细粒度锁线程安全且并发性能好。这个对比在选择题里经常出现记熟了基本不会丢分。2.2 一个容易在选择题里翻车的经典陷阱Integer缓存与String不可变性集合之外Java基础部分的题目也很能拉开差距因为很多题看起来简单实际上全是坑。印象比较深的一道题是关于Integer缓存的Integer a 100; Integer b 100; System.out.println(a b);输出什么如果改成Integer a 200; Integer b 200;呢第一段代码输出true第二段输出false。原因很简单Integer类内部有一个缓存池范围是-128到127使用valueOf方法创建对象时如果值在这个范围内直接返回缓存池里的同一个对象所以比较的是引用结果是true。超出范围的200没有缓存每次valueOf都会new一个新对象两个对象引用不同结果是false。这是笔试选择题的经典套路但实际开发里应该用equals比较两个Integer对象的值而不是这一点在代码评审里经常被人忽略。String相关的题目也差不多。String是final类不可变字符串常量池保证字面量相同的String对象只有一份。String s1 abc; String s2 new String(abc); s1 s2答案是false因为s1指向常量池里的对象s2指向堆上新建的对象。面试官想考察的其实就是你对“引用比较”和“值比较”的理解以及对String不可变性的掌握。题目本身不难但干扰项经常会混入StringBuilder、StringBuffer如果你分不清“线程安全的StringBuffer”和“非线程安全的StringBuilder”的适用场景也会出错。还有一类必考的是try-catch-finally的执行顺序。比如方法里try部分有一个return语句finally块里也有一个return语句返回值以哪个为准正确答案是finally块里的return会覆盖try里的return。因为JVM在执行try里的return前会先把返回值保存在局部变量表中然后跳转去执行finally块如果finally里也有return就会直接使用finally的返回值作为方法最终返回值。这个考点高频出现但很多人只在理论上知道没有自己写过验证代码做题时容易被绕进去。2.3 从这套卷子看Java复习方法背面试题不如读源码做这套卷子给我最大的感受是题目并不超纲但如果你只是背过面试题集而没有真正读过源码很多选项会模棱两可。比如HashMap的树化阈值、负载因子0.75的来历、ConcurrentHashMap在JDK 8中的实现变化这些在面试题集里可能只有一句话但笔试会用更细的角度来考。我当时复习用的方法比较笨但很有效打开JDK源码把HashMap、ArrayList、String这几个常用类的核心方法读一遍不用逐行看懂但要把数据结构、扩容机制、关键常量记下来。比如ArrayList扩容是原来容量的1.5倍HashMap扩容是2倍扩容时机和触发条件分别是什么。读完源码之后再做选择题看到选项基本一眼就能判断对错而不是靠猜。单靠“刷面试题—背答案”这个循环遇到多选题很容易翻车。3. 数据库与SQL大题业务约束翻译成查询条件的能力3.1 典型的SQL题查每个部门工资最高的员工怎么写出不丢数据的版本这套卷子里SQL题的分值是真的高大概占了整个卷子的四分之一而且题目描述特别像实际需求。印象很深的一道题有两张表一张员工表emp字段包括员工ID、姓名、部门ID、工资一张部门表dept字段包括部门ID、部门名称。要求写一条SQL查询每个部门工资最高的员工输出部门名称、员工姓名、工资。大部分人的第一反应是SELECT dept_id, MAX(salary) FROM emp GROUP BY dept_id但这样只能拿到每个部门的最高工资数值拿不到对应的员工姓名。要拿到完整信息常见做法是把聚合结果作为子查询再和员工表做连接SELECT d.dept_name, e.emp_name, e.salary FROM emp e JOIN ( SELECT dept_id, MAX(salary) AS max_salary FROM emp GROUP BY dept_id ) m ON e.dept_id m.dept_id AND e.salary m.max_salary JOIN dept d ON e.dept_id d.dept_id ORDER BY d.dept_id;这里有个最容易踩的坑如果同一个部门有两个员工的工资并列最高这条SQL会把两个人都查出来。这道题隐含的业务需求是“找出工资最高的员工”那并列的情况到底算一个还是多个我当时的做法是直接在答案里把这种情况写进注释说明并列时会返回多条记录并询问是否需要只返回一条。这个细节在后来的面试里被面试官专门提出来问过他说这是他故意留的考察点。所以笔试写SQL的时候不要只满足于“能跑出结果”要多想一步边界情况是什么业务上应该怎么处理另外2017年的时候MySQL 5.7还比较主流窗口函数不像现在这么普及所以我在笔试里优先写了子查询方案。如果是现在的环境MySQL 8.0里用ROW_NUMBER()窗口函数会更简洁SELECT dept_name, emp_name, salary FROM ( SELECT d.dept_name, e.emp_name, e.salary, ROW_NUMBER() OVER (PARTITION BY e.dept_id ORDER BY e.salary DESC) AS rn FROM emp e JOIN dept d ON e.dept_id d.dept_id ) t WHERE rn 1;如果想保留并列最高的人把ROW_NUMBER()换成RANK()或者DENSE_RANK()就行了。这个知识点建议大家两种写法都掌握因为现在的笔试面试越来越倾向于让你用窗口函数解题简洁又不容易出错。3.2 索引失效的典型场景选择题里的高频套路SQL基础之外这套卷子还考了不少索引相关的题基本集中在“以下哪种情况会导致索引失效”和“这条查询会走索引吗”两种问法。我整理了笔试里出现频率最高的几个索引失效场景做成了一个表场景简单示例失效原因对索引列使用函数运算WHERE YEAR(create_time) 2024破坏了索引列原始值的有序性隐式类型转换WHERE phone 13800000000phone是varchar字符串和数字比较时发生类型转换前导模糊查询WHERE name LIKE %张无法利用B树前缀匹配OR条件连接非索引列WHERE id 1 OR age 20age无索引需要全表扫描来验证age条件联合索引未遵循最左前缀索引(a,b)条件只有WHERE b 1联合索引的B树先按a排序跳过了a无法使用索引这几个场景不只要记住还要理解背后的原理。B树索引能够加速查询本质上是因为索引列的值是有序排列的你可以沿着树快速找到目标区间。一旦对索引列做了函数运算、类型转换或者前导模糊匹配索引本身的顺序就帮不上忙了优化器只能退而求其次走全表扫描。这套卷子考索引的方式和实际开发中遇到的SQL慢查询问题非常贴近所以把这些场景记忆成“经验”比死记硬背效果好得多。3.3 事务隔离级别与锁ERP系统为什么格外看重这一块用友的笔试还考了事务相关的题这跟它的业务强相关。ERP系统里跑的都是财务数据、库存数据如果同时有多个人在对同一条记录做修改事务隔离级别设置得不合理就可能出现脏读、不可重复读、幻读这些问题直接导致账目对不上。所以笔试里出现“请说明四种事务隔离级别及其解决的问题”这种题完全是业务驱动的。这道题在卷子里给了一张表问你在不同隔离级别下哪些问题会被解决。标准答案是这样的隔离级别脏读不可重复读幻读读未提交READ UNCOMMITTED可能发生可能发生可能发生读已提交READ COMMITTED避免可能发生可能发生可重复读REPEATABLE READ避免避免可能发生串行化SERIALIZABLE避免避免避免这里需要注意一个细节MySQL的InnoDB存储引擎在可重复读隔离级别下通过间隙锁Gap Lock和MVCC机制已经能很大程度上避免幻读问题所以MySQL默认的隔离级别就是可重复读。而Oracle默认是读已提交。笔试里如果题目没有明确说“基于MySQL InnoDB”默认答标准SQL层面的结论就行不要提前把间隙锁的内容写上去容易画蛇添足。我见过有同学在面试时直接说“可重复读不会发生幻读”被面试官反问“MySQL默认隔离级别下到底会不会”之后就卡住了。这块内容一定要分清楚标准SQL和InnoDB实现上的区别。4. 手写算法题在无IDE环境下保证正确率的实战套路4.1 这套卷子里值得优先准备的算法题型清单用友笔试的算法题不算难但考察点非常明确就是基础数据结构和简单动态规划。我把这套卷子里出现的题型整理成一个清单准备校招的同学可以照着这个方向练链表题链表反转、判断链表是否有环、合并两个有序链表字符串题字符去重、字符串反转、判断回文串二分查找在有序数组中查找目标值、查找第一个大于等于目标值的位置模拟题按规则模拟一个过程比如约瑟夫环简单动态规划跳台阶、斐波那契数列、最长公共子序列这些题目本身在LeetCode上都有对应原题难度基本在Easy到Medium之间。但和LeetCode纯刷题不一样的是在线笔试环境没有IDE的自动补全提示也不能靠编译器帮你查语法错误。所以平时练习时就要养成手写代码的习惯尽量不依赖快捷键和提示写完之后自己在脑子里模拟几组测试用例跑一遍。4.2 一道典型的跳台阶题从递归到动态规划的完整推导这套卷子的编程题里有一道很经典的跳台阶问题一只青蛙一次可以跳上1级台阶也可以跳上2级台阶问跳上一个n级台阶总共有多少种跳法。这个题看起来简单但它考察的点很丰富。首先它能考你的抽象能力。跳上第n级台阶最后一步只有两种可能从第n-1级跳1级上来或者从第n-2级跳2级上来。所以跳法总数就是f(n) f(n-1) f(n-2)边界条件是f(1)1f(2)2。这本质上就是斐波那契数列的变体。但如果你真的在笔试里写一个递归函数那就踩坑了。递归写法虽然代码简洁但时间复杂度是O(2^n)n稍微大一点就会超时。我当时在草稿纸上先算了一下f(40)的递归调用次数大约是1亿多次在线OJ的时限根本撑不住。正确做法是改成迭代用两个变量滚动累加public int jumpFloor(int n) { if (n 2) { return n; } int a 1, b 2; for (int i 3; i n; i) { int temp a b; a b; b temp; } return b; }这样时间复杂度是O(n)空间复杂度是O(1)。笔试里如果能写出这个版本再顺手在注释里提一句“递归会遇到重复计算迭代可以避免栈溢出”面试官印象分会高很多。还有一点需要注意题目没有说n的范围但如果是Java的int类型n超过46左右结果就会溢出int范围。严谨一点的写法是把返回值改成long或者在注释里说明数据范围。这个细节体现了工程经验在线笔试系统不一定能测出来但面试时被追问的几率很高。4.3 在线笔试的三个隐性要求方法签名、输入处理和边界自测在线写代码和本地写代码有三个很不一样的地方很多人第一次用友笔试的时候就在这里吃过亏。第一个是方法签名问题。有些在线笔试平台用的是牛客ACM模式需要你自己写public class Main、自己接收输入、自己输出结果有些用核心代码模式比如LeetCode风格只需要实现一个方法。用友这次用的是核心代码模式所以不需要处理输入输出但这个一定要提前在模拟环境里确认好别等到考试开始才发现自己一直在写ACM模式的代码。第二个是输入格式问题。如果遇到ACM模式要格外注意输入里可能有多个测试用例每行一个中间可能有空格分隔。很多同学只处理了一组输入导致后续用例全部读不到。建议写代码前先看清题目对输入格式的描述再在纸上简单模拟一下解析过程避免低级错误。第三个是边界自测。写完代码之后先在脑子里跑几组极端用例空链表、只有一个节点、数组长度为0、n等于0或负数、输入是最大值。这些都是在线OJ最喜欢埋的边界测试点。我在笔试时习惯先在代码里写好防御逻辑比如链表题先判断if (head null || head.next null)把空指针风险提前杀干净然后再考虑核心逻辑。这个习惯看着不起眼但真能救你很多次。5. 网络、Linux与常考常识选择题里的隐性分水岭5.1 HTTP状态码与TCP三次握手用表格记住不混淆网络题在这套卷子里大概占了五六个选择题分值不算高却是很多人失分的重灾区。因为这些知识点单纯靠背很容易记混尤其是状态码这种看起来很多、实际有规律的东西。HTTP状态码这块整理成下面的表格之后会清楚很多状态码含义实际场景301永久重定向网站更换域名旧的地址永久跳到新地址302临时重定向未登录用户临时跳转到登录页401未认证没有携带登录凭证403禁止访问已登录但没有权限比如普通用户访问管理员接口404资源不存在请求的URL对应不到任何资源500服务器内部错误后端代码抛了未捕获的异常502网关错误Nginx后面没有可用的后端服务503服务不可用服务过载或正在维护504网关超时后端处理时间过长Nginx等待超时这套卷子里关于TCP三次握手的题问得很经典“为什么建立连接需要三次握手而不是两次”答案要抓住两个核心。第一三次握手能确认双方的收发能力都正常。第一次客户端发SYN服务端收到后知道客户端发送能力正常第二次服务端回SYNACK客户端收到后知道服务端收发能力都正常第三次客户端回ACK服务端收到后知道客户端接收能力正常。如果只握手两次服务端无法确认客户端的接收能力是否正常。第二可以防止过期的连接请求突然到达服务端服务端误以为是要建立新连接白白分配资源。TIME_WAIT也考过。主动关闭连接的一方会进入TIME_WAIT状态等待2MSL两倍最大报文段生存时间后才真正释放连接。原因是确保最后一个ACK报文能到达对端如果ACK丢了对端会重发FIN主动关闭方可以用这个状态里的连接重发ACK而不是直接抛异常。这个细节在选择题里经常作为干扰项出现记住“2MSL”和“主动关闭方”这两个关键词就够了。5.2 Linux常用命令看到“给命令说结果”就能拿分Linux题在这套卷子里形式很固定基本都是给你一小段命令问输出什么或者让你选一条能达到目的的命令。常见的有这几类查看端口占用netstat -tlnp-t表示TCP协议-l表示监听状态-n表示显示数字IP和端口-p显示进程号。实际开发中用这个命令排查端口被占用的频率非常高。查找日志里的关键字grep ERROR app.log更高级一点是grep -A 5 -B 5 ERROR app.log把匹配行前后各5行也带出来方便看上下文。如果日志文件很大还可以用grep 关键字 app.log | tail -100只查最后100条。统计文件中每个单词出现的次数cat words.txt | tr -s \n | sort | uniq -c | sort -rn。这条命令是经典组合tr把空格替换成换行sort排序uniq -c统计连续相同行最后sort -rn按次数降序排列。这套命令在一次笔试里真的出现过选项里给了几种不同的排列组合很多人因为不熟悉tr的作用选错了。查看系统资源top看CPU和内存占用free -h看内存总量和占用df -h看磁盘空间。这几条都属于送分题记住选项里最合理的组合就行。Linux这块的复习成本其实很低不必系统学一遍运维知识把上面这些高频命令以及它们的常见参数组合搞清楚笔试选择题基本就够用了。5.3 “浏览器输入URL后发生了什么”一道题串联所有网络知识这套卷子里有一道综合性很强的选择题问的是“在浏览器地址栏输入一个网址并按下回车之后会发生什么”。这种题现在很多公司都爱考因为它能把DNS解析、TCP连接、HTTP请求、服务端处理、浏览器渲染整个链路串起来特别适合考察一个人对网络体系的全貌理解。这道题的完整链路是这样的浏览器先解析URL提取出协议、域名、端口和路径然后检查本地DNS缓存有没有对应域名的IP。如果没有就向本地DNS服务器发起递归查询一路查到权威DNS服务器拿到IP地址。接着浏览器通过这个IP向服务器发起TCP连接经历三次握手后建立连接。如果URL是HTTPS协议还会多一步TLS握手协商密钥。连接建立后浏览器把请求行、请求头和请求体组装成HTTP请求报文发送给服务器。服务器收到后经过后端处理返回HTTP响应浏览器拿到响应后先判断状态码再按Content-Type解析响应体。如果是HTML就开始解析HTML并构建DOM树同时解析CSS构建CSSOM树合成渲染树后进行布局和绘制。每一段链路对应这套卷子里的一个知识点DNS解析对应网络基础三次握手对应传输层HTTP请求对应应用层浏览器渲染对应前端常识。如果能把这条链路完整理清楚遇到相关选择题时基本不会慌。准备这种综合题建议自己拿张纸从头到尾把流程画一遍每画一步就在旁边标注这里可能考什么画过一遍之后记忆会非常牢固。6. 笔试之后的复盘分数只是结果错题才是面试素材6.1 为什么面试官会翻你的笔试记录很多人笔试通过后就彻底把卷子扔了这是一个很大的失误。以我后来的经验来看用友这类公司的技术面试官在面试前是会调出你的笔试记录来看的尤其是编程题和SQL题看你当时的代码风格、边界处理、注释习惯然后围绕这些内容展开提问。我当时在SQL题里写了“并列最高时如何处理”的注释面试官专门在这一题上追问了二十多分钟从窗口函数问到索引再到事务隔离级别等于一道题把数据库所有核心知识都串起来了。这意味着笔试不只是笔试它实际上是你面试的“开卷材料”。你在卷子上写下的每一个思路、每一段代码都可能成为面试官挖掘你技术深度的线索。所以笔试结束后应该第一时间把自己写的答案复制出来重新审视找出哪些地方答得不够好哪些地方有优化空间。不要等项目结束、面试过了才想起来复盘那时候记忆早就模糊了。6.2 复盘时按考点分类整理而不是只看对错我自己的复盘方法是把错题按考点分类而不是按题目顺序记录。比如我会建四个分类Java基础、数据库、算法、网络与Linux然后把做错的题填进去每道题旁边写三行内容正确答案、当时为什么选错、下次怎么避免。这个方法最核心的价值是帮你找到知识盲区而不是单纯地记录一道错题。举个例子如果我在集合相关的多选题里把“ConcurrentHashMap在JDK 8中使用了分段锁”这个错误选项选上了错因栏里就写不熟悉JDK 8的CASsynchronized实现下次遇到ConcurrentHashMap先确认版本。这样过几天再翻这本错题集你看到的不再是一堆孤立题目而是一张“自己的知识漏洞地图”哪里薄弱一目了然。这套方法我在后来准备其他公司的笔试时也一直在用效率远高于反复刷同一套题。6.3 一个值得坚持的小习惯考后三天内把编程题重写三遍最后分享一个我自己后来总结出来的习惯笔试结束后的三天内把编程题拿出来重写三遍。第一遍对着自己的答案改把所有语法错误和逻辑错误修掉第二遍关掉原来的答案凭记忆重新写一遍看看能不能独立写对第三遍给自己限时按笔试的紧张感再来一遍确保能在规定时间内完成。这三遍的意义在于第一遍练的是改正错误第二遍练的是知识内化第三遍练的是稳定输出。我在准备用友后续面试时把跳台阶那题用这个方法练了三遍所以面试时面试官临时改了一个条件“一次可以跳1级、2级或3级”我都能很快推导出新的递推式。这种举一反三的能力不是靠刷题刷出来的而是靠深度复盘磨出来的。如果你正在准备校招真心建议不要只追求“这套题我做过”而要追求“这套题背后的每个点我都能讲明白”这一点在面试环节尤其重要。