ARTICLE DETAIL

资讯详情

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

德邦2017秋招Java笔试题全解析:HashMap、JVM与线程池核心考点复盘

德邦2017秋招Java笔试题全解析:HashMap、JVM与线程池核心考点复盘 德邦2017秋招Java笔试试卷到现在我还能想起那份卷子的味道A4纸正反面印得密密麻麻选择题恨不得把Java基础翻个底朝天简答全是看似简单却容易阴沟翻船的问题最后两道编程题压轴时间紧到没空多想。那会儿我还在念大四跑去物流行业的独角兽公司投技术岗一轮笔试确实让我清醒了不少——校招笔试题看着基础其实背后想看的根本不是你会不会背而是有没有真的理解Java这门语言。这篇文章不是单纯发一份“回忆版真题”我想把它当成一次复盘把试卷里反复出现的知识点、当时大家普遍踩的坑、以及考后才想明白的底层逻辑都拆开聊一遍。不管你是正在备战秋招的应届生还是想自查Java基础的1到3年经验工程师这份拆解都能帮你少走点弯路。内容不局限于题目本身我会把每道题背后的原理、延伸出来的常考变体和实操排查方法一并讲清楚。1. 试卷整体设计与考点分布1.1 题型构成与答题节奏根据我当时参加线下笔试的体感德邦2017秋招Java试卷基本分为四类单选、多选、简答和编程题。客观题大概35题左右分值不高但覆盖面很广简答3到4题通常围绕面向对象、集合、JVM等高频方向编程题2题放最后考察基础算法和逻辑设计能力。总时长我记得是120分钟但120分钟让大多数人做完是很紧的。正常节奏应该是前30分钟处理完选择题别纠结再用20到25分钟解决简答题尽量把关键词写全最后留40分钟左右给编程题剩余时间检查客观题。我记得考场里很多同学在选择题上花费太多时间结果编程题只能草草写个思路。笔试不是要求你拿满分而是要在有限时间内展示出综合能力所以“先易后难、先拿分后抠分”是大原则。1.2 各模块知识点占比与考察意图我凭回忆整理了一个大体占比不一定完全精准但足以看出试卷重心模块大致占比典型考点考察能力Java基础语法与面向对象30%重载重写、String、equals/hashCode、static、final、抽象类与接口语言基本功集合框架15%HashMap原理、ArrayList扩容、HashSet去重机制源码理解能力并发编程10%线程状态、synchronized、volatile、线程池多线程基础JVM与内存10%运行时数据区、垃圾回收、内存溢出深层次理解Spring与MyBatis15%IoC、AOP、Bean生命周期、#{}和${}企业开发常识数据库与SQL10%SQL写法、事务隔离级别数据能力算法与编程题10%排序、链表、逻辑设计编码功底从占比能看出德邦这类物流企业招Java工程师并不追求你懂多高深的大数据框架而是希望你先能扎实地理解Java核心毕竟仓储、运输、订单这些核心系统的稳定性全靠一线开发的基础是否扎实。试卷里很多题都是把“背过的答案”换了个马甲比如多选中问“下面哪些做法可能导致内存溢出”其实就是变着法子考JVM和集合使用。2. 核心Java基础考点解析2.1 面向对象、重载重写与多态难题笔试里出现频率最高的永远是面向对象。我记得有一道简答题是“重载和重写的区别分别说明编译期和运行期的行为”这类题看似送分但想拿满并不容易。重载是同一个类里的多个方法方法名相同但参数列表不同它是编译期多态重写是子类重新定义父类方法方法签名必须一致它是运行期多态。真正容易出错的进阶问法是父类引用指向子类对象时调用一个静态方法会发生什么调用一个私有方法呢答案是无论引用类型是什么静态方法都属于类本身跟实例无关所以调用的是引用类型的方法私有方法不能被子类重写也同理。再比如“编译看左边运行看右边”这句话只适用于实例方法不适用于静态方法和成员变量很多人一紧张就答串了。一个更深的考察点是构造方法执行顺序。笔试中经常给出一个父类和子类各自有静态代码块、构造代码块、构造方法问创建子类对象时顺序如何。正确顺序是父类静态代码块 - 子类静态代码块 - 父类构造代码块 - 父类构造方法 - 子类构造代码块 - 子类构造方法。我当时把“父类构造代码块和父类构造方法”的顺序写反了痛失一分。理解这个顺序的关键是记住静态初始化先于实例初始化父类初始化先于子类初始化。2.2 String、equals与hashCode的连环坑String类是Java笔试的常青树。我记得有道题问“String、StringBuilder、StringBuffer的区别”这种题现在已经在八股文里被反复咀嚼了。重点是String不可变StringBuilder线程不安全但效率高StringBuffer线程安全但效率低。但德邦的卷子喜欢多走一步问String str new String(abc)到底创建了几个对象。这个问题的正确答案是如果常量池里已经有“abc”则只在堆上创建一个对象引用指向堆同时字符串内容指向常量池如果常量池里没有“abc”则会创建两个对象一个在常量池一个在堆上。注意不同JVM版本存储位置不完全一样但笔试一般按常量池在方法区这种经典答案来处理比较稳妥。equals和hashCode那题我记得很清楚HashSet判断元素重复的流程是先算hashCode定位到桶再调用equals比较。如果hashCode相同但equals不等会放在同一条链表上如果hashCode不同equals根本没机会执行。所以为什么要重写equals就必须重写hashCode因为如果不重写两个业务上相等的对象可能hashCode不一致导致HashSet认为它们不重复HashMap也存不进去逻辑就乱了。笔试中还经常给一段代码比如用equals比较两个字符串是否相等实际上String已经重写了equals所以比较的是内容而不是引用。最容易错的是Integer的-128到127缓存机制Integer a 127; Integer b 127; a b为true但改成128就变成false。这个坑其实是JavaIntegerCache默认缓存区间的副作用理解之后连带着把自动装箱拆箱的题都能做对。2.3 集合框架源码与HashMap底层集合框架考得最多的是HashMap。德邦那道选择题差不多是HashMap底层结构是什么JDK8之后是数组链表红黑树扩容和树化时要关注阈值。默认初始容量16负载因子0.75当链表长度大于等于8且数组长度大于等于64时链表转红黑树。红黑树的引入是为了防止极端哈希冲突导致查询退化成链表。很多同学背这些参数背得很熟但问到“为什么负载因子是0.75”就沉默了。这其实是一个时间与空间的折中负载因子太小会导致频繁扩容浪费空间太大又会让哈希冲突概率上升影响查询效率。0.75在统计学上算是能接受的平衡点。扩容是旧数组长度左移一位即翻倍并且JDK8在扩容时引入了尾插法避免JDK7头插法在高并发下产生循环链表的问题因为HashMap从来就不是线程安全的并发场景要用ConcurrentHashMap。ArrayList的考点集中在扩容机制和与LinkedList的对比。ArrayList默认扩容为原容量的1.5倍第一次add时若为空数组会扩容到10。有个细节new ArrayList()和new ArrayList(0)的初始处理不一样0容量时第一个元素插入也会触发扩容到10。和LinkedList对比时不要张口就说“ArrayList查询快LinkedList增删快”应该补充说明ArrayList通过索引查询是O(1)LinkedList查询是O(n)增删要看操作位置ArrayList在尾部add是O(1)在中间插入需要移动元素LinkedList在中间插入虽然不需要移动元素但定位到那个位置本身是O(n)。这种有条件的回答在面试里也加分。2.4 异常体系与try-with-resources异常那部分我印象最深的是简答题“Error和Exception有什么区别RuntimeException与非RuntimeException有什么区别”第一个问题好答Error是JVM层面的严重错误程序一般无法恢复Exception是程序可以捕获处理的异常。第二个问题则要分清编译时异常和运行时异常。FileNotFoundException、SQLException这些除RuntimeException外的异常编译器强制你处理否则写不通过NullPointerException、ArrayIndexOutOfBoundsException等运行时异常则不需要显式捕获。还有一道很容易答错的题try块里有returnfinally块里也有return执行顺序是什么答案是finally里的return会覆盖try里的return所以结果以finally为准。但更推荐的做法是不要在finally里写return这样会让代码逻辑混乱。如果你实在想展示高水平可以提一下System.exit(0)会终止JVM此时finally块不会执行。有个例外是如果在finally里又抛出异常原始异常会被吞掉这种问题在JDK7的try-with-resources里经常被用来做文章。try-with-resources是Java 7引入的语法糖只要是实现了AutoCloseable接口的资源都可以放在try后面的括号里使用结束后JVM会自动关闭。笔试中如果给一段旧代码用BufferedReader读文件问怎么优化最佳回答是改成try-with-resources。注意它关闭资源的顺序是逆序的先声明的后关闭。虽然这个细节笔试出过的不多但我在实际代码评审里见过有人因为这个顺序问题被坑过。3. 并发、JVM与性能调优除了八股文还得会排查3.1 并发编程高频题线程状态、锁与volatile并发模块在德邦试卷里不算重但基本每年都会出两三题。最常见的是线程状态机就那个NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED考法包括“调用sleep和wait后线程进入什么状态”“synchronized锁竞争失败后进入什么状态”。sleep进入TIMED_WAITINGwait进入WAITING锁竞争失败进入BLOCKED这些基础要烂熟于心。synchronized和volatile是常客。你不仅要答出synchronized是重量级锁最好再补充它在JDK6之后经过锁升级变成了无锁、偏向锁、轻量级锁、重量级锁的渐进过程。volatile则要抓住关键三点保证可见性、禁止指令重排、不保证原子性。笔试常问“volatile能否替代synchronized”标准答案就是不能因为它无法保证复合操作的原子性比如i这种操作volatile达不到线程安全效果。另外有一个题目版本很经典“两个线程交替打印1到100怎么实现”虽然这在LeetCode不算难但笔试现场手写容易出bug。常规方案是用synchronized wait/notify或者用Lock Condition但更简洁的是用一个volatile变量加CAS自旋。不过手写时我建议用wait/notify因为更容易让阅卷人一眼看穿你的思路。记住wait/notify必须在synchronized块里调用否则会抛IllegalMonitorStateException。3.2 线程池参数与任务拒绝策略线程池这个考点在2017年已经开始流行了现在几乎成了Java面试必问。德邦的题我记得是给出一段通过Executors.newFixedThreadPool创建线程池的代码问有哪些问题和替代方案。问题在于FixedThreadPool使用无界LinkedBlockingQueue当任务量突增时队列会无限堆积最终导致内存溢出。所以规范做法是直接用ThreadPoolExecutor构造器显式指定核心线程数、最大线程数、空闲时间、工作队列和拒绝策略。线程池有四个拒绝策略AbortPolicy直接抛异常、CallerRunsPolicy让提交任务的线程自己执行、DiscardPolicy默默丢弃、DiscardOldestPolicy丢弃最老的任务后重新提交。笔试题常问“如果队列满了怎么办”答案就是从这四个策略里选一个适合业务场景的。但更高级的回答是结合自己的项目说比如我们会在任务提交端做降级或者把关键任务发到消息队列而不是只背策略名。还有一个被反复问的核心问题核心线程数怎么设定这里不要背网上流传的那些“CPU密集就N1IO密集就2N”公式最好能理解原理。CPU密集任务应该接近CPU核心数减少无谓上下文切换IO密集任务因为线程常被阻塞等待可以开更多线程去利用CPU。比如我用一个Web服务每个请求都有数据库I/O核心线程数就可以设置成CPU核数的数倍。笔试没必要写太长但写上这两句原因分数肯定比只写公式高。3.3 JVM运行时数据区与对象创建流程JVM题在德邦试卷里通常有两三道最基础的是“JVM运行时数据区有哪些区域”。标准回答是程序计数器、虚拟机栈、本地方法栈、堆、方法区有的答案算上运行时常量池。注意程序计数器是唯一不会OutOfMemoryError的区域堆和方法区是最容易出现内存溢出的区域。再进阶一点会问“对象创建过程”。一道简谈话题是new一个对象在JVM中经历了什么从类加载后的类检查开始然后分配内存、初始化零值、设置对象头、执行构造方法。内存分配方式有指针碰撞和空闲列表两个选择取决于垃圾回收器是否带压缩整理功能并发环境下还要通过CAS或本地线程分配缓冲来解决指针竞争。选择题喜欢在这个地方挖坑比如“哪个区域不会OOM”——答案是程序计数器。3.4 垃圾回收算法与内存溢出实战垃圾回收算法主要有标记-清除、标记-复制、标记-整理。标记-清除有碎片问题效率不稳定标记-复制将内存分成两块浪费空间标记-整理则把存活对象往一边移动适合老年代。新一代收集器如G1、ZGC引入了区域化和并发标记笔试如果只考算法名字和特点就按经典答案写。和垃圾回收紧密相连的是内存溢出结合热词里的“java: outofmemoryerror: insufficient memory”这个我相信很多人在实际开发里都遇到过。笔试可能会这样出一个JVM进程频繁Full GC但内存占用不断上升你的排查思路是什么正确答案是先用jps找到进程号再通过jmap -heap查看堆内存使用用jstat -gcutil看GC频率和年轻代老年代使用率然后jmap -dump导出堆转储文件最后用MAT或VisualVM分析大对象和GCRoot引用链。我当时就遇到过一次实际线上的OOM一个批量导出订单功能每导出一个Excel就往内存里塞一条订单DTO结果攒到几百万条堆直接爆了。排查后定位到是List在循环里无限累积换成分批写文件后问题解决。这个案例说明笔试光背概念没用最好结合自己的项目讲出完整的定位过程。如果你的答案里有“通过堆转储分析发现X对象占用了80%内存”这种细节阅卷人就会觉得你是真懂。4. Spring与MyBatis笔试里少不了的框架题4.1 Spring IoC/AOP与Bean作用域框架部分比重不小核心是Spring。IoC和AOP几乎是必考而且经常会问“你对IoC的理解”。不要只答“控制反转就是把对象创建交给容器”要往下说一层Spring容器通过反射创建Bean、管理依赖关系让开发者只关心业务逻辑依赖注入是IoC的具体实现方式支持构造器注入、Setter注入和字段注入。我在现代代码规范里都推荐构造器注入因为它让依赖关系以不可变的形式暴露出来也方便单元测试。AOP的经典应用是事务、日志、权限。有些题会问“Spring AOP和AspectJ有什么区别”你可以说Spring AOP基于动态代理只支持方法级别的切面而AspectJ是编译期植入支持更丰富的连接点。如果考到动态代理JDK动态代理要求目标类实现接口CGLIB代理不要求Spring在选择上如果目标类实现了接口则默认JDK动态代理否则CGLIB。Bean作用域问得最多的是singleton和prototype。singleton是默认的容器启动时就可能创建对象prototype是每次获取都创建新对象。再往下问有时会牵扯到ThreadLocal和Session作用域但我遇到的2017笔试更关注singleton和prototype下的线程安全问题。记住singleton Bean在并发场景下如果有可变成员变量要小心数据竞争解决方法是使用ThreadLocal、加锁或改成无状态。SpringMVC的Controller默认就是singleton所以不要在Controller里保存可修改的成员字段。4.2 SpringMVC执行流程与请求处理SpringMVC的执行流程也是基础题。笔试一般给流程让你排序请求先到DispatcherServlet然后HandlerMapping找到对应的Handler再通过HandlerAdapter调用Controller方法返回ModelAndView经视图解析器生成View最后渲染响应。关键是要记住所有组件都围绕DispatcherServlet展开。常见变体是“RequestMapping和GetMapping有什么区别”后者是前者的简化和约束限定HTTP方法为GET。还有RequestBody和ResponseBody一个负责把请求JSON反序列化成对象一个负责把返回对象序列化成JSON。如果试卷出“前端传的JSON字段名是user_name后端字段是userName怎么处理”答案是用JsonProperty注解或者在配置里开启下划线转驼峰映射。很多同学死记注解含义遇到这种具体场景就懵了所以我一直强调笔试也要看题眼。4.3 MyBatis的#{}和${}以及事务管理MyBatis在当时的物流项目里用得非常多所以德邦卷子通常会有1到2题。最经典的区别题就是MyBatis中#{}和${}有什么区别#{}是预编译占位符会生成?传给JDBC能有效防止SQL注入${}是字符串拼接直接替换进SQL有注入风险但某些动态场景比如传入表名、列名、排序字段又不得不使用。所以回答要辩证一点能用#{}尽量用#{}必须用${}时一定要做白名单校验。事务这块往往和Spring联系起来考。Transactional的失效场景是个高频坑类内部调用、方法不是public、异常被try-catch捕获、数据库引擎不支持事务、传播行为设置错误。我当年笔试遇到的是“为什么同类中调用另一个Transactional方法事务不生效”其实就是因为动态代理只能拦截外部调用内部调用this.method()不会经过代理对象。解决方式有通过AopContext.currentProxy()获取代理对象或者把方法拆到另一个Service里调用。事务传播机制也是常客。REQUIRED、REQUIRES_NEW、NESTED、SUPPORTS这些概念至少要知道前两个。REQUIRED是默认的存在事务就加入不存在就新建REQUIRES_NEW是挂起当前事务新建一个独立事务。假如一个批量导入Excel的服务里每条记录插入都用REQUIRES_NEW最后一条失败会不会影响前面答案是不会因为每条都是独立事务。但要注意这种写法会频繁提交事务性能较差。5. 编程题实战还原从冒泡排序到设计题5.1 排序算法冒泡与快排实现及优化德邦试卷最后两道编程题第一题通常是排序。2017年我记得考的是“手写冒泡排序并说明时间复杂度”这题简单到有点意外但越简单越容易栽跟头。我写代码时差点把flag忘掉导致不管数组是否有序都会执行完所有轮次。正确的冒泡排序加了个优化public void bubbleSort(int[] arr) { if (arr null || arr.length 2) return; int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) break; } }这个flag可以在数组已经有序的情况下提前结束循环时间复杂度最好情况降到O(n)。笔试阅卷看到这个细节基本就是满分态度。同时还要答出最坏和平均时间复杂度都是O(n²)空间复杂度O(1)是稳定排序。如果扩展到快速排序还要注意选基准、分区和递归边界。快速排序的常见坑是递归导致栈溢出以及最坏时间复杂度退化为O(n²)。笔试手写快排时用左指针和右指针往中间挤写完后自己先跑几个例子验证。我用Java实现了一个经典版本public void quickSort(int[] arr, int left, int right) { if (left right) return; int pivot arr[left (right - left) / 2]; int i left, j right; while (i j) { while (arr[i] pivot) i; while (arr[j] pivot) j--; if (i j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; i; j--; } } quickSort(arr, left, j); quickSort(arr, i, right); }笔试时最好在方法注释里写明pivot取中位数或随机选择这样能尽量避免最坏情况。面试官或阅卷人看到这种细节会觉得你不是只会背代码。5.2 链表反转、括号匹配与二分查找除排序外第二道编程题有时候是链表和栈相关的题。链表反转是超级经典我当年因为迭代写法不够熟练只能先用递归写但没写出边界条件差点翻车。迭代反转要注意保存next节点再用三个指针逐步调整。代码是这样的public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode next curr.next; curr.next prev; prev curr; curr next; } return prev; }括号匹配这道题也经常作为“栈”的应用出现判断字符串里的()[]{}是否合法核心是遇到左括号入栈遇到右括号弹栈比对。这种题考察的不是算法难度而是是否能在白板或纸上把思路完整呈现出来。二分查找则需要小心mid的计算我习惯用mid left (right - left) / 2避免leftright可能溢出还要注意循环条件是left right。这类题目在笔试中很能拉开区分度思路完整但细节有bug的人和大题能全对的人一眼就分出来了。5.3 场景设计题物流订单状态机德邦毕竟是物流公司编程题有时候会结合业务出场景设计题。我记得有一道大概是“设计一个简单订单状态流转模型订单可能有新建、已揽收、运输中、已签收、已取消等状态如何控制状态合理流转”。这本质上是一道状态机设计题。正常回答要分几个层面状态定义用枚举避免魔法数字状态流转用状态机或状态表非法状态迁移要抛异常或拒绝操作。简单实现可以用Map定义每种状态可以合法转移到的目标状态提交状态变更时先查映射表再执行更新。如果有更复杂的权限逻辑可以用状态模式把每个状态封装成类但笔试写清思路即可。这种题突出的是工程思维。它要求你不仅会写算法还能把业务规则落到代码结构里。我当时回答时提了一句“高并发场景下可以使用Redis锁或分布式锁防止重复签收”虽然不知道加不加分但至少表达了对真实场景的考虑。校招笔试最终想筛选的就是这种能把“代码能力”和“业务理解”结合起来的人。6. 笔试经验与备考策略给正在准备秋招的你6.1 做题顺序与时间分配很多人笔试失败不是不会而是时间分配失衡。我的方法很固定先快速扫一遍试卷全貌把编程题看一眼记住题型和难度不做然后从头做选择题遇到卡壳超过两分钟的先标记跳过接着做简答题把能确定的关键词写上去不要写长篇大论省时间最后集中火力做编程题。编程题只靠“想”是不行的至少留下40分钟动手。如果看到不会的题也应该把思考过程和伪代码写出来阅卷人看的是思路哪怕不能真正跑通也比空白强。我还记得当时旁边一个同学排序题写了个大概但没有处理空数组我猜他这类细节扣分很吃亏。6.2 八股文要背到什么程度从热搜词“java面试八股文”就能看出现在大家备考都爱背八股。背诵本身没问题问题在于只背不理解。你背了“HashMap默认容量16、负载因子0.75”但笔试题只要换个角度问“为什么是0.75”你如果只能背数字就答不出来。所以正确策略是每天背10个基础题同时追着“为什么”去挖一层源码或看一篇原理讲解。我当时给自己定了个规则一个知识点能用自己的话给另一个同学讲明白才算掌握。比如“volatile保证可见性”不能只背这一句要能解释“线程A修改了变量线程B可能一直使用缓存中的旧值而volatile会让修改立刻刷新到主内存并且失效其他线程的缓存行”。用大白话能讲出来面对笔试的简答题也就不怕了。6.3 踩坑记录环境、读题与边界条件笔试中容易踩的坑不只是技术本身。有一些小细节在平时写代码时不觉得考试时却很致命。第一是读题不仔细有的题要求写出“算法思路和复杂度分析”很多人只写了代码白白丢分。第二是代码风格问题变量命名像a、b、c倒还好但阅卷人一定更愿意看含义明确的命名。第三是边界条件排序题里空数组、单元素数组链表题里null节点这些都要提前处理。还有一个很现实的坑本地IDE里能跑通不代表写得规范。笔试答题纸或在线编辑器不会给你编译反馈所以写代码时要格外注意语法和分号。我通常会先在草稿纸上列出特殊输入再在代码注释里写出“如果是空数组直接返回”这样哪怕代码有笔误阅卷人也知道你考虑到了。6.4 校招Java学习路线建议看完德邦2017的卷子你会发现它考的知识点并不超纲几乎是Java学习路线中段的内容。如果现在的你还在大二大三建议按这个顺序打基础先搞定JavaSE基础包括集合、异常、IO、多线程然后学JVM入门不用太深但要理解内存区域和垃圾回收再学数据库和SQL掌握索引、事务接着接触Spring和MyBatis能写CRUD不足以通过笔试要看懂IoC和AOP原理最后是算法至少把排序、链表、二叉树、哈希这些刷熟。大四阶段则可以按“每天一套笔试题”的节奏冲刺。重点不是题量而是每做一套都要复盘错题对应哪个知识点原理是什么再找两三道同类型变体巩固。德邦的卷子给我一个启发校招题不会故意为难你它考察的就是你是否愿意花时间把这些基础点真正搞明白。把基础吃透比背再多偏题怪题都管用。写在最后的体会现在回头看德邦2017秋招Java笔试试卷可能不是最难的一份但它很有代表性。它逼着我把“会写Java”和“理解Java”区分开来也让我意识到笔试不只是考试更是一次系统自查。当时我在HashMap和线程池那块丢了不少分实习后遇到线上OOM才真正理解那些知识点背后代表的是真实的生产问题。如果你正在准备秋招不要只盯着题目本身而是把每个考点都想象成实际工作里可能出现的场景带着这个思路去复习效果会好很多。希望这份拆解能给你一些参考祝笔试顺利早日拿下心仪的offer。
返回列表