ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用友秋招笔试Java与SQL考点详解:从基础到实战

用友秋招笔试Java与SQL考点详解:从基础到实战 作为从2017年校园招聘季走过来的老开发后来又参与过几轮校招面试看到“用友2017秋招笔试题二”这个标题还是很有感触。那几年恰好是企业级应用开发向互联网架构转型的过渡期用友这类老牌管理软件厂商的笔试题风格和纯互联网公司有明显区别——它既有算法题又特别强调Java基础功底和数据库设计能力还带着点ERP业务色彩。这篇文章我就结合当年的题目风格和近年来的面试趋势把这类笔试的考察逻辑、典型题型和解题思路完整梳理一遍给准备应聘企业级软件开发岗位的同学做参考。1. 整体考察方向与出题逻辑1.1 用友类ERP企业笔试的独特风格先说结论用友的笔试题和其他互联网大厂完全不同。BAT、TMD那些公司喜欢考动态规划、贪心算法、红黑树一张卷子做下来像是ACM选拔赛。但用友这类做企业管理软件的公司它的核心产品是财务软件、ERP系统、人力资源管理系统这些东西的特点是什么是业务流程复杂、数据关系庞杂、并发场景绕、权限模型精细。所以它的笔试题目风格整体是“Java基础 数据库 基础算法 业务逻辑设计”的复合结构。基础算法会考但不会考到让人抓狂的难度真正的重头戏在Java语言特性和数据库设计上。这也是很多科班同学容易失误的地方——以为所有公司笔试都是刷LeetCode结果一来发现考的全是JVM内存模型、HashMap原理、SQL语句优化直接懵了。当年这套“笔试二”的卷子我记得题型大概是选择题涵盖Java基础、操作系统、网络、简答题考察理解深度、编程题算法 SQL、综合设计题业务场景建模。这种结构在今天看来依然是管理软件厂商校招的标准配置。1.2 为什么笔试要这么设计理解出题逻辑比你多刷几十道题都重要。站在公司的角度想用友做的是企业级软件客户是大型国企、制造业企业、连锁零售公司系统要7x24小时稳定运行财务数据一分钱都不能错。这就决定了它需要的开发人员必须具备三个素质第一Java基础要非常扎实。企业级应用十有八九跑在Java技术栈上从Servlet到Spring从JDBC到MyBatis整个体系都建立在Java语言之上。你对HashMap的理解、对并发编程的掌握直接决定了能不能写出高性能高可靠的后台服务。第二数据库能力要过硬。管理软件说白了就是在操弄数据——订单数据、库存数据、财务凭证数据几十张表之间的关联关系、事务一致性、查询性能优化是每天的日常工作。所以笔试中SQL题的分量非常重而且往往是手写SQL场景题不是在选择题里让你判断哪个关键字对。第三要有业务抽象思维。这是企业级软件和互联网应用的最大区别。互联网产品经理一句话后端接口改个字段完事但ERP里一个“销售出库”动作要联动库存表、应收应付表、成本核算表、流水日志表。笔试的综合设计题考的就是这个——给你一个业务场景让你设计表结构、梳理接口逻辑。1.3 这套题适合谁参考如果你正在准备企业级应用开发方向的校招尤其是目标公司包含用友、金蝶、浪潮这类管理软件厂商这套笔试题目值得认真做一遍。就算你不去这类公司把里面的Java题和SQL题吃透对面试其他做传统行业数字化、金融系统、政务系统的公司也很有帮助。如果你是刚学完Java基础、准备找实习的大三学生我也建议你看看这篇文章。我会把每道题的解题思路拆开讲包括它考察的知识点、常见的错误解法、以及更优的实现方式。这些东西在学校里很少会有人给你串讲但实际笔试中高频出现。2. 核心细节解析与实操要点2.1 Java基础题的深层考察点先说选择题里Java部分的几个高频考点这些几乎是这类公司笔试的“必考题”如果现在还不熟赶紧回头补。HashMap的内部实现原理。别只知道“键值对存储”要能说清楚默认容量是16负载因子是0.75扩容阈值是12。JDK 1.7和1.8在底层数据结构上的区别——1.7是数组加链表头插法1.8改成数组加链表加红黑树尾插法当链表长度超过8且数组容量大于等于64时链表转红黑树。为什么转红黑树因为链表查询是O(n)数据多了效率低红黑树查询是O(log n)。这些是送分题也是拉分题因为很多人只知道个大概。String、StringBuilder、StringBuffer的区别。这道题几乎是Java笔试的“保留节目”。要答到点子上String是不可变字符串每次拼接都会创建新对象循环里做字符串拼接会产生大量中间对象性能和内存都不好StringBuilder是可变字符串线程不安全但性能好单线程场景首选StringBuffer加了synchronized关键字线程安全但性能略差多线程场景才用它。JVM内存模型和垃圾回收。常考的有堆内存分代新生代、老年代、GC Roots可达性分析、常见的垃圾回收器CMS、G1的特点。笔试中让你判断“哪个区域是线程共享的”——堆和方法区是线程共享的虚拟机栈、本地方法栈、程序计数器是线程私有的。这些属于JVM入门级知识但既然写在了简历的“熟悉JVM”上就必须能答上来。2.2 并发编程从理论到实战还有一类高频题是并发编程。不是让你背“线程有几种状态”这种死知识而是真正考察你有没有写过并发代码。常考的有这几个synchronized和Lock的区别。synchronized是JVM层面的关键字自动释放锁异常时不会死锁Lock是JDK提供的接口需要手动加锁和释放锁一般在finally里unlock优点是支持公平锁、可中断锁、超时获取锁。笔试里可能还会追问synchronized锁的是什么锁定的是对象头里的Mark Word锁升级的过程是偏向锁到轻量级锁再到重量级锁。能把这个过程说清楚说明是真懂。volatile关键字的作用。两个核心保证可见性禁止指令重排序。但不保证原子性。经典场景是双检锁单例模式需要用volatile修饰实例变量防止指令重排导致拿到未初始化完成的对象。这个点几乎是必考的我建议你把双检锁单例的代码完整默写一遍。线程池参数和流程。七个核心参数核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。执行流程是先判断核心线程数是否已满没满就直接创建线程执行满了就放入阻塞队列队列满了就判断能不能再创建线程到最大线程数还不行就执行拒绝策略。常考的拒绝策略有AbortPolicy抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy静默丢弃。这块要会画流程图笔试时简单描述清楚就行。2.3 数据库高频题型梳理数据库是整个笔试卷里占比最重的一部分没有之一。用友的笔试特别爱考的是这几类SQL题手写多表关联查询。比如让你查“每个部门的部门名称、员工数量、平均工资”涉及GROUP BY和JOIN的结合。这种题看着简单但很多人写错在于GROUP BY后面到底能跟哪些列HAVING和WHERE的区别是什么——WHERE是在分组前过滤HAVING是在分组后过滤。子查询和聚合函数组合。经典场景是“查询工资高于本部门平均工资的员工信息”。这个需求一定要会写因为它考到了自连接、子查询、聚合函数三个知识点。先按部门算出平均工资再关联员工表过滤出高于平均工资的记录。索引失效的场景。这个经常以选择题形式出现选项里给你几个SQL查询条件问你哪个会走索引。关键点对索引列使用函数会导致索引失效如WHERE YEAR(create_time) 2020隐式类型转换会让索引失效如WHERE phone 13800001111phone是varchar类型但你没加引号LIKE以通配符开头会导致索引失效如LIKE %abc%OR连接条件时如果其中一个字段没索引整个查询可能不走索引。事务隔离级别。读未提交、读已提交、可重复读MySQL默认、串行化。要能说清每种级别解决什么问题、遗留什么问题。读未提交有脏读问题读已提交解决了脏读但存在不可重复读可重复读解决了不可重复读但存在幻读InnoDB通过MVCC加间隙锁基本解决了串行化级别最高但性能最差。3. 实操过程与核心环节实现3.1 编程题手写单例模式和生产者消费者用友笔试二的编程题不太会出特别偏的算法它更喜欢考经典设计模式和多线程协作。这里我们拿两个最典型的题来完整演示。第一题手写一个线程安全的单例模式要求双重检查锁实现考察点是并发安全 性能兼顾标准的写法public class Singleton { // volatile防止指令重排序 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; } }这段代码有三个关键点必须想明白。第一为什么加了synchronized还要再判断一次instance是否为空因为当两个线程同时通过第一次检查后一个线程拿到锁创建了实例并释放锁另一个线程如果不再判断一次就直接创建就会产生两个实例。第二为什么instance必须用volatile修饰因为new Singleton()不是一个原子操作它分为分配内存、初始化对象、将引用指向内存三个步骤CPU和编译器为了提高性能可能会对这三步进行重排。如果不用volatile禁止重排序一个线程可能拿到一个“分配了内存但还未初始化完成”的对象引用。第三构造方法为什么要私有因为单例模式的前提就是不允许外部new出新的实例只能通过getInstance获取唯一对象。第二题使用阻塞队列实现生产者消费者模式这道题考察的是对线程协作的理解用ArrayBlockingQueue来实现是最简洁的解法public class ProducerConsumerDemo { private static final int CAPACITY 10; private static final ArrayBlockingQueueInteger QUEUE new ArrayBlockingQueue(CAPACITY); static class Producer extends Thread { Override public void run() { for (int i 1; i 100; i) { try { QUEUE.put(i); System.out.println(生产: i); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } static class Consumer extends Thread { Override public void run() { while (true) { try { int val QUEUE.take(); System.out.println(消费: val); Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } public static void main(String[] args) { Producer producer new Producer(); Consumer consumer new Consumer(); producer.start(); consumer.start(); } }这里不需要手动wait/notify因为ArrayBlockingQueue内部已经用ReentrantLock和Condition实现了阻塞等待机制。put方法在队列满时会阻塞当前线程take方法在队列空时会阻塞当前线程。这种写法既简洁又不容易出错。不过我要提醒你笔试时如果你只写这种“无脑消费”的版本可能不会拿满分。如果你在Consumer的循环里加一个退出条件——比如消费到某个数量后break退出或者用volatile boolean变量来控制线程停止——会让面试官觉得你考虑问题更全面。实际企业开发中线程的优雅停止是非常看重的点尤其是涉及资源释放的场景。3.2 SQL题手写部门员工统计查询SQL题是这类笔试的大头我挑一道当年卷子里比较有代表性的题来拆解。题目背景有两张表employees员工表字段employee_id员工编号、employee_name员工姓名、department_id部门编号、salary工资和departments部门表字段department_id部门编号、department_name部门名称。题目一查询每个部门的部门名称、员工人数、平均工资按平均工资降序排列。SELECT d.department_name AS 部门名称, COUNT(e.employee_id) AS 员工人数, AVG(e.salary) AS 平均工资 FROM departments d LEFT JOIN employees e ON d.department_id e.department_id GROUP BY d.department_id, d.department_name ORDER BY AVG(e.salary) DESC;我拿这道题说三个很容易踩的坑。第一个坑GROUP BY到底应该写哪些列。MySQL有一个ONLY_FULL_GROUP_BY模式如果开启了这个模式SELECT后面出现的非聚合列必须全部出现在GROUP BY中。所以这里GROUP BY写了d.department_id和d.department_name两个字段。虽然department_id已经能确定department_name但为了兼容性最好都写上。第二个坑为什么用LEFT JOIN而不是INNER JOIN。LEFT JOIN会保留所有部门那些还没有员工的部门的员工人数会显示为0这符合业务直觉——“查所有部门的情况”。如果用了INNER JOIN没有任何员工的部门会被过滤掉结果就不完整了。第三个坑ORDER BY后面写的是AVG(e.salary)而不是别名平均工资。虽然在MySQL里可以直接ORDER BY平均工资但在一些严格模式的数据库里别名在ORDER BY阶段可能还没生效。写成AVG(e.salary)是最稳妥的。题目二查询每个部门中工资最高的员工信息。这道题可以有两种写法我建议你在笔试中优先写关联子查询的版本SELECT e.employee_id, e.employee_name, e.department_id, e.salary FROM employees e WHERE e.salary ( SELECT MAX(salary) FROM employees WHERE department_id e.department_id );是不是看起来很简单但这个写法有个问题如果一个部门里有两个员工并列最高工资这两个人都会被查出来。这在某些场景下是符合要求的取所有最高工资的人但如果你只想取一个人那就要用窗口函数。SELECT employee_id, employee_name, department_id, salary FROM ( SELECT employee_id, employee_name, department_id, salary, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn FROM employees ) t WHERE rn 1;这里用到了窗口函数ROW_NUMBER()按部门分区工资降序排列然后取排序为1的员工。注意MySQL 8.0及以上才支持窗口函数如果你在笔试时用的是MySQL 5.7环境这个写法会报错所以稳妥起见还是用关联子查询。3.3 综合设计题用户权限模型设计还有一道让我印象深刻的综合设计题它让你设计一个简单的用户权限管理模型。企业级系统里权限设计是标配功能这道题考察的是你的实际业务抽象能力。常规的模型是RBAC基于角色的访问控制核心就是三张主表和两张关联表用户表useruser_id, username, password, status角色表rolerole_id, role_name, role_desc权限表permissionpermission_id, permission_name, permission_url用户角色关联表user_roleuser_id, role_id角色权限关联表role_permissionrole_id, permission_id为什么不用“用户直接关联权限”的简单模型因为真实的企业系统里用户数量可能上万权限点可能几百个如果每个用户单独配置权限运维成本会爆炸。引入角色这一层后你只需要给角色配好权限然后给用户打上角色标签就行。比如说一个新入职的财务专员把他的用户分配给“财务专员”这个角色他就自动获得了所有财务模块的权限。权限调整时也只需要改角色不需要逐个用户去改。我在做这道题时会额外补充几个设计点这些是加分项多对多关系的关联表联合主键设为两个外键同时加上创建时间create_time字段方便追溯权限配给的时间。逻辑删除优于物理删除。用户离职时不应该直接删除用户记录而是把status置为0禁用这样历史流水日志关联用户时不会因为外键失效而查不到数据。菜单权限和数据权限要区分。菜单权限控制“你能看到什么功能按钮”数据权限控制“你能操作哪些数据范围”。比如两个销售经理一个负责华东区一个负责华南区他们的菜单权限可能完全一样但打开销售报表时只能看到自己区域的数据。这个区分在很多笔试设计题里都会用到能说出来说明你真的理解企业权限系统的需求。4. 常见问题与排查技巧实录4.1 时间分配策略这套笔试题的题量不算小合理分配时间能直接影响最终成绩。我的建议是选择题控制在15到20分钟内完成因为基础题会的秒选、不会的不要恋战SQL手写题花30到40分钟这类题分值大而且答案相对标准平时练得多就容易拿分编程题留40到50分钟包含读题、构思、写代码、检查综合设计题留20到30分钟用固定的框架去套思考过程不要超过10分钟重点是把表结构和关键字段写清楚。最忌讳的情况是在一道算法题上死磕一个多小时结果后面的大分值SQL题和设计题全空着。在笔试里拿到的分才是王道做不出来的题果断跳过这个道理很多人都懂但真正上了考场就容易上头。4.2 编程题中容易忽略的边界条件编程题最容易翻车的不是主逻辑而是边界条件。我给你们几个实际踩过的坑第一单例模式里构造方法有没有私有化。有些同学背了一整套双重检查锁的代码但构造方法忘了加private导致别人可以在外部直接new出第二个实例整个单例聊胜于无。第二字符串判空到底怎么判断。笔试里常常会遇到处理字符串的题目很多人直接写if (str null)就完了但实际上还应该判断if (str.length() 0)以及if (str.trim().isEmpty())。当用户输入一堆空格时你的程序能不能正确处理是评判代码健壮性的关键。第三数组循环遍历时下标越界。比如合并两个有序数组这类题写while循环时要注意索引是否超出数组长度。我建议在写完循环条件后心里模拟几组边界数据——空数组、只有一个元素的数组、已经全部处理完的数组——跑一遍看看会不会抛出ArrayIndexOutOfBoundsException。4.3 SQL题的两个隐性失分点SQL题我觉得特别要强调两个隐性失分点它们不直接影响语句能否执行但决定了面试官给你的代码打几分。第一个是查询可读性。很多同学手写SQL时会连表名都用缩写比如SELECT e.n FROM emp e。这在真正的笔试中是扣分的——面试官看不懂、也不好验证。规范的做法是给表起有意义的别名employees表别名直接叫emp或者干脆不写别名查询的列名写成具体的employee_name而不是一个字母n。同样的SQL逻辑清晰和潦草的书写在阅卷体验上完全是两个档。第二个是NULL值处理。查询平均工资时如果一个部门的员工没有工资记录工资列为NULLAVG函数会自动忽略NULL值这没问题。但如果你用SUM(salary)除以COUNT()来自己算平均工资就要小心了——COUNT()不会忽略NULL行这样算出来的结果会比AVG函数的结果小。了解每个聚合函数对NULL的处理方式是SQL笔试里的隐藏考点。4.4 综合设计题的避坑思路综合设计题最容易犯的错是想得太复杂。比如权限模型设计有些同学一上来就写五张表十张表把用户分组、数据范围、部门层级全部塞进去。这不是在展示能力而是在暴露你没有分清主次。笔试题的时间有限你要做的是先交付一个基础完整的方案再在最后用几句话提一下扩展点。面试官更愿意看到结构清晰的答案而不是堆砌概念的答案。还有一个很实用的思路设计题一定要画图辅助说明。不要觉得笔试是纯文字的手画一个简单的表关系图、接口流程图会让阅卷人更容易理解你的设计。如果现场没有画图工具用文字列清楚“核心表清单”和“关键字段说明”也能达到类似的效果。4.5 机考环境的几件小事一般这种笔试都是机考形式选择在平台上直接勾选编程题在网页编辑器里写。我的建议是提前做两件事第一熟悉常见的在线编程平台。有些同学平时在本地IDE里写代码习惯了自动补全和快捷键了一堆到了网页编辑器里只有一个纯文本框连缩进都要手动敲瞬间就不会写了。提前在牛客网、LeetCode中文站模拟在线编程环境熟悉那种没有智能提示的裸写感觉很有必要。第二注意代码是不是会被编译运行。如果题目要求的是“写出完整可运行的代码”那你的代码里不能有语法错误否则编译不通过直接零分。如果题目只要求“写出核心逻辑”那你可以在注释里写清思路没必要死磕所有语法细节。关键是读题时看清楚它的要求有些同学明明只需要写核心逻辑却非要补全完整的类定义结果补全过程中多了个分号错扣了不必要的分。5. 准备方法与进阶建议5.1 针对企业级软件开发岗的复习主线如果你还有一到两周准备时间我建议你按这样的优先级来安排复习第一优先级Java基础集合、异常、String、JVM内存模型、并发编程基础。这是笔试里选择题和简答题的题源也是性价比最高的复习模块。第二优先级数据库。SQL手写题几乎是这类笔试的必考模块你把多表查询、子查询、聚合函数、窗口函数练熟就能拿稳一大部分分数。第三优先级设计模式。不要死背23种设计模式重点理解单例、工厂、策略、模板方法这几个企业开发中真正常用的模式。笔试和面试问的都是“你的项目里哪里用到了什么模式”。第四优先级算法。重点复习数组、链表、字符串、二叉树的基础操作题LeetCode上的简单到中等难度就够了。5.2 我多次参与校招面试后的观察最近几年我也参与过几次校招面试站在面试官的角度说一个比较真实的感受笔试成绩很重要但面试时我最关注的不是一个人笔试考了多少分而是他在面对一个不熟悉的问题时能不能表现出良好的思考路径和分析习惯。很多学生技术知识储备是足够的但遇到实际需求时容易慌要么抓着已有的模板套要么直接放弃。这个能力其实笔试过程中的草稿纸就能看出来——你有没有尝试拆解问题、分步骤描述思路、有理有据地做取舍。所以我的建议是平时多积累笔试时放轻松。基础扎实了很多问题就算没有原题答案你也能现场推理出来。用友的这套笔试二考察的说到底还是“能否成为企业级软件开发的合格工程师”这一件事。5.3 分享一个我的压箱底练习方法最后分享一个我当年自己用的方法把笔试题当做“教学复盘”来做。每次做完一套题不急着对照答案而是先把每道题考察的知识点列出来再尝试用自己的话复述一遍解题过程。比如这道SQL题我为什么用LEFT JOIN它和INNER JOIN的区别是什么如果换成子查询性能上会有多大差异。这样复盘一遍等于把一道题嚼透了比漫无目的地刷十道题效果都好。我当时就是把这套方法用在了用友和其他几家软件公司的真题复盘上后来真到面试现场面试官一开口问问题时我甚至能猜到他想考我哪个知识点那种感觉真的非常有底气。
返回列表