用友2018秋招Java笔试题复盘:HashMap、JVM与并发考点全解析
发布时间:2026/8/31 14:05:56 作者:尧图编辑部 阅读量:1,286

聊到用友2018秋招Java笔试题四我印象最深的不是哪道题特别难而是整套卷子的节奏感。前半小时你会觉得“就这”后半小时开始怀疑自己是不是漏看了什么条件交卷前十分钟发现好几道题都是“看似基础实际埋雷”。这套题是我当年秋招做过的Java笔试卷里非常能代表企业级应用厂商考察Java工程师风格的一套既有大量基础题刷掉背书党又有几道需要真正写过代码才能答对的题还有一两道能区分“背过八股”和“真懂原理”的题。这篇不像网上那些只贴题目和答案的帖子我会按我记忆里的卷面顺序把高频考点、易错点、扩展追问全部串起来讲一遍。无论你是准备校招、社招跳槽还是纯粹想把Java基础打扎实这套题都值得认真过一遍。1. 先把卷面摊开这套题到底在考什么1.1 题量与题型分布用友这套秋招笔试题的卷面我当时拿到的是纸质版有的城市是机房在线答题时间两个小时总分100分。题型大致是这样的题型题量分值我的感受单选题15题30分覆盖面广JavaSE为主少量数据库和网络多选题5题15分最恶心少选多选都扣分简答题3题15分类和对象、集合、并发三选二编程题2题25分一道数据结构一道简单业务逻辑设计SQL题1题15分三表关联查询还要求写优化思路说实话这个分布在一众互联网大厂里不算激进。头部的电商、社交大厂那时候已经开始狂问分布式、中间件、高并发场景题了用友这张卷子还是偏“底子”的。但别小看这种卷子它对细节的抠法比直接问“Redis为什么快”要阴险得多。1.2 明显送分题与拉开差距的题送分题基本集中在单选前10题比如“Java的基本数据类型有几种”“下列哪个关键字用于异常处理”“ArrayList和LinkedList哪个查询更快”。只要系统学过Java这部分闭着眼也能拿分。真正拉开差距的是后面几类多选题里经常出现“下列说法正确的是”每个选项都长得像对的实际至少有一个选项是错的。简答题不直接问“了解集合吗”而是问“HashMap在JDK 8中链表转红黑树的阈值为什么是8为什么不是7或9”。编程题不考“反转字符串”而是考“从文件中读取订单数据按金额排序并输出”需要你写出完整可运行的代码包括异常处理。SQL题除了一句查询外还要求你写出索引建议并解释为什么这样建索引。如果你只是刷过别人的面经没有真正自己敲过代码、看过源码这套卷子能把你戳得很疼。1.3 两个小时内怎么分配时间我自己当时的时间分配是选择题30分钟多选题10分钟简答题20分钟编程题40分钟SQL题15分钟剩下5分钟检查。但实际做下来选择题花了快40分钟因为有几道题需要实际推算比如二叉树深度、HashMap扩容后的下标位置。如果你将来也要参加这类笔试我的建议是选择题遇到不会的果断标记不要恋战一道题最多两分钟。多选题宁可少选不要多选很多题目规则是“全部选对得满分选对但不全得一半分多选或错选不得分”。编程题先写解题思路注释再写代码至少保证核心逻辑的分能拿到。SQL题务必用缩进把层次写清楚阅卷人一眼能看到你的连接条件。2. 逐题复盘选择题里的高频考点与易错细节2.1 集合框架HashMap的容量、哈希碰撞、红黑树阈值选择题里有一道我记得特别清楚HashMap默认初始容量是多少选项有8、10、16、32。答案是16。但真正有水平的追问是后面那题为什么是16而不是10为什么加载因子是0.75这里展开说。HashMap的容量被设计成2的幂次方是为了能用(n - 1) hash位运算代替取模速度更快。162^4所以默认容量是16。如果你传入一个不是2的幂次方的初始容量比如19HashMap内部会用tableSizeFor方法把它转换成大于等于19的最小2的幂次方也就是32。加载因子0.75是空间和时间的折中。调大了比如1.0哈希冲突会更严重查询变慢调小了比如0.5空间浪费明显。0.75这个数值在泊松分布模型下桶内链表长度达到8的概率已经非常小所以链表转红黑树的阈值是8转回链表的阈值是6避免在树和链表之间频繁切换。这道题的延伸问到过“JDK 8的HashMap和JDK 7有什么区别”。你至少要能答出三点JDK 8引入红黑树JDK 7没有。JDK 8插入用尾插法JDK 7用头插法。JDK 7扩容时会重新计算hashJDK 8通过高位运算简化了计算。这些都属于“你真正看过源码才答得出”的细节。我建议你把JDK 8 HashMap的put、get、resize三个方法的源码读透因为后续技术面的追问基本都是从这里长出来的。2.2 String与包装类 和 equals 的坑单选题里有一道非常经典的String s1 new String(abc); String s2 abc; System.out.println(s1 s2); System.out.println(s1.equals(s2));第一个输出false第二个输出true。这个大多数人都知道。但用友这道题又加了一层String s3 a bc; String s4 abc; System.out.println(s3 s4);答案是true因为编译期常量折叠a bc会被编译器直接优化成常量abc所以s3和s4都指向常量池里同一个对象。比这个更阴的是改个写法String s5 a; String s6 s5 bc; System.out.println(s6 s4);答案是false。因为s5是变量变量的拼接在运行期通过StringBuilder完成生成的是堆里的新对象。同理Integer的缓存范围也有类似的坑。Integer a 127; Integer b 127; a b是true但如果是128就走缓存了不对-128到127之间是缓存128超出范围会new两个不同对象所以128那个比较是false。这类题的本质不是考语法而是考你对JVM内存模型和编译器优化机制的熟悉程度。我当时在这道题上多留了个心眼把常量池、堆、栈之间的关系在脑子里又过了一遍后面JVM的题答起来就顺多了。2.3 并发编程volatile、synchronized与线程池有题问volatile关键字能保证什么。选项里有可见性、有序性、原子性。正确选项是可见性和有序性它不能保证原子性。很多刚学Java的人都以为volatile和synchronized差不多其实是两个层面的东西。volatile解决的是“多线程之间某个变量的修改对其他线程可见”的问题。它通过内存屏障禁止指令重排序读的时候强制从主内存读写的时候强制刷回主内存。但count这种操作分三步读、加一、写回volatile管不了这三步的原子性。所以并发编程题里凡是看到volatile int count然后多线程做自增的代码问结果是多少你都要明白答案是不确定的。另一个常考的是synchronized和ReentrantLock的区别。我当时把要点写成了对比synchronized是关键字ReentrantLock是类。synchronized是隐式锁自动释放ReentrantLock需手动lock和unlock。synchronized是非公平锁ReentrantLock默认非公平但可设置为公平锁。ReentrantLock支持tryLock超时等待支持多个Condition条件。JDK 6之后synchronized经过锁升级优化性能已经不输ReentrantLock。还有线程池的题问ThreadPoolExecutor的核心参数里workQueue放的是什么。答案是等待执行的任务。很多选择题把workQueue和threadFactory混淆实际上threadFactory是创建线程的工厂。2.4 JVM内存区域与类加载JVM的题在这套卷子里占了不小比重。选择题问“下列哪个区域不属于线程共享”选项有堆、方法区、虚拟机栈、元数据区。答案是虚拟机栈它是线程私有的。紧接着的简答题是“描述一下类加载的双亲委派模型为什么需要它”双亲委派的意思是一个类加载器收到类加载请求后不会自己先去加载而是把请求委派给父加载器逐级向上最后才由自己加载。它的好处有三个避免类被重复加载比如你自定义了一个java.lang.String因为双亲委派最终会由启动类加载器加载JDK自带的String避免核心类被覆盖。保证Java类型体系的安全。避免不同类加载器加载出同名类导致混乱。这道题背后还藏着一个考点如果你自己写了一个java.lang.String然后放到classpath下能加载成功吗答案是不能会抛SecurityException因为双亲委派机制拦截了它。这也是为什么很多面试官喜欢顺着双亲委派问Tomcat的类加载器为什么要打破双亲委派。Tomcat需要为不同Web应用提供隔离的类加载环境所以它自己实现了一套WebAppClassLoader先加载自己WEB-INF/classes下的类再委派给父加载器。2.5 Spring与数据库企业级应用绕不开的部分作为企业级应用厂商用友笔试里Spring和数据库肯定不会缺席。Spring的题目有一道是“Spring Bean的默认作用域是什么”。答案是singleton单例。然后问“prototype作用域适合什么场景”。适合有状态的对象比如一个对象里面存了当前用户信息如果用单例多线程并发访问会互相污染数据。还有一道事务传播行为的题“ServiceA的methodA调用ServiceB的methodBmethodA的传播行为是REQUIREDmethodB的传播行为是REQUIRES_NEW如果methodB抛了异常methodA已经执行的部分会回滚吗”答案是methodA已执行的部分不会因为methodB的回滚而回滚因为REQUIRES_NEW会开启一个新事务新事务的执行结果与外部事务无关。但如果methodB异常向上抛出且methodA捕获后不再抛异常methodB新事务回滚methodA自己的事务因为没有抛出RuntimeException可能正常提交。数据库的题经典的是“为什么InnoDB的索引要用B树而不是二叉搜索树或哈希表”。参考答案是二叉搜索树在极端情况下会退化成链表树高度过高磁盘IO次数增加。哈希表虽然单点查询O(1)但不支持范围查询和排序。B树是多路平衡树高度低一般三层左右就能存储千万级数据一次查询只需要3到4次磁盘IO。B树的数据都存储在叶子节点并且叶子节点之间用指针连接非常适合范围查询。还有一道事务隔离级别的多选题问你InnoDB默认的隔离级别是什么。答案是REPEATABLE READ可重复读。但InnoDB通过MVCC和间隙锁在可重复读下避免了幻读所以实际隔离效果接近串行化。3. 编程题与SQL题真正拉分的是手写能力3.1 手写编程题的评分逻辑编程题不是只给代码就满分。阅卷老师会看你的解题思路、边界条件、时间复杂度分析。我当时就吃过亏题目要求“找出整数数组中连续递增的最长子序列长度”我直接写了一个双重循环答案对了但时间复杂度O(n^2)评论区都说应该一次遍历O(n)搞定。后来我养成了一个习惯任何算法题先写注释说明暴力解法然后优化为最优解最后再写代码。比如求最长连续递增子序列长度public int findLengthOfLCIS(int[] nums) { if (nums null || nums.length 0) { return 0; } int maxLen 1; int curLen 1; for (int i 1; i nums.length; i) { if (nums[i] nums[i - 1]) { curLen; } else { maxLen Math.max(maxLen, curLen); curLen 1; } } return Math.max(maxLen, curLen); }这段代码的核心是“当连续递增中断时重置curLen为1”。很多初学者会忘记在循环结束后再更新一次maxLen导致整个数组都是递增的用例出错。这种边界条件笔试里最容易扣分。另外一道题是“反转单链表要求迭代和递归两种方式”。迭代写法就是三个指针prev、cur、next依次挪动public ListNode reverseList(ListNode head) { ListNode prev null; ListNode cur head; while (cur ! null) { ListNode next cur.next; cur.next prev; prev cur; cur next; } return prev; }递归写法简洁但容易绕晕public ListNode reverseList(ListNode head) { if (head null || head.next null) { return head; } ListNode newHead reverseList(head.next); head.next.next head; head.next null; return newHead; }我当时先用迭代法写然后在注释里补充递归思路保证至少一种写法完整。笔试题量大能完整写出一种就不错了别贪多。3.2 SQL题三表关联与索引优化SQL题给的是员工表、部门表、薪资表三张表要求查每个部门薪资最高的员工姓名和薪资。这类题有两种典型写法。MySQL 8.0及以上可以用窗口函数SELECT department_name, employee_name, salary FROM ( SELECT d.name AS department_name, e.name AS employee_name, s.salary, ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY s.salary DESC) AS rn FROM employee e JOIN department d ON e.dept_id d.id JOIN salary s ON e.id s.employee_id ) t WHERE rn 1;如果用的是MySQL 5.7不支持窗口函数就要用关联子查询或临时表SELECT d.name AS department_name, e.name AS employee_name, s.salary FROM employee e JOIN department d ON e.dept_id d.id JOIN salary s ON e.id s.employee_id WHERE s.salary ( SELECT MAX(s2.salary) FROM salary s2 JOIN employee e2 ON s2.employee_id e2.id WHERE e2.dept_id d.id );这道题的坑在于如果两个员工薪资相同且都是部门最高第一种子查询会返回多条记录第二种窗口函数能保证只取一条。笔试时如果没特别说明“只取一人”我建议用窗口函数或者明确写出“如果有并列则取第一条”。索引优化思路也是采分点。我当时这样写的员工表的dept_id建普通索引。薪资表的employee_id建唯一索引或普通索引。如果按照部门维度统计较频繁考虑联合索引(dept_id, salary)。阅卷人看到你能主动写出索引建议基本这题就拿了大半的分。3.3 业务设计题从订单数据中提取关键信息还有一道编程题不是纯算法而是描述一个场景某电商系统每天产生大量订单要求你设计一个方法从订单列表中统计出每个商品的销售总金额并按照金额从高到低输出。这题本质是考Map的用法和排序但很多人一上来就用HashMapString, BigDecimal存金额忘了考虑BigDecimal相加要用add而不是加号。我当时是这样写的public ListMap.EntryString, BigDecimal calculateTotalAmount(ListOrder orders) { MapString, BigDecimal totalMap new HashMap(); for (Order order : orders) { totalMap.put( order.getProductId(), totalMap.getOrDefault(order.getProductId(), BigDecimal.ZERO) .add(order.getAmount()) ); } ListMap.EntryString, BigDecimal result new ArrayList(totalMap.entrySet()); result.sort((e1, e2) - e2.getValue().compareTo(e1.getValue())); return result; }这里用了getOrDefault和BigDecimal的add而不是totalMap.put(key, totalMap.get(key) amount)因为double的精度在做金额计算时会有问题。这道题考察的就是“你会不会在业务代码里用正确的方式处理精度”。4. 那些容易丢分的细节阅卷视角下的采分点4.1 手写代码的边界条件最容易被扣笔试阅卷人最喜欢看的是你写不写空指针判断、数组越界判断、循环结束后是否更新最大值。每次面试复盘我都发现丢分点往往不在主体逻辑上而在以下几个位置数组题没有判空和长度0。链表题没有处理head为null。字符串题没有处理空串和null。递归题忘记停止条件。金额计算用了float/double而不是BigDecimal。一个很实用的习惯是写完代码后自己脑内跑三个用例——空输入、单元素输入、正常输入。如果是链表再加一个两个节点的用例。这一条能帮你少丢至少三分。4.2 简答题的“废话过多”问题很多人误以为简答题写得多就分高其实阅卷是看关键词给分的。比如让你说“HashMap的put流程”你得先写“计算hash - 定位桶 - 如果桶为空直接放 - 如果桶不为空判断是否链表或红黑树 - 链表遍历查找存在则覆盖不存在则尾插 - 超过8转红黑树 - 扩容判断”。你把这些关键词写全然后稍微展开说明比长篇大论讲HashMap历史强得多。我当时吃了个亏有一道题问“JDK 8中HashMap为什么要引入红黑树”我洋洋洒洒写了半页纸从哈希函数讲到泊松分布但漏了“解决链表过长导致的查询退化为O(n)问题”这个核心句。所以后来我总结出一个答题模板先给结论再写理由最后加一个例子。结论先行阅卷人扫一眼就能看到你的答案。4.3 多选题的“宁可少选”策略用友这套卷子多选题规则我记得是少选得一半分多选、错选不得分。所以遇到拿不准的选项我的策略是只选最确定的那个。比如题目问“下列哪些是线程安全的集合”我确定ConcurrentHashMap是但拿不准Collections.synchronizedList包装后的List算不算那我只选ConcurrentHashMap至少保住一半分。这种策略虽然激进但比平均分高。如果你追求满分那就要把每个集合类的底层结构都梳理一遍。4.4 SQL题的隐性问题SQL题还有一个很容易忽略的点表连接时如果字段名有歧义必须加上表别名。比如员工表和薪资表都有employee_id字段你直接写WHERE employee_id s.employee_id数据库会报错。这种低级错误在笔试里非常常见。另外要求“写出优化思路”的题不要只写“加索引”。你要能写出哪张表的哪个字段需要加索引。避免使用select *只查询需要的字段。大数据量分页时避免offset过大用延迟关联。这些才是阅卷人想看到的优化能力。5. 从这套题反推用友的招聘偏好与后续面试衔接5.1 笔试通过后面试会怎么追问做完这套题后我最大的感受是用友的面试官问的问题几乎都从笔试题往外扩。比如笔试考了HashMap面试时就问“如果自定义对象作为HashMap的key需要注意什么”答案是要重写equals和hashCode保证equals相等时hashCode也相等。笔试考了事务传播行为面试就让你现场说一个REQUIRES_NEW的实际使用场景。我当时举的例子是在日志记录场景中哪怕主业务事务回滚日志也必须记录成功所以日志记录方法要设置为REQUIRES_NEW。笔试考了JVM内存区域面试就让你分析String s new String(abc)之后堆、栈、常量池里各有什么。所以你是为了面试准备的话刷这套笔试题不能只对答案你要把每个考点都延伸出至少三个追问自己用A4纸把答案写一遍。这样笔试过了面试环节也能顺势衔接上。5.2 企业在招聘后端Java工程师时其实是在挑什么人用友属于企业级软件头部厂商产品线包括ERP、财务软件、人力云等。这些系统的特点是业务复杂、数据量大、事务要求严格、系统不能轻易宕机。所以笔试里大量考察集合、并发、JVM、数据库本质上是在筛选两类能力一是基础能力扎实能处理日常开发中遇到的并发问题、内存问题、SQL性能问题。二是业务建模能力能把复杂的订单、报表、审批流转换成清晰的代码结构。这和互联网大厂“上来就考算法、手撕红黑树”的风格差别很大。你准备用友这类公司的笔试题重点应该放在Java基础、Spring、数据库、企业级应用设计上而不是死磕DP和贪心。5.3 简历上怎么呼应这套题的考察点如果你投的是用友的Java开发岗简历上的技术栈描述一定要覆盖这张卷子的核心考察点。我建议在简历里明确写出熟练掌握Java集合框架研究过HashMap底层结构及扩容机制。熟悉JVM内存模型、垃圾回收算法有JVM调优经验。熟悉MySQL InnoDB存储引擎掌握索引优化、事务隔离级别。熟悉Spring事务传播行为能处理分布式事务场景。这些句子看起来简单但每一个都对应笔试的一道题。面试官看到你的简历描述和笔试题高度重合对你后续面试的提问起点也会友好很多。5.4 我后来复盘这套题时的几点体会笔试结束当晚我把所有错题和模糊题重新整理了一遍发现真正丢分的不是知识盲区而是这三件事多选题太贪心总想着拿满分结果扣分。手写算法题没写注释逻辑跳步阅卷人读起来费劲。SQL关键字没大写表连接字段没加别名被扣了印象分。这些都是可以在考前通过模拟训练规避的问题。所以我强烈建议你笔试前完整地做两套题型类似的卷子用手机计时模拟真实考场环境。你会发现“会做”和“在两个小时内做完”完全是两回事。最后分享一个小技巧做笔试的时候不要直接在题目旁边写答案先在草稿纸上列关键词。比如看到HashMap立刻写下“数组链表红黑树、加载因子0.75、扩容2倍、头插尾插区别”看到事务立刻写下“ACID、传播行为、隔离级别、MVCC”。这些关键词既是你的答题大纲也是你检查答案时的对照清单。很多题只需要你看到关键词就能拼出正确答案远比临时回忆靠谱。