ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用友秋招Java笔试题解析:从基础到JVM与并发

2026/8/31 4:55:40 拓冰建站 浏览量
用友秋招Java笔试题解析:从基础到JVM与并发 提到用友的秋招Java笔试题三很多准备校招的同学第一反应是一家做ERP和财务软件起家的公司笔试应该更看重业务逻辑吧真拿到卷子你会发现完全不是这样。基础语法、集合、并发、JVM、Spring、SQL、手写算法一个都没少甚至不少题目就是面试“八股文”的笔试版。区别只是面试可以边聊边引导笔试完全看你在限定时间内能不能把答案写稳。这套题我后来反复给好几届学弟学妹讲最适合用来检验Java基础是否扎实尤其是想冲企业级应用开发岗位的同学认真做一遍的价值比盲目刷几十道简单题大得多。1. 这套题的整体画像用友到底想考你什么1.1 考点分布与题型结构用友这套笔试题的题量不算大以我看到的版本来说一般分成四个部分选择题、简答题、手写代码题和SQL题。考试时间大概120分钟整体节奏偏紧。选择题覆盖Java基础、集合框架、并发、JVM和Spring简答题则挑两三个核心知识点深入考比如“HashMap在JDK8里做了什么优化”“Spring AOP的实现原理”手写题一般是一道算法加一道设计模式算法常见的是排序或者链表反转设计模式大概率是单例SQL题通常会给一个多表关联的业务场景要求写查询同时解释为什么这样写。答题模块题量考察重点大致分值占比选择题15题左右Java基础、集合、并发、JVM、框架概念约40%简答题4题左右原理理解比如HashMap、Spring IOC/AOP约20%手写代码题2题左右排序、单例、生产者消费者等约25%SQL题1-2题多表关联、聚合统计、索引优化约15%这个结构很值得玩味。选择题量大是为了在有限时间里面广撒网判断你知识面够不够简答题看你是不是只背结论能不能把“为什么”讲明白手写代码题直接暴露代码功底SQL题则是在模拟真实业务里天天要干的事。对于用友这种做ERP、财务软件和云服务的公司来说这套组合拳非常合理。1.2 为什么是这些考点答案在用友的业务场景里用友的核心产品线集中在企业级应用这类系统有一个共同特点数据结构复杂事务密集并发访问量虽然不像电商大促那么夸张但胜在逻辑链路长、状态多。你想想一个财务模块一张凭证要联动总账、明细账、辅助账背后还有审批流、权限控制、操作日志。这种场景下Java集合用得好不好、HashMap和ConcurrentHashMap选得对不对直接关系到接口性能JVM内存模型清不清楚出了问题你连OOM日志都看不懂Spring用得熟不熟可能连Bean的初始化顺序都理不清。所以这套卷子压根不是想为难你它是在替你未来的同事提前确认一件事你是不是真的理解Java还是只会照着教程敲代码。如果只背概念不追底层很容易在简答题和手写题上翻车。比如“synchronized和ReentrantLock的区别”这道题几乎每个准备过面试的人都能背两句但换成“在你负责的订单导出功能里你会选哪个锁”很多人就开始含糊了。2. 高频基础题解析这些分不能白丢2.1 String与字符串池一道题能扯出半个Java运行时用友这套题里有一道非常经典的选择题String s1 new String(abc); String s2 abc; String s3 s1.intern(); System.out.println(s1 s2); // 输出 System.out.println(s2 s3); // 输出 System.out.println(s1.equals(s2)); // 输出答案是false、true、true。很多人第一反应是“int都是这样比较的”但字符串不一样。双引号直接创建的字符串会放进运行时常量池new String(abc)则会在堆上额外创建一个对象。s1引用的是堆里的对象s2引用的是常量池里的对象所以用比较肯定不相等。s1.intern()方法会去常量池找有没有字面量“abc”有就直接返回常量池引用因此s2和s3指向同一个对象。equals比较的是内容当然相等。这道题真正的考点不是这三个输出而是你有没有理解字符串池的机制。面试时考官还可能追问如果常量池里一开始没有“abc”呢intern()会把当前字符串对象加入常量池并返回引用具体行为还要看JDK版本。在JDK8及之后常量池在堆里的“元空间”之前的位置所以intern()调用后如果常量池不存在会把堆中字符串的引用复制到常量池或者说记录引用细节可以继续深挖。笔试题只要答对层引用关系就够了但准备面试最好把带引号字符串对象创建了几个这个问题一起想清楚。2.2 、equals与hashCode面试里最容易被带跑的一组概念试卷里还有一类题给你一个自定义类只重写了equals但没有重写hashCode然后问放到HashSet里会出现什么现象。答案是可能出现重复元素。这题考的不只是语法而是Java对象约定。默认情况下Object的equals比较的是引用地址hashCode返回的也是基于地址的哈希值。如果重写equals让两个不同对象在业务上相等但没重写hashCode它们哈希值可能不同。HashSet添加元素时先根据hashCode定位桶再和桶内元素equals比较两个业务相等的对象因为哈希值不同进了不同桶就都被放进去了。这样集合就出现了“逻辑重复”。所以用友这类题背后的潜台词是你在写业务代码的时候有没有下意识遵守“equals相等则hashCode必须相等”这个约定。尤其是用实体对象做去重、做Map的key时这个问题特别容易踩坑。我见过一个真实案例工单系统里把两个字段相同的订单当成了不同key导致统计报表数据翻倍最后就是一查发现实体只重写了equalshashCode没动。笔试考这个真的不是无聊。2.3 HashMap底层从数组链表到红黑树的演进逻辑HashMap几乎是用友笔试选择题里的常青树。有一道选择题大概是JDK8中HashMap在什么情况下链表会转成红黑树答案是链表长度大于等于8且数组长度大于等于64。很多人只记住了8忘了64这个前置条件。如果单链表长度到8但数组长度还小于64会优先触发扩容而不是转红黑树。为什么是8这背后其实有个泊松分布的计算在负载因子0.75、随机哈希的假设下链表长度达到8的概率已经非常低。用8作为阈值是为了在时间和空间上取得平衡。红黑树本身也有维护成本节点插入和删除要做旋转、变色规模不够大的时候链表反而更省。数组长度要求64也是同理数组太小说明哈希碰撞太严重应该先扩容分散而不是直接上树。再往下追可能会问到put流程先计算hash定位数组下标如果数组位置为空直接放进去不为空判断key是否相等相等则覆盖否则判断当前节点是不是树节点是则走红黑树插入不是则遍历链表。遍历过程中如果链表长度达到阈值就转树。这个过程几乎每年必考。准备的时候可以自己画一遍流程比死记结论效果好得多。3. 并发与JVM拉开分数差距的硬骨头3.1 synchronized与ReentrantLock不只考区别更考选型思路简答题里很常见的一道synchronized和ReentrantLock有什么区别。背答案容易把答案落到场景里难。两者的核心区别可以整理成一张表对比维度synchronizedReentrantLock实现方式JVM层面关键字JDK API基于AQS锁的获取与释放自动需要手动lock/unlock是否可中断不可以除非抛异常lockInterruptibly支持中断是否公平默认非公平构造时可指定公平或非公平条件变量配合wait/notify支持多个Condition性能优化后差别不大灵活性更高这里要记住一个关键点不要觉得ReentrantLock一定比synchronized快。JDK6之后synchronized引入了偏向锁、轻量级锁、重量级锁的升级路径大多数场景下性能已经够用。选ReentrantLock的理由更多是功能比如需要超时获取锁、需要公平锁、需要多个等待队列。笔试题里如果问“用哪个”答案应该是“看需求没有绝对好坏”。这种开放回答反而能让阅卷人觉得你有实际工程经验。另外ReentrantLock的实现基础是AQSAbstractQueuedSynchronizer面试官很可能顺着问AQS里的state变量、CLH队列、独占和共享模式。笔试简答一般不用写那么深但如果你能在答案里自然提到“基于AQS内部维护了volatile的state和等待队列”会在基础上加分。3.2 JVM内存区域与OutOfMemoryError从笔试题到线上故障JVM相关的题在用友这套卷子里基本不会缺席。选择题问你Java运行时数据区包括哪些答案是程序计数器、虚拟机栈、本地方法栈、堆、方法区JDK8之后是元空间。注意方法区和元空间的关系JDK8之前叫永久代JDK8之后移到本地内存并改名元空间字符串常量池也移到了堆里。如果同学现在还回答“永久代在堆里”基本就暴露年龄了。更进阶的题目会直接给一段OOM日志比如java.lang.OutOfMemoryError: Java heap space这表示堆内存不够。可能是对象太多可能是内存泄漏。文章标题热搜里还有一个“insufficient memory”本质上也是内存分配失败的一种表现。排查思路一般是先通过jstat或jmap看堆使用情况再导出堆转储文件用MAT或者VisualVM分析是否有大对象、是否有对象无法回收。线上CPU飙升时还会配合jstack看线程栈确认是不是有死循环在疯狂创建对象。笔试题里还可能问栈溢出和堆溢出的区别。栈溢出常见于无限递归报StackOverflowError堆溢出常见于持续创建对象又没法回收报OutOfMemoryError。这两个概念看起来简单但很多人会把“方法区OOM”和“堆OOM”混在一起。方法区/元空间OOM通常是因为运行时生成了大量类比如反射、动态代理使用不当堆OOM是对象实例太多。分清“类”和“对象”两个层面这一块的题目基本都能拿稳。3.3 类加载机制双亲委派到底在保护什么有一道题我当时印象深刻什么是双亲委派模型为什么需要它很多人只记得“先让父加载器加载加载不到自己再加载”但说不清意义。双亲委派的核心是避免Java类被重复加载更关键的是保证核心类的安全性。举个例子如果我们自己写一个java.lang.String类再通过自定义类加载器加载没有双亲委派的话系统里就可能出现两个不同版本的String一个来自JDK一个来自你的工程。这种情况下类与类之间的兼容性就崩了。有了双亲委派加载java.lang.String时最终会交给启动类加载器加载到的始终是JDK自带的String你写的那个String永远不会被应用类加载器加载。这就是为什么网上很多“自定义java.lang.String”的例子会报SecurityException因为加载入口就做了限制。笔试如果出扩展题可能会问“怎么打破双亲委派”。常见的答案有Tomcat的WebAppClassLoader它为了隔离不同应用的依赖会先加载自己目录下的类再委派给父加载器也就是“父委托优先”变成了“自身优先”。还有SPI机制通过线程上下文类加载器让父加载器去加载子加载器路径下的实现类。这些概念不需要全部展开但至少要知道双亲委派不是强制规定而是默认实现。4. 手写代码与分析题从会背答案到会写代码4.1 手写单例模式五道防线一个都不能少用友这套笔试题的手写代码题里单例模式是出镜率很高的一个。很多同学觉得简单随手就写public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { instance new Singleton(); } return instance; } }这种写法在单线程下没问题但一到多线程环境就会创建多个实例。于是有人改成在方法上加synchronized这样线程安全了但每次调用都要加锁性能差。笔试题真正想看的多半是双重检查锁DCL写得好不好public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { // 第一次检查 synchronized (Singleton.class) { // 加锁 if (instance null) { // 第二次检查 instance new Singleton(); } } } return instance; } }这段代码有两个地方特别容易被忽略。第一个是volatile。instance new Singleton()在字节码层面不是原子操作它分三步分配内存、初始化对象、把引用指向内存。如果这三步被重排成“先把引用指向内存再初始化对象”另一个线程就可能拿到一个半初始化的实例。volatile禁止了指令重排才能保证安全。第二个是两次if判断。第一次判断是为了避免无意义的加锁第二次判断是为了防止多个线程同时通过第一次判断后重复创建对象。少了任何一次锁的意义都会打折。当然单例还有静态内部类和枚举两种更优雅的写法。枚举写法在《Effective Java》里被推荐过因为它既能保证线程安全又能防止反射和序列化破坏。笔试如果时间充裕可以把枚举写法写出来顺便解释一句“它天然防止反射攻击”能看出你真的读过经典书。4.2 快速排序20分钟手写边界条件才是分水岭算法手写题里排序是永恒的主角。用友这套题选择排序的概率很高尤其是快速排序和冒泡排序。冒泡排序简单但考官往往不会满足于此更愿意让你手写快排因为你写完之后他还能追问“时间复杂度最坏是什么”、“怎么避免最坏情况”。一个标准且不容易出错的快排实现可以这样写public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivotIndex partition(arr, left, right); quickSort(arr, left, pivotIndex - 1); quickSort(arr, pivotIndex 1, right); } private static int partition(int[] arr, int left, int right) { int pivot arr[right]; // 取右端为基准 int i left; for (int j left; j right; j) { if (arr[j] pivot) { swap(arr, i, j); } } swap(arr, i, right); return i; } private static void swap(int[] arr, int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; }这段代码的逻辑是取数组最右边的值作为基准用一个i指针记录“比基准小的区域”的边界遍历j发现arr[j]小于基准时就交换到前面最后把基准放到i的位置。这样一次partition之后基准左边的元素都小于它右边的都大于它然后递归两边继续排。写的时候最容易出问题的点有三个递归终止条件没写或写错导致栈溢出partition里循环边界没控制好漏掉最后一个元素数组只有一个元素时left和right相等如果不提前返回会出现无限递归。以前我见过有人把if (left right)写成if (left right)单元素数组时也能过但双元素数组就可能出错。笔试环境没编译器这种边界细节只能靠平时练成本能。4.3 一道SQL题索引失效场景你踩过几个SQL题在ERP类公司的笔试题里几乎必考。用友这道题我记得是给了一张订单表和一张用户表让你统计每个地区订单金额前3名的用户。这类题核心难点是多表关联、分组排序和窗口函数的使用。窗口函数在MySQL 8.0之后很常用写法是SELECT region, user_name, order_amount FROM ( SELECT u.region, u.user_name, SUM(o.amount) AS order_amount, ROW_NUMBER() OVER (PARTITION BY u.region ORDER BY SUM(o.amount) DESC) AS rn FROM orders o JOIN users u ON o.user_id u.id GROUP BY u.region, u.user_name ) t WHERE rn 3;但光会写查询还不够笔试还喜欢在SQL题里夹带一个索引优化的问题。比如让你判断WHERE salary * 1.1 5000这个条件能否用到salary字段的索引。答案是大概率用不上因为对索引列做运算会让优化器放弃索引同理WHERE name LIKE %张因为前导通配符也基本用不上索引。还有一类非常隐蔽的坑字段是varchar类型查询条件写成数字时MySQL会做隐式类型转换索引也会失效。这些场景如果没踩过笔试很容易想当然。5. 踩坑实录与备考建议5.1 在线笔试环境里最容易翻车的几个问题用友和其他大厂的校招笔试一样走在线OJ系统。很多同学不是不会做题是输在环境上特别可惜。一个高频问题是代码里写了package语句OJ要求主类放在默认包下写了package直接编译失败。另一个高频问题是类名不是MainOJ的判题程序会按照Main类去找入口类名写错整个提交直接判零。还有一个非常现实的问题本机JDK版本和在线环境JDK版本不一致。比如本机是JDK17在线环境是JDK8你写了个var或者用List.of编译就报错。热搜里那条“源发行版 17 需要目标发行版 17”其实也是类似问题编译目标和运行环境不匹配。笔试前最好确认自己IDE的编译级别是哪个版本写代码尽量只用JDK8的语法稳一点。还有一个冷门但确实存在的情况Lombok在编译期报错提示“You arent using a compiler supported by lombok”。如果你是线下写代码可能是IDE的注解处理器没配好如果是在线OJ基本可以确定不支持Lombok。所以笔试手写代码题不要依赖Lombok的Data、Slf4j能手写getter/setter就手写别拿自己的分数赌环境。5.2 时间分配与答题顺序先拿稳再攻坚120分钟听着长真写起来很紧。我的建议是先快速扫一遍所有题目把一眼就有答案的选择题直接做了别在纠结题上死磕。选择题平均每题最多1分钟超过2分钟还没有思路先标记跳过。简答题控制在20到25分钟每道题别写成长文按点答清楚就好。手写代码题至少留40分钟因为写完还要检查边界条件。SQL题留15分钟读题要仔细别漏掉排序条件或分组字段。顺序上推荐先做SQL题。为什么因为SQL题信息量大越到后面脑子越乱越容易看错表字段而且SQL题分数占比不低第一感觉往往最准。然后再做代码题代码题写完之后心里会踏实很多后面做选择题的心态完全不一样。最后再回头补前面跳过的题目那时候即使不会也能靠排除法蒙一个不至于空白。5.3 从这套题延伸出的Java学习路线个人版很多同学让我推荐Java学习路线我的个人经验是不要一上来就抱着Spring Boot写项目先把语言地基打牢。第一阶段吃透Java基础语法、面向对象思想、常用集合、IO和异常。第二阶段啃并发和JVM这个阶段推荐结合源码读《Java并发编程的艺术》和《深入理解Java虚拟机》是最好的两本参考书。第三阶段再学框架Spring的IOC和AOP一定要从设计原理去理解而不是只写注解。第四阶段学数据库和SQL优化这是去用友这类toB公司绕不开的技能。刷题方面像用友这套卷子里的知识点基本覆盖了Java面试高频八股文的半壁江山。但我想提醒的是刷题刷到最后一定要回到源码。String、ArrayList、HashMap、ConcurrentHashMap这些类都值得打开IDE看一遍源码。你看过源码之后笔试题里那些“为什么”根本不用背。另外如果还有余力可以做一个小型的用户管理系统或者订单系统不用花哨但要把用户登录、权限校验、订单列表、详情查询这些基础功能实现一遍。这样做的好处是能把Spring、MyBatis、MySQL、Redis串起来。面试时如果被问到“你项目里怎么处理并发”“怎么优化一个慢查询”你至少有真实素材可以讲而不是背标准答案。这套用友2018秋招Java笔试题三我后来给很多人讲过每次最后都会说一句笔试不是看你背了多少答案而是看你在接触一个知识点的时候有没有想过它为什么这么设计。DCL为什么要加volatileHashMap为什么是链表长度8才转红黑树类加载器为什么默认双亲委派。这些想透了你拿下的就不只是用友一张卷子而是所有Java笔试的底层逻辑。