用友校招Java笔试题复盘:基础考点与解题思路

发布时间:2026/8/31 6:29:28
用友校招Java笔试题复盘:基础考点与解题思路 先说个背景我当年秋招的时候也投过用友印象里用友的笔试在传统软件厂商里算是比较有代表性的难度不算刁钻但范围铺得很广。这里拿“用友2017秋招笔试题四”这套题来做个完整复盘。这套题整体偏Java技术栈同时带了一部分数据库、算法和逻辑题题型上选择、简答、编程都有。对应届生来说这套题的参考价值在于它很典型地反映了传统企业级软件公司校招笔试的出题思路——基础为主、实用性优先、不追求偏题怪题但广度要求高。我尽量把每类题背后的考点、解题思路和答题技巧都拆开讲清楚不只是给答案而是把“为什么这么考”和“下次遇到该怎么办”一并讲透。如果你正在准备类似企业的笔试或者想系统回顾Java基础这篇文章值得花二十分钟慢慢看。1. 整体试卷风格与考察范围解析先给这套题定个调。用友的笔试题目结构通常由三块组成技术客观题选择判断、主观简答题、在线编程题。“四”这套卷子从题量分布来看技术客观题大概占60%主观题占20%编程题占20%。从考察内容上看Java基础占了很大比重包括集合、多线程、异常处理、JVM基础数据库方面主要是SQL编写和索引优化算法题偏简单到中等难度不会刻意出动态规划这类较难的题型还有一部分逻辑推理题用来测思维敏捷度。为什么我要单独强调“传统软件厂商”这个背景因为用友的笔试风格和互联网大厂有明显差异。互联网公司更偏重算法、高并发场景设计、源码级原理考察一问就是“ConcurrentHashMap的put流程”“Redis集群脑裂怎么处理”。但用友这类做ERP、财务软件、ToB业务的公司更看重候选人基础扎不扎实、能不能马上上手做业务开发所以笔试题目会多一些JavaSE基础、SQL语句、设计思路类的题。你可以理解为互联网大厂考的是“上限”传统软件厂商考的是“底线”。这就给准备笔试的人提了个醒如果你同时投了这两种类型的公司复习策略必须区分开。打通用基础——集合、多线程、JVM内存结构、SQL——这四块是无论如何都要吃透的但针对用友这类企业额外要多花时间看Java基础语法细节、异常处理机制、字符串相关API、数组和集合的转换操作这些看似“简单”的点反而是出题人最喜欢埋坑的地方。2. Java基础考点逐题拆解2.1 集合框架不只是背区别还要理解数据结构这套卷子里集合相关题目至少出现了三到四道覆盖了ArrayList和LinkedList的区别、HashMap的底层实现、HashSet的去重原理。这类题属于“老面孔”但很多人容易栽在不求甚解上。先说说ArrayList与LinkedList这道经典送分题。大部分人都知道“ArrayList底层是数组LinkedList底层是双向链表所以ArrayList查询快、LinkedList插入删除快”。但这句话其实是简化过的直接写上去很容易被扣分。真实情况是LinkedList的插入删除快是有前提的——你得已经定位到了指定节点此时插入才是O(1)的但如果按索引插入LinkedList需要先遍历到那个位置复杂度是O(n)反而比ArrayList的O(n)拷贝更慢虽然两者的O(n)常数不同。而ArrayList查询快也有前提——按索引随机访问是O(1)但如果按值查找同样是O(n)。更关键的是ArrayList在尾部插入时是O(1)摊还在头部插入时因为要移动所有元素复杂度是O(n)。所以面试或笔试遇到这道题不要只甩结论要把“什么场景下哪个更快”说完整这样才能体现你真的理解了。再看HashMap。2017年这个时间点JDK 8已经是主流了所以回答HashMap底层结构时要提到“数组链表红黑树”这三层结构。数组用来做哈希桶链表解决哈希冲突当链表长度超过阈值8且数组长度大于等于64时链表会转成红黑树把查询时间复杂度从O(n)降到O(log n)。这里面有几个细节容易被忽略第一为什么阈值是8因为遵循泊松分布在负载因子0.75、哈希函数足够散列的情况下链表长度达到8的概率极低约千万分之一所以8是个基于概率统计的工程选择。第二为什么还要一个数组长度64的条件因为如果数组长度还很短哈希碰撞本来就严重此时应该先扩容而不是转树。第三HashMap不是线程安全的多线程并发put可能导致数据覆盖JDK 7里还可能因头插法产生循环链表JDK 8改成尾插法后这个问题没了但依然存在丢数据的问题——并发场景应该用ConcurrentHashMap。HashSet这道题核心要理解HashSet底层就是包装了一个HashMapadd操作其实是往HashMap里put元素value统一是一个固定的Object常量。所以HashSet的去重原理本质上依赖HashMap的key去重机制而key去重依赖hashCode()和equals()两个方法先算hashCode定位桶再用equals比较链表/树中的元素是否相等。如果你往HashSet里存自定义对象不重写这两个方法去重就会失效。这是很经典的坑很多人在笔试的简答题里翻车就是只回答“HashSet可以去重”没说清楚机制。2.2 字符串与常量池细节决定成败字符串相关的题在Java基础里永远占有一席之地这套卷子也不例外。出题方式通常是给一段代码让你判断创建了几个对象或者s1 s2的结果是true还是false。先把最基本的知识点理清楚字符串常量池在JDK 7之后移到了堆内存中String s abc这种写法会先去常量池找有没有“abc”没有就创建有就直接复用而String s new String(abc)一定会先在堆上创建一个String对象同时如果常量池里没有“abc”也会创建一份。所以new String(abc)可能创建一个或两个对象。常见的考察形式是String s1 abc; String s2 abc; String s3 new String(abc); 问s1 s2和s1 s3的结果。前者是true因为都指向常量池中的同一个对象后者是false因为s3指向堆上的对象。再延伸一步String s4 s3.intern()intern方法会去常量池找找到了就返回常量池的引用所以s1 s4是true。这里我要重点强调一个很多人会写错的点字符串拼接。String s1 a b c这种字面量拼接编译期就会优化成abc所以和String s abc是同一个对象。但如果是String a a; String b a b;这种含变量的拼接编译期没法优化运行时会new一个StringBuilder或StringBuffer走append流程最终生成新对象。所以这类题里“final修饰的变量”是陷阱——如果String A a是final的那A b里的A会被当作常量处理编译期就能确定结果结果又变成了常量池对象。我当年笔试时就吃过这个亏写了答案没注意到变量是否被final修饰后来复盘才发现出题人就是故意埋伏这个点。遇到字符串比较的题第一件事先看变量有没有final第二件事看“”两边的操作数是否全是字面量然后才能下结论。2.3 异常处理掌握机制不过度设计异常相关的题目属于看起来简单、答好却不容易的类型。常见考法有两种一种是给一段try-catch-finally代码问输出顺序另一种是让你说出checked exception和runtime exception的区别以及什么时候该用哪种。先看finally的执行时机。结论很明确无论try里有没有异常finally里的代码都会执行而且是在return语句执行之前执行。但有几个例外情况要记牢System.exit()会直接终止JVMfinally不会执行如果try里执行了无限循环或死锁finally自然也不会执行finally里如果也有return语句会覆盖掉try或catch里的return值。最后这一点是最常见的坑我见很多笔试代码题都在这里出过陷阱。举个典型例子public static int test() { int x 1; try { return x; } finally { x 2; } }问返回值是1还是2很多人想当然觉得finally一定会改掉返回值答案是2。但实际上返回的是1。原因是return x这一步会先把x的值1保存到局部变量表或操作数栈中然后才去执行finallyfinally里修改x只是改了x这个变量的值不会影响已经保存的返回值。但如果finally里写的是return x那返回值就是2了因为return语句会重新读取x的值。再看看checked和runtime和error的三分法。Exception分两类checked exception受检异常和runtime exception运行时异常。受检异常必须显式捕获或抛出比如IOException、SQLException运行时异常不需要强制处理比如NullPointerException、ArrayIndexOutOfBoundsException。Error是另一个体系通常表示JVM层面的严重问题比如OutOfMemoryError、StackOverflowError程序不应该试图去捕获它们。笔试简答题里如果问“项目中如何设计统一异常处理”最好的答法是自定义业务异常类继承RuntimeException在Service层抛出通过全局异常处理器统一捕获并转换为对应的错误码和消息。为什么选RuntimeException而不是checked exception因为checked exception会强制每个调用方去处理导致代码里到处是try-catch非常啰嗦而runtime exception抛出后可以集中在最上层处理代码更干净。这也是Spring框架的默认实践。2.4 多线程与并发基础从概念到实践多线程在传统软件企业的笔试中属于查漏补缺型考点考得不会很深但基本概念必须清晰。这套卷子里涉及了线程的创建方式、synchronized和Lock的区别、volatile关键字的作用、线程池的基础参数等问题。先说线程创建。经典的两种方式是继承Thread类和实现Runnable接口Java 8之后又多了Callable和FutureTask的方式可以拿到返回值以及线程池提交任务的方式。笔试里问“为什么推荐用Runnable而不是继承Thread”核心原因是Java单继承的限制——实现了Runnable的类还能继承其他类而继承Thread后就不能再继承任何类了。另一个原因是解耦Runnable把任务本身和线程执行机制分开了任务可以在线程池、Thread、甚至是其他执行器里复用比直接继承Thread灵活得多。然后是synchronized和Lock的选择。两者都是Java中实现线程同步的手段区别在于synchronized是JVM层面的关键字可以在方法上或代码块上用获得锁和释放锁都是自动的使用简单但不够灵活Lock是JDK层面的接口典型实现是ReentrantLock需要用lock()和unlock()手动控制配合try-finally使用但它提供了更多能力——可中断获取锁、可超时获取锁、公平锁、多个条件队列Condition等。在低竞争场景下synchronized经过JVM优化后性能并不比Lock差甚至更好锁升级、偏向锁、轻量级锁但在高竞争或需要灵活控制的场景Lock优势更明显。volatile这个关键字也是一个高频考点。它的两个语义是可见性和禁止指令重排序。所谓可见性就是当一个线程修改了volatile变量的值新值对其他线程是立即可见的通过内存屏障和缓存一致性协议实现禁止重排序则保证volatile变量读写前后的代码顺序不会被编译器或CPU随意调换。但volatile不保证原子性i这种复合操作即使变量是volatile的依然有并发问题。一个经典结论是volatile适合修改变量的状态标志位比如控制线程停止的boolean变量但不适合做计数器。线程池方面要掌握的核心参数有七个corePoolSize核心线程数、maximumPoolSize最大线程数、keepAliveTime非核心线程空闲存活时间、unit时间单位、workQueue任务队列、threadFactory线程工厂、handler拒绝策略。这里有个常见问题“先放队列还是先创建非核心线程”答案是当提交任务数超过核心线程数时任务先进队列排队只有当队列也满了才会创建非核心线程。这和其他人的直觉有偏差需要特别注意。四种拒绝策略也要能说出来AbortPolicy抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队列最老的任务。3. 数据库与SQL题型分析3.1 手写SQL考点集中在关联查询和分组统计用友是做管理软件起家的数据库SQL是笔试绝对的重头戏这套题里的SQL题占分不低。出题场景通常是“员工表employee”“部门表department”“薪资表salary”这种经典的业务模型题目要求涵盖单表查询、分组统计、关联查询、子查询、分页等。举一个我印象中类似的真题场景有两个表员工表emp_id, emp_name, dept_id, salary部门表dept_id, dept_name要求查出每个部门薪资最高的员工信息。这道题有好几种写法我分别说一下。第一种思路是子查询配合INSELECT e.dept_id, e.emp_name, e.salary FROM emp e WHERE (e.dept_id, e.salary) IN ( SELECT dept_id, MAX(salary) FROM emp GROUP BY dept_id );这种写法在MySQL里支持行值比较简洁直观。需要注意GROUP BY之前要先过滤掉NULL值等脏数据不然可能查出奇怪的结果。第二种思路是用关联子查询SELECT e.dept_id, e.emp_name, e.salary FROM emp e WHERE e.salary ( SELECT MAX(salary) FROM emp e2 WHERE e2.dept_id e.dept_id );关联子查询的逻辑是“对于外层每个员工找到他所在部门的最高工资再比较”这种写法好在通用性强任何数据库都支持。缺点是如果数据量大性能会受影响因为每行都要执行一次子查询。第三种思路是窗口函数SELECT dept_id, emp_name, salary FROM ( SELECT dept_id, emp_name, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM emp ) t WHERE rn 1;窗口函数是SQL标准里的高级特性MySQL 8.0之后才支持2017年大部分学校教材里还没怎么讲笔试如果用这个写法会给阅卷人留下好印象。但这取决于题库的判分方式如果答案是人工阅卷那加分明显如果是机器比对建议还是写传统写法更稳妥。3.2 索引设计理解B树和最左前缀原则索引类题型通常以两种形式出现一种直接问“什么是索引有什么优缺点”另一种给一个SQL查询语句让你分析用到了哪个索引或者让你给表设计索引。后者难度更高一些也很考验实战功底。先说基础概念。索引是对数据库表中一列或多列的值进行排序的一种结构它让查询时可以快速定位到目标行而不必全表扫描。MySQL的InnoDB存储引擎默认索引结构是B树。为什么不用B树因为B树的数据都存储在叶子节点且叶子节点之间用链表相连这使得B树非常适合范围查询比如WHERE age BETWEEN 20 AND 30——先找到起点叶子节点然后顺着链表往后遍历即可而B树的数据分布在所有节点中范围查询需要多次回溯效率低。另外B树非叶子节点只存索引键不存数据所以同样的页大小能容纳更多键值树的高度更矮磁盘IO次数更少。树的高度一般在2到4层这意味着查询一个千万级数据表的记录最多只需3到4次磁盘IO。联合索引这块必须条理清晰地讲清楚。假设建了一个联合索引(emp_name, dept_id, age)这个索引实际上会按照emp_name排序emp_name相同的记录再按dept_id排序dept_id也相同的再按age排序。所以你能用到完整索引的最左匹配必须满足查询条件里包含emp_name。如果跳过了emp_name直接按dept_id查索引就失效了因为B树里dept_id不是最顶层的排序依据。这就是所谓的最左前缀原则。理解了原理前端做系统设计时就能灵活应用了。举例来说WHERE emp_name 张三 AND age 25这样的查询因为跳过了中间列dept_idage条件无法用到索引的第三列只能从满足emp_name的记录中逐一过滤。但如果查询是WHERE emp_name 张三 AND dept_id D1那么两列都能充分利用索引。我踩过一个坑为了优化一条慢查询我建立了一个联合索引(a, b, c)但后来发现线上有些查询条件只用了b和c完全没走索引。当时的修改方案是新增一个(b, c)的联合索引把满足不了最左前缀的查询覆盖掉。所以在设计索引时一定要提前梳理业务方常见的查询模式而不是上来就堆索引——索引也是要占空间、拖慢写入的。3.3 SQL优化从执行计划到实际调优SQL优化是简答题的高频考点常见的问法是“一条SQL查询很慢你会从哪些角度排查和优化”。这道题没有标准答案但答题时踩点要全按下面的思路答题基本能拿高分。第一步先看是不是没走索引。用EXPLAIN查看执行计划关注type字段从好到差的顺序是system const eq_ref ref range index ALL。如果看到ALL全表扫描就要注意说明查询没用到索引。再看key字段确认实际用到的索引是否和预期一致。有一种常见情况是查询条件里有索引列但因为函数操作或隐式类型转换导致索引失效例如WHERE DATE(create_time) 2024-01-01这种写法会让create_time索引失效改成WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00之后才能命中索引。第二步看是不是SELECT了太多不需要的列。SELECT *要改成只查需要的列因为InnoDB的二级索引不包含全部字段如果只用SELECT *可能会造成回表去主键索引再查一次。如果查询的字段全部在二级索引里就能实现覆盖索引扫描效率高很多。第三步看查询是否做了无谓的排序或分组。ORDER BY如果没走索引数据库会使用filesort当数据量大时很慢。GROUP BY同理。优化的思路是让排序字段和WHERE条件里的字段组成联合索引让数据本来就有序。如果实在要排序可以通过增加sort_buffer_size或调整max_length_for_sort_data来缓解。第四步看是否有大量数据扫描。比如用LIKE %xxx%做模糊搜索因为前导通配符会让索引失效。这种场景的合理方案是用全文索引或搜索引擎如Elasticsearch。但如果是包含关系查询把通配符放在后面LIKE xxx%前缀匹配仍然可以走索引。作为补充有一点很重要但容易被忽略优化SQL永远先看执行计划再动手。不要凭感觉“这个SQL好慢加个索引吧”。有一次我排查一个慢查询EXPLAIN发现SQL明明走了索引但查询还是慢后来定位到是查询结果集太大服务端和客户端交互次数太多把网络传输时间都耗掉了。这已经不属于SQL本身的问题了而是业务设计问题需要分页或者分批处理。不加分析直接动SQL容易白费功夫。4. 算法与逻辑题的做题策略4.1 常见题型偏基础重思路这套题里的算法编程题整体难度在LeetCode Easy到Medium之间不会涉及太复杂的算法。常见题型有以下几类第一类字符串处理。比如反转字符串、判断回文串、找出字符串中第一个不重复的字符。这类题通常考察你对String、StringBuilder、char[]的熟练度以及边界条件的处理。写的时候注意Java里String是不可变对象频繁修改字符应该用StringBuilder或StringBuffer避免产生大量中间对象。第二类数组与链表。比如合并两个有序数组、删除链表中重复元素。这类题核心是搞清楚索引边界和链表指针的next指向。写完后一定用简单的用例在纸上走一遍流程比如输入[]、[1]、[1,1]这种临界输入验证代码不会越界或死循环。第三类经典的递归题。比如斐波那契数列、青蛙跳台阶。这类题要掌握自顶向下的递归写法和自底向上的迭代写法。用递归时记得加记忆化数组memoization不然很容易超时——这是用友笔试常见的扣分点。第四类简单的排序查找。比如手写快排或者二分查找。二分查找的细节很多包括循环条件用left right还是left right、mid怎么取left (right - left) / 2避免溢出、更新边界时mid要不要加1减1。我推荐背下一套固定的写法每次套用不要考场上临时想。4.2 做题顺序与时间分配在线编程这部分很多人有个误区总想按顺序从第一题做到最后一题。我的建议是先快速浏览一遍所有编程题先做自己最有把握的把能拿的分稳稳拿到再回来啃难题。为什么因为在线判题系统很多时候只按通过的测试用例百分比给分哪怕你只通过了一部分用例也有分空着就一分没有。对于思路不完整的题目先写一个暴力解法把核心逻辑跑通至少能拿到部分用例的分数。很多时候暴力解配合适当的剪枝通过率并不低。比如需要遍历所有排列组合的题先写递归枚举再用哈希表做去重能拿到不少分。还要注意输入输出边界。笔试里常出现“多组测试数据”的描述很多同学没读清楚就只处理了一组。正确做法是使用while (in.hasNext())持续读取不要默认只有一行输入。这些细节虽然不起眼但在判题系统里可能就是“通过”和“不通过”的差别。4.3 逻辑推理题的答题技巧逻辑题在这套卷子里也有出现常见的有条件推理、真假话判断、图形推理。这类题目不涉及特定的知识储备考察的是逻辑链条的推导能力。应对这类题我有个实用方法论先用符号化方式把条件写出来再用排除法逐项判断。尤其像“如果A那么B”“只有C才D”这种假言命题先把“如果把条件反过来会怎样”的几种情况列清楚再对照选项逐一排除能显著提高正确率。真假话问题可以用“假设法”假设某个人说的是真话代入所有条件看是否矛盾。如果矛盾说明假设不成立换一个假设。这类题只要耐心推一推一般都能做对。图形推理相对靠“题感”。常见规律包括图形旋转顺时针/逆时针每次多少度、对称性轴对称/中心对称、线条数量变化增/减/奇偶交替、方块重组平移/翻转等。练多了之后看到图形就能大致锁定考察方向。5. 主观题部分答题思路与踩分点5.1 项目描述类问题这套笔试的主观题里有一道是根据你自己的项目经历描述一个功能模块的设计和实现过程。这种题没有标准答案但有踩分点。第一部分要讲清楚项目背景和业务需求。不要上来就谈用了什么技术先让阅卷人知道你要解决什么问题。比如“项目是一个企业内部报销系统核心需求是支持员工在线提交报销单并走审批流”这比“项目用了Spring Boot MyBatis”更有价值因为技术是为业务服务的。第二部分要讲你的技术选型并给出理由。这里可以体现你的思考深度。比如为什么用Redis缓存组织架构数据因为组织架构变动频率低而查询频率极高用缓存能把响应时间从200ms降到10ms。这就能体现你对方案选型是有判断力的而不是随便用的。第三部分要讲核心难点和解决方案。哪怕你的项目看起来很简单也一定能挖出难点比如并发控制、事务一致性、接口性能、权限控制等。重点不是难点有多“高深”而是你能不能用条理清晰的方式把解决办法讲清楚。我建议套用STAR原则情境、任务、行动、结果。比如“订单并发状态下防止超卖最终通过数据库乐观锁版本号机制解决压测下正确率达到100%”。5.2 设计类问题设计类题目在笔试里也很常见问法可能是“设计一个登录模块”“设计一个订单系统”“设计一个短URL服务”。很多同学看到这种题就慌觉得一定要画一堆架构图、写一堆组件。其实这类题考察的是你面对一个需求时能否有条理地拆解问题。答题建议采用“边界→模块→接口→存储→优化”的框架。第一步先明确业务边界把系统的输入输出弄清楚。比如登录模块的核心动作是接收用户名密码→校验身份→发放凭证→后续请求校验凭证。这一步想清楚后面所有模块拆解就有了主线。第二步拆分子模块。还是拿登录举例可以拆成用户注册模块、密码加密模块、验证码生成模块、凭证管理模块。每个模块给出核心接口比如User login(String username, String password)、Boolean validateToken(String token)。第三步设计存储。涉及用户数据存哪张表、字段有哪些、密码字段怎么存这里要自然引出“密码不能明文存储应该加盐哈希”的知识点比如用BCrypt。第四步谈优化空间。比如登录接口要防暴力破解增加验证码和登录失败次数限制凭证用JWT或Redis实现分布式会话做到多端登录互踢。照这个顺序写即使你不了解具体业务也能写出“结构完整、有条理”的答案。阅卷人看到你有从0到1搭建系统的思维框架自然会给高分。5.3 开放性问题如何答得出彩开放性问题通常没有唯一标准答案常见的考察方向是“你如何看待加班”“你对自己的职业规划是什么”“如果项目上线前发现严重Bug怎么办”。这类题看似随意实际上考察的是沟通能力和解决问题的思路。技术岗位的开放题通常和技术相关。比如“如果线上系统出问题了你如何排查”这题其实有标准流程先查看监控和报警确认影响范围再看日志定位异常堆栈根据异常类型判断是代码问题、网络问题还是数据问题线上紧急修复后要有临时方案回滚或降级故障恢复后要写复盘报告做根因分析。答题时把这条链路写全就比只写“看日志”要突出得多。6. 复盘与后续准备建议整套卷子做下来你会发现一个规律大部分题考察的知识点并不是某本技术书的边角料而是Java程序员日常开发天天在用的基础能力。这给准备校招的人一个启示——不要把大量时间花在刷偏题怪题上先把手头常用技术栈的原理吃透再针对目标企业的业务特点做定向准备。针对用友这类做ToB软件的企业建议多留意几个方向数据库设计能力他们非常看重、Java集合和并发基础日常开发高频使用、基本的系统设计能力项目中要学会梳理模块和接口、以及沟通表达的逻辑性主观题答题一定要分条、分行、按步骤写让阅卷人看着舒服。最后说一个小技巧准备笔试时不要只停留在“做对题”的层面。每做完一道题花两分钟想一想——这道题考察的是哪个知识点如果我是面试官我会基于这个知识点追问哪些内容带着这种思路去复习每次做题都会成为一次面试模拟坚持下来笔试和后续的面试都能受益。