ARTICLE DETAIL

资讯详情

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

用友秋招Java笔试题解析:从集合到JVM的高频考点与避坑指南

用友秋招Java笔试题解析:从集合到JVM的高频考点与避坑指南 1. 先看这张卷子的出题套路用友秋招Java笔试到底在考什么1.1 从标题信息能读出哪些关键线索“用友2018秋招Java笔试题六”这个标题看起来只是张普通校招卷但里面有大量值得挖掘的信息。用友是国内老牌的企业服务厂商核心产品集中在ERP、财务软件、云服务这条线上它的Java岗位笔试风格一直比较偏“基础扎实度业务落地能力”不会像互联网大厂那样大量堆算法题也不太喜欢出偏题怪题。如果你正在准备类似的企业级Java岗位笔试这张卷子透露出的考点方向非常典型值得认真拆一遍。“六”这个编号说明这是整套卷子中的第六份。一般企业校招笔试题库会准备多套平行卷题量和难度基本对齐考点覆盖会有意错开。比如第一套可能重点考Java基础第二套可能偏数据库第三套可能偏框架到了第六套它往往承担着“查漏补缺、平衡难度”的功能。所以看第六套题不能只看单个题目更要看它和前几套在考点上是怎么互补的。另外一个隐含信息是题型。2018年秋招的Java笔试普遍还是“选择题简答题编程题”的三段式结构用友这套卷子也不例外。选择题通常集中在Java基础语法、集合框架、异常处理、JVM基础简答题一般会要求你说清楚某个机制的原理比如类加载、HashMap的扩容、并发编程的中断机制编程题则以手写算法或小型业务逻辑为主难度大概在LeetCode中等偏下重点考察代码的完整性和边界处理能力。注意我拿不到第六套的原题卷面下面所有题目示例和解析是我基于2018年用友Java岗笔试题库的常见考点还原的。目标不是猜原题而是把这套卷子背后考察的知识体系讲清楚。你按这个体系去复习覆盖度只会高不会低。1.2 六套题常见的模块分布与做题节奏结合用友笔试题库的出题习惯我把它历年秋招Java卷的模块分布整理成了下面这个参考表。这个表同时适用于判断你现在拿到的这套题大概考了什么、每块该花多少时间。模块常见题量建议用时核心考点Java基础语法8-10题20分钟数据类型、运算符、String、异常、泛型集合框架5-8题15分钟HashMap、ArrayList、HashSet、并发集合JVM与内存3-5题10分钟内存区域、类加载、垃圾回收多线程与并发4-6题15分钟synchronized、volatile、线程池、锁数据库4-6题15分钟SQL编写、索引、事务隔离级别框架与设计模式4-6题10分钟Spring IOC/AOP、单例模式编程题1-2题25分钟手写算法、字符串处理、排序从这个时间分配能看出一个规律Java基础的比重非常大选择题大约占三分之一。很多复习不到位的人喜欢把时间全押在框架和JVM上结果到了笔试现场发现最基础的String比较和ArrayList扩容反而答不牢这类丢分特别可惜。笔试题量大时做题节奏很重要我的建议是遇到拿不准的选择题先标记跳过把简答和编程题的时间留足回头再处理模棱两可的题目因为编程题一旦写不完整张卷子及格就很悬。2. Java基础核心考点拆解集合、异常与面向对象2.1 面向对象与equals/hashCode——一道必拿分的题面向对象是Java笔试题里雷打不动的送分题但送分不等于白给。用友这类企业笔试题特别喜欢把面向对象的基础概念和实际代码场景揉在一起考比如给你一段代码问你运行结果或者问你equals和hashCode为什么要一起重写。我记得有一道典型的题目是这样出的public class User { private String name; private int age; // 省略构造方法、getter/setter Override public boolean equals(Object obj) { if (this obj) return true; if (obj null || getClass() ! obj.getClass()) return false; User user (User) obj; return age user.age Objects.equals(name, user.name); } // 注意这里没有重写 hashCode() }问题通常是“这个类放入HashSet后会出现什么问题”要答好这道题你需要理解HashSet的存储逻辑它内部其实是HashMap的key部分元素的唯一性靠hashCode定位桶再用equals判断桶内是否有重复元素。如果两个User对象的name和age都相等equals返回true但因为hashCode没有重写默认使用Object的native方法根据对象内存地址计算两个不同对象的hashCode大概率不同就会落到HashMap的不同桶里。结果就是两个逻辑相等的对象同时存在了HashSet中破坏了Set的不重复语义。更隐蔽的问题是如果你先往HashSet里放了一个对象然后修改了这个对象参与hashCode计算的字段比如name再去contains它会因为hashCode变化而定位到另一个桶导致找不回原来的对象。这就是“hashCode依赖可变字段”的坑在企业级代码里非常容易碰到笔试题也爱在这里挖坑。实操心得重写equals时必须同时重写hashCode而且hashCode的计算字段要和equals比较的字段保持一致。如果你用IDE自动生成默认是基于所有非静态、非transient字段计算的这个行为在大多数场景下是安全的但在把对象放进HashSet后不要修改关键字段。2.2 HashMap的实现细节——从存储结构到扩容机制HashMap是Java集合里出场率最高的考点原因很简单它几乎是所有后端业务都会用到的数据结构而且它的实现细节能一次性考察数组、链表、红黑树、哈希算法、位运算、并发安全等多个知识点性价比极高。2018年这个时间点正好是JDK 8普及的阶段所以笔试基本默认考JDK 8的HashMap实现。你需要记住的核心结论如下底层结构是数组加链表链表长度超过8且数组长度大于等于64时链表会转成红黑树转换阈值是TREEIFY_THRESHOLD 8退化阈值是UNTREEIFY_THRESHOLD 6。这里经常考“为什么一个是8一个是6”其实是为了避免链表和红黑树在临界点反复转换留出缓冲区间。默认初始容量是16默认加载因子是0.75f。扩容阈值是容量乘以加载因子即16*0.7512也就是HashMap里元素个数超过12时触发扩容。扩容时容量翻倍即从16变成32。JDK 8的扩容过程中元素在新数组中的位置要么在原索引要么在原索引加原数组长度这个规律来源于(n-1) hash这个取模运算是一个非常精巧的位运算设计。笔试里常出现一个变种题问你“HashMap的容量为什么必须是2的幂次方”。答案关键在于(n-1) hash这里。当n是2的幂次方时n-1的二进制低位全是1这样与hash做位与运算时结果能均匀覆盖0到n-1的所有下标减少哈希碰撞。如果容量不是2的幂次方比如17那么n-1的二进制是10000与hash做位与后低4位永远是0只有高位参与运算大量元素会落到少数的桶里冲突会非常严重。另一个高频考点是put方法的完整流程。你可以按这个顺序记忆先对key的hashCode做异或扰动高16位异或低16位然后根据(n-1) hash找桶下标如果桶为空直接放入新节点如果桶不为空遍历链表或红黑树判断key是否存在存在则覆盖value不存在则尾插新节点最后检查size是否超过阈值超过则resize。注意JDK 8之前HashMap在并发场景下扩容可能出现循环链表导致get时无限循环这是著名的死循环Bug。JDK 8改用尾插法后这个问题被解决但HashMap依然不是线程安全的容器。笔试如果问“HashMap能不能用在多线程环境”标准答案是不能应该用ConcurrentHashMap。2.3 异常机制与try-with-resources——别在细节上翻车异常这块在笔试题里看起来简单实际上是个重灾区。很多人在复习时觉得异常就是try-catch-finally没什么好准备的结果做题时被“finally里的return会不会覆盖try里的return”“受检异常和非受检异常怎么区分”这种问题问住。先看finally和return的问题。下面这段代码的运行结果是什么public static int test() { int i 1; try { return i; } finally { i 2; } }答案是返回1不是2。执行顺序是先执行try里的return i此时i的值1被保存到返回值槽里然后执行finallyfinally里把i改成2但返回值槽里存的还是1最后方法返回1。如果finally里也有return情况就变成finally的return直接覆盖try的return比如finally里写return 3方法返回的就是3。这个知识点在笔试里反复出现核心是理解“finally中的return会覆盖try中的return”。再来看受检异常和非受检异常的区分。受检异常Checked Exception是编译器强制要求处理的异常比如IOException、SQLException不在方法上声明throws或使用try-catch编译就过不了非受检异常RuntimeException及其子类不需要强制处理比如NullPointerException、IndexOutOfBoundsException、ArithmeticException。笔试常考的形式是给你一行代码问你该异常属于哪一类或者问你对自定义异常该怎么选择。实际开发中的经验法则是可恢复的异常用受检异常不可恢复的编程错误用运行时异常。还有JDK 7引入的try-with-resources这个在企业笔试题里出现频率很高因为用友这种企业级Java开发大量涉及文件、数据库连接这些需要关闭的资源。try-with-resources的语法是try (FileInputStream fis new FileInputStream(test.txt); BufferedReader br new BufferedReader(new InputStreamReader(fis))) { // 业务代码 } catch (IOException e) { // 处理异常 }这种写法的好处是资源自动关闭不需要手写finally代码更简洁。它的底层原理是编译器会自动生成close调用并把close阶段抛出的异常追加到try块异常的suppressed列表里。笔试如果问“try-with-resources和传统try-finally的区别”回答要点包括三点代码简洁性、异常屏蔽问题被解决suppressed机制、资源必须实现AutoCloseable接口。3. JVM与多线程拉开差距的重点区3.1 JVM内存区域与类加载过程JVM相关考点在第六套卷子里通常占3到5题看起来不多但每一道都是拉开差距的关键。原因很简单Java基础题大家都会答JVM题是真的能筛掉一部分没系统复习过的人。先看内存区域。JVM运行时数据区按线程共享与否可以分成两部分。线程共享的区域是堆和方法区JDK 8后方法区被移入元空间MetaSpace其中堆存放对象实例是垃圾回收的主要区域元空间存放类元数据、常量、静态变量等。线程私有的区域是虚拟机栈、本地方法栈和程序计数器其中虚拟机栈是高频考点因为栈帧里存储了局部变量表、操作数栈、动态连接、方法出口等信息笔试常问的结构性问题包括“栈溢出是什么异常”“堆溢出是什么异常”。关于内存溢出常考的是StackOverflowError和OutOfMemoryError的区别。前者是虚拟机栈深度超过JVM允许的深度时抛出的典型场景是无限递归后者是堆空间不足时抛出的典型场景是创建大量对象且无法被回收。笔试选择题里还常混淆这两个比如问“递归没有终止条件最终抛出什么异常”答案必须是StackOverflowError。类加载过程同样是个保留考点。完整流程是加载、验证、准备、解析、初始化后面三步合称连接。准备阶段和初始化阶段是最容易被混淆的准备阶段为类的静态变量分配内存并设置零值比如static int a 100在准备阶段a的值是0而不是100真正赋值为100发生在初始化阶段这个区别几乎每年笔试都会出现。双亲委派模型也是个必考点。它的工作过程是如果一个类加载器收到了类加载请求它不会自己先去加载而是先把这个请求委派给父类加载器每一层都是如此最后请求传到最顶层的启动类加载器只有父类加载器反馈自己无法加载时子加载器才尝试自己加载。这样做的核心价值是保证Java核心库的类型安全比如java.lang.String不管被哪个类加载器加载最终都由启动类加载器加载避免用户自定义的同名类覆盖核心API。实操心得很多人在复习类加载时会问“为什么一定要用双亲委派直接用自己加载不是更快吗”这个理解方向是反的。双亲委派不是性能优化是安全机制。如果没有双亲委派你写一个java.lang.String并放到classpath里系统就会加载你自己的版本其他类在引用String时行为就不可控了。3.2 垃圾回收基础回收区域、判断算法与常见收集器垃圾回收GC在JVM考点里属于必选项。笔试通常不会深入考GC调优参数但会考三个基础概念哪些区域需要GC、怎么判断对象是否存活、CMS和G1的区别是什么。哪些区域需要GC很好判断程序计数器、虚拟机栈、本地方法栈都是线程私有的生命周期跟线程一致栈帧出栈时就自动回收了不需要GC参与。需要GC的主要是堆和方法区元空间其中堆是GC的主战场。这个判断题偶尔会在选择题里出现选项会写成“JVM所有内存区域都需要垃圾回收”这就是错的。判断对象是否存活有两个算法引用计数法和可达性分析算法。引用计数法是给对象加一个引用计数器每被引用一次加1引用失效减1计数为0时判断为可回收。但这个算法解决不了循环引用的问题两个对象互相引用但没有外部引用时两者计数都不为0永远无法回收。所以主流JVM比如HotSpot用的是可达性分析算法从一组称为GC Roots的根对象出发通过引用链向下搜索凡是不可达的对象都判定为可回收。GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、JNI引用的对象等。关于收集器2018年笔试题的时代背景是G1开始取代CMS。CMS收集器是“并发标记清除”收集器它的特点是通过多次标记和并发清理来减少停顿时间缺点是会产生内存碎片且无法处理浮动垃圾。G1收集器则是把堆划分成多个Region通过跟踪各Region的回收价值来优先回收最大收益的区域能更可控地设定停顿时间。笔试常见的问法是“CMS和G1有什么区别”答题要点是CMS基于标记清除会产生碎片G1基于Region复制不产生碎片且能指定预期停顿时间。3.3 synchronized与volatile的底层区别多线程考点里synchronized和volatile是一对高频搭档经常在一道题里同时考查。先看volatile它有两个核心语义。第一是保证可见性一个线程修改了volatile变量后其他线程能立刻看到最新值第二是禁止指令重排序也就是内存屏障的作用。但volatile不保证原子性典型例子是volatile int count进行count操作在多线程下结果依然会出现错误因为count不是原子操作它包含读取、加1、写回三个步骤volatile保证的是每次读写都直接从主内存操作但这三步之间依然可能被其他线程插入。synchronized则是重量级的互斥锁它既能保证原子性也能保证可见性。从实现上看synchronized在JVM层面通过monitor对象实现进入同步代码块时执行monitorenter指令退出时执行monitorexit指令。JDK 6之后JVM对synchronized做了大量优化引入了偏向锁、轻量级锁、重量级锁的升级路径这也是面试官很喜欢问的考点。偏向锁是同一个线程多次获取锁时不再做同步操作轻量级锁是使用CAS自旋来获取锁自旋超过一定次数或竞争激烈时升级为重量级锁重量级锁依赖操作系统互斥量线程会进入阻塞状态。笔试常出现的一道综合题是“synchronized修饰静态方法和修饰实例方法有什么区别”答案是修饰静态方法时锁的是类的Class对象修饰实例方法时锁的是当前实例对象。这个区别在实际开发中很容易踩坑比如两个线程分别对同一个类的不同实例调用synchronized实例方法其实是不互斥的因为它们锁的是不同的对象但如果调用的是synchronized静态方法所有实例都共享同一个类锁线程之间就会互斥。3.4 线程池的核心参数与拒绝策略线程池在Java笔试里不仅是理论题还经常和编程题结合比如让你手写一个线程池的提交逻辑或者给你一个场景问该用哪种拒绝策略。最经典的考法是构造函数参数辨析ThreadPoolExecutor的七个参数分别是核心线程数corePoolSize、最大线程数maximumPoolSize、空闲线程存活时间keepAliveTime、时间单位unit、工作队列workQueue、线程工厂threadFactory、拒绝策略handler。这里有个容易混淆的点是线程池的创建逻辑。很多人以为来了任务就创建线程先创建到最大线程数再用队列缓冲这个理解是错的。实际流程是当提交任务时如果当前线程数小于核心线程数直接创建新线程执行如果当前线程数大于等于核心线程数任务会先进入工作队列等待队列满了才创建新线程直到最大线程数如果线程数已经到最大且队列也满了才会触发拒绝策略。还有一个考核点是拒绝策略的四种类型。AbortPolicy是默认策略直接抛出RejectedExecutionExceptionCallerRunsPolicy是让提交任务的线程自己执行该任务DiscardPolicy是直接丢弃新任务DiscardOldestPolicy是丢弃队列里最旧的任务然后重新尝试提交新任务。实际企业开发中一般的做法是用CallerRunsPolicy或自定义策略因为直接丢弃任务容易造成数据丢失而抛出异常可能会导致上游调用链断裂。注意笔试中如果看到“线程池设置核心线程数为10最大线程数为20队列容量为50此时提交第80个任务会发生什么”这类题你要按上面的流程一步步推前10个任务直接创建核心线程执行第11到第60个任务进入队列等待第61个到第80个任务会创建新线程执行达到最大线程数20此时如果再提交第81个任务队列已满、线程数已达最大值就会触发拒绝策略。4. 框架、数据库与算法题补充4.1 Spring与Spring Boot的常见考察方向用友笔试题对框架的考察不会特别深但Spring IOC和AOP这两个基本概念是绕不开的。IOC控制反转考的是“控制权从对象本身转移到容器”这个思想简单说传统创建对象是new UserService()使用Spring后对象实例的创建和依赖装配由容器完成你需要什么就在字段上加Autowired或通过构造器注入。笔试常问“Spring IOC容器是如何管理Bean的”核心答法是容器启动时读取配置信息XML或注解通过反射创建Bean实例根据依赖关系完成注入然后把它存储在容器中使用时直接从容器获取。AOP面向切面编程的核心考点是动态代理。Spring AOP默认使用JDK动态代理要求目标类实现接口如果目标类没有实现接口则使用CGLIB生成目标类的子类代理。笔试中偶尔会问“JDK动态代理和CGLIB的区别”回答要点是JDK动态代理基于接口通过Proxy类和InvocationHandler生成代理对象反射调用方法CGLIB通过继承目标类生成子类使用ASM字节码框架生成字节码被代理的方法不能是final的因为final方法不能被重写。Spring Boot的出现让配置量大幅减少所以现在的笔试题也会顺带考SpringBootApplication注解它其实是Configuration、EnableAutoConfiguration、ComponentScan三个注解的组合。4.2 数据库索引与事务隔离级别数据库在Java笔试题里的地位很高因为Java后端开发必然要写SQL、做表设计、调查询性能。用友这类偏企业应用的公司数据库相关的题目会占比很大第六套卷子里的数据库题比较偏向基础加简单实践的搭配。先看索引。笔试必考的是“索引为什么能加快查询”这需要理解B树数据结构。B树是多路平衡查找树所有数据都存储在叶子节点并且叶子节点之间通过指针相连形成有序链表这种结构让范围查询非常高效只需要找到起点然后顺序遍历叶子节点即可。相比B树B树的内节点不存数据只存索引值意味着同样的磁盘页能容纳更多索引项树的高度更低磁盘IO次数更少。这也是InnoDB引擎选择B树作为索引结构的原因。索引题经常还会考查“什么情况下索引会失效”。常见的失效场景包括对索引列使用函数或计算、使用LIKE以通配符开头、使用OR连接非索引列、隐式类型转换导致类型不一致。比如WHERE age 1 20在age上有索引也不会走索引因为索引是基于原始值构建的对列做运算后索引顺序被破坏优化器只能放弃索引走全表扫描。再看事务隔离级别。SQL标准定义了四个隔离级别读未提交、读已提交、可重复读、串行化。隔离级别越高一致性越强但并发性能越低。MySQL InnoDB默认使用的是可重复读这和Oracle等数据库默认的读已提交不同是面试的高频对比题。可重复读解决了脏读和不可重复读问题但依然存在幻读问题。InnoDB通过间隙锁Gap Lock和临键锁Next-Key Lock来解决幻读这也是一个可以在简答题里深入展开的点。4.3 手写算法题的现场策略编程题是Java笔试里最考验临场发挥的部分用友的编程题很少考特别难的算法主要集中在排序、字符串处理和简单数据结构应用上。根据以往考题的习惯出现频率比较高的题型包括冒泡排序或快速排序的手写实现、字符串去重与统计、数组去重、链表反转、求最大公约数、二进制中1的个数等。手写快排是笔试编程题里无法回避的一道题我建议所有准备Java笔试的人都把快速排序的代码练到肌肉记忆的程度。这里给一个可以直接默写的版本public void quickSort(int[] arr, int left, int right) { if (left right) return; int pivot arr[left]; int i left, j right; while (i j) { while (i j arr[j] pivot) j--; arr[i] arr[j]; while (i j arr[i] pivot) i; arr[j] arr[i]; } arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }这段代码用的是“挖坑填数”的双指针写法边界条件只判断left right不需要额外处理空数组和单元素数组。笔试时如果时间紧张写一个清晰正确的版本比追求最优解更重要因为判卷老师看的是代码逻辑和完整性。编程题的答题策略上我建议按四步走先花1分钟读懂题目要求边界条件列出来再用注释把解决思路写在代码前面这样即使代码没写完阅卷人也能看到你的思路然后写代码边写边注意空指针、数组越界、超大数溢出这些边界问题最后留2分钟自测两个用例一个是正常用例一个是极端用例。这四步能帮你避免因为粗心丢掉“会做的题”的分。5. 高频踩坑实录与实战建议5.1 笔试现场的五个常见失误这些年我复盘过不少考生的笔试情况发现真正丢分的地方往往不是不会做而是栽在一些看似不起眼的细节上。下面这五个失误在有志于通过Java笔试的群体里出现频率很高逐一列出来帮你避坑。第一大失误是选择题里的“编译能否通过”类问题。这类题会给你一段代码问你编译结果很多人在阅题时会下意识地“脑补”这段代码可以运行而忽略了语法层面的问题。比如抽象类里没实现抽象方法却把类写成普通类、接口方法没有加public却用了protected实现这些都是编译错误。建议遇到编译类题目时先在心里把代码“翻译”成对应的字节码行为而不是直接看逻辑是否正确。第二大失误是集合相关的空指针。笔试题经常考ArrayList、HashMap的null处理比如new HashMapString, String().get(key)返回null如果直接调用返回值的toString方法就会抛NullPointerException。很多人在刷题时把注意力放在数据结构特性上却忽略了返回值参与后续操作时的空指针风险。第三大失误是把“运行期异常”和“编译期错误”混为一谈。笔试选择题中经常会出现“以下哪个是受检异常”这种提问有人会把ArrayIndexOutOfBoundsException当成受检异常原因是对异常体系不熟。这类基础点的记忆没有捷径建议画一张异常体系图把Java标准库里的常见异常都归到受检与非受检两类中。第四大失误是重载和重写混淆。重载是同一个类中方法名相同、参数列表不同与返回值类型无关重写是子类重新实现父类方法方法签名必须相同且访问权限不能更低。笔试问“以下哪些属于重载”时如果选项里出现“返回值类型不同但参数列表相同”这不构成重载直接排除。第五大失误是自动拆装箱的隐藏陷阱。比如Integer a 128, b 128;中a和b用比较返回false因为Integer缓存范围默认是-128到127超出缓存范围会创建新对象而Integer c 100, d 100;用比较返回true。这个缓存机制在笔试题里出现频率非常高很多人记住了结果却说不清原因建议从Integer.valueOf的源码角度理解而不是死记硬背。5.2 从刷题到面试的复习路线参考如果你正在准备类似用友秋招Java岗这样的企业级笔试我个人建议不要死磕偏题怪题而是按“基础语法、集合、JVM、并发、数据库、框架”这个顺序逐块过关。基础语法部分把Java编程思想或官方教程过一遍重点看面向对象、异常、泛型、反射集合部分不要只看API用法要深入到源码级别比如HashMap的put流程、ArrayList的扩容机制JVM部分把内存区域、类加载过程、垃圾回收算法和常见参数背熟并发部分把synchronized、volatile、Lock、线程池、CAS这几个主题吃透数据库部分多动手写SQL重点练习多表查询和索引优化框架部分以Spring IoC和AOP为主理解核心原理。时间安排上如果还有两到三周时间我建议前两周做模块化复习每个模块搭配对应章节的练习题后一周集中做整套模拟卷并严格按照考试时间限制自己。模拟卷的错题一定要整理成错题库重点看错因是知识点疏漏还是审题失误。实操心得刷题过程中遇到不会的题先不要立刻看答案。拿一张纸写下你现在的思路再对照答案找出思路偏差在哪里。这个方法比直接背答案有效得多因为笔试考的是你做决策的过程而不是你记住的结论。很多通过了笔试的人在面试时被追问“为什么选这个方案”却答不上来就是因为当初刷题时根本没有想清楚推理链路只是背住了直观答案。这种薄弱点会直接影响后续面试建议从笔试阶段就养成“知其所以然”的习惯。按前文的复习框架把每个考点都过到“能讲给别人听”的程度再去做第六套这类整卷你就能明显感觉到做题速度和对答案的把握都不一样了。
返回列表