ARTICLE DETAIL

资讯详情

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

有赞2019校招Java笔试A卷盘点:从基础语法到高并发核心考点

有赞2019校招Java笔试A卷盘点:从基础语法到高并发核心考点 有赞2019校招Java笔试A卷这个话题在准备校招的同学里已经流传了好一阵子。很多人拿到这套题的第一反应是找答案、背考点但我更建议你先想清楚一个底层问题一家做电商SaaS的公司为什么笔试里要问那么多Java基础答案其实蛮直接有赞这种业务背景系统要面对真实的交易、库存、营销活动代码跑在线上是要替商家扛住真金白银的流量的。所以笔试想筛选的不是谁背过的面试八股文多而是你在高并发、缓存、分布式这些真实需求面前有没有扎实的Java基本功。这篇文章不主张你死记硬背而是围绕这套A卷常见的考察模块把这背后真正该理解的知识点、踩过的坑、以及可复用的答题思路一次讲清楚。1. 项目概述这套A卷到底在考什么1.1 校招笔试背后的考察逻辑先说一个很多人会忽略的事实校招笔试和社招笔试的考察目标完全不一样。社招你还能指望候选人带着项目经验来讲业务沉淀校招大家基本都是白纸学校教的课程又差别很大这时候笔试的作用就不是“考你会不会某个框架”而是“验证你有没有成为合格Java工程师的潜力”。有赞的笔试内容覆盖面很广从面向对象、集合框架、并发编程到JVM、Spring Boot、MySQL、Redis再到手写排序算法基本是把一个Java工程师日常工作中最常接触的知识点全部划了一遍。这种设计是有道理的电商SaaS的核心链路是订单、商品、会员、营销、支付这些模块没有一个是脱离Java基础能写好的。比如订单状态流转你要理解枚举和状态机高并发下库存扣减你要理解线程安全和锁会员标签筛选你要熟悉集合操作和Lambda表达式。所以我的建议是不要把这套题当成“要背的题库”而是当成“知识体检单”。你哪个模块答得模糊说明哪个模块平时只停留在“能用”的层次还没有到“懂原理”的层次。这种体检越早做越好因为查漏补缺的成本远低于面试时被问倒的成本。1.2 答题顺序与时间分配建议校招笔试的时长一般控制在120分钟到150分钟题目结构大致是选择题单选多选混合、代码阅读或简答题、编程题。我的习惯是拿到卷子先花两三分钟把整张卷子扫一遍然后按“编程题→代码阅读→简答→选择”的顺序来做。为什么建议先把编程题做了因为编程题的分数权重通常最高而且它考察的是你能否在限定时间内写出可运行的代码这恰恰是最容易通过提前训练拿稳的部分。先把大分拿到手心态就会稳很多。选择题里那些拿不准的先标记好跳过等所有大题做完再回头看不要在某个选项上耗五分钟。还有个容易被忽略的细节笔试环境里往往没有IDE的自动补全和编译提示你要在文本编辑器里直接手写代码。所以平时练习的时候别一直依赖IDE偶尔用文本编辑器裸写几个类感受一下“没有提示”的状态上考场会从容很多。2. Java基础考点不是让你背定义是看你会不会用2.1 面向对象三大特性的面试级理解面向对象是Java笔试必考模块但考法往往不是“说说什么是封装”而是“给你一个业务场景请你设计一个可扩展的类结构”。这时候你只背概念是不够的得真正理解封装、继承、多态在代码里是怎么落地的。以电商订单为例。订单状态从待支付到已支付到已发货每一步都有对应的业务规则。如果代码里所有地方都能直接修改订单的status字段那上线两周必然乱成一锅粥。正确的做法是把订单状态变更的行为收敛到订单对象内部对外只暴露类似pay()、ship()、complete()这样的业务方法内部再做状态合法性校验——这就是封装的意义。继承和多态在电商场景里更常见。比如同一笔订单普通会员、VIP会员、企业客户享受的折扣策略不一样。如果你写三个if分支硬编码那每加一种客户类型就要改一遍核心代码迟早出事故。更好的方案是定义一个DiscountStrategy接口让不同等级客户各自的类去实现calculate(Order order)方法再用一个工厂把客户类型映射到对应的策略对象。这样主流程代码一行都不用改新增一个客户类型只是新增一个类的事。笔试里“设计一个能灵活扩展的订单计费系统”这种题本质就是在考面向对象的抽象能力。2.2 运算符、表达式与枚举的细节陷阱选择题里运算符和执行顺序的题目出错率一直很高。我印象很深的几类题目a与a的返回值区别、位运算的优先级、字符串拼接时隐式类型转换、三目运算符两个分支类型不一致时的自动类型提升。这些题目在真实开发里也会冷不丁冒出来比如写缓存key拼接的时候数字和字符串混着加稍不留神就拼出意料之外的字符串。所以考试前把Java运算符优先级表重新过一遍性价比很高。枚举是另一个高频考点而且它不只是“定义几个常量”这么简单。在订单状态机场景里枚举可以携带状态码、描述文本甚至可以在枚举内部定义抽象方法让每个枚举值分别实现对应的处理逻辑。举个例子定义一个OrderStatus枚举包含PAY_WAITING、PAID、SHIPPED、COMPLETED、CLOSED几个值每个值关联“状态码”和“中文描述”同时提供一个nextStatus()方法明确当前状态可以合法流转到哪些状态。这样订单状态变更的合法性校验就收拢在一个类里了业务层调用时非常干净。Lambda表达式和函数式接口是Java 8之后的高频考点。笔试里常给你一段用匿名内部类写的排序代码让你改成Lambda写法或者反过来问你Lambda表达式的底层原理。Lambda本质上是函数式接口的实例编译器会把它转换成invokedynamic指令再配合LambdaMetafactory生成一个实现了目标接口的匿名类。理解到这层你就不会把它当成魔法而能解释为什么Lambda只能访问effectively final的局部变量了。2.3 数组、异常与常用类的必踩深坑数组越界是Java初学者最常碰到的异常笔试里也特别喜欢出这类代码阅读题。这里想提醒一个容易被忽略的操作用Arrays.asList()把数组转成List后得到的List是定长的不能调用add()和remove()否则会抛UnsupportedOperationException。很多人不知道这个细节代码写得很开心一跑就废。ArrayList遍历过程中用remove()删除元素也会因为modCount变化触发ConcurrentModificationException。笔试里这种题目就是考察你有没有真正写过会踩坑的代码。异常处理这块我见过太多“捕获了异常却什么都没做”的代码也就是空catch块。这种代码在笔试里可能看不出问题但在真实业务里是灾难——线上出了问题日志里什么都查不到只能靠猜。正确的做法是捕获异常后要么记录日志并抛出合适的上层异常要么至少打一行包含上下文的错误日志。笔试简答题如果问“finally块里return会发生什么”要能说清楚finally块中return会覆盖try块中的返回值而且这是一种极其容易让同事抓狂的写法生产环境建议直接禁止。常用类里String、StringBuilder、StringBuffer的对比是必考内容。String不可变每次拼接都会创建新对象循环里做大量拼接会产生大量中间对象白白触发GC。StringBuffer是线程安全的方法用了synchronized修饰StringBuilder是线程不安全的但单线程下性能更好。笔试选择题喜欢在“拼接10000次字符串哪个性能最优”这种题里埋伏笔答案通常是StringBuilder因为绝大多数场景都是单线程操作加锁的成本完全没必要。3. 集合框架与并发编程代码能不能扛住大促流量全看这里3.1 从HashMap到ConcurrentHashMap源码层面拆解集合框架是Java笔试的重中之重而HashMap又是集合框架里的重中之重。有赞这种电商公司业务天然带着高并发属性所以它对候选人集合功底的要求会比普通公司更严格。笔试里常问的HashMap问题基本集中在几个点底层结构是什么、put操作的完整流程、为什么容量是2的幂、什么条件下链表转红黑树、为什么JDK 8要把头插法改成尾插法。先说过一遍put流程。HashMap底层是数组链表红黑树的结构当你调用put(key, value)时会先对key的hashCode做一次扰动计算让高位也参与低位的哈希运算然后再和数组长度减1做位与运算得到桶的位置。如果当前桶是空的直接放进去如果桶里已经有元素就用equals方法判断key是否已存在存在则覆盖value不存在则追加到链表尾部。当链表长度达到8、且数组长度达到64时链表会转成红黑树把查询时间复杂度从O(n)降到O(log n)。当元素个数超过数组长度的0.75倍时触发扩容容量翻倍然后重新计算所有元素的位置。那么为什么JDK 8要把头插法改成尾插法因为头插法在扩容时会发生链表逆序多线程环境下扩容时容易出现循环链表一旦get操作遍历到环上就死循环了CPU直接飙满。JDK 8改成尾插法后扩容时链表顺序保持不变死循环问题得到缓解。这里要特别注意虽然死循环问题缓解了但HashMap在多线程下仍然线程不安全并发put可能丢数据所以高并发场景永远不要裸用HashMap。这也自然引出了ConcurrentHashMap。JDK 8的ConcurrentHashMap抛弃了JDK 7的分段锁设计改用CASsynchronized锁桶头节点的方式锁粒度更细并发度更高。源码里每次put都会做一个循环先判断桶是否为空为空时用CAS直接把节点放进去如果桶不为空就锁住头节点再进链表或红黑树操作。因为大多数写入操作都只锁一个桶不同桶之间的写入可以完全并行所以整体吞吐量很高。3.2 线程池、锁与ThreadLocal的实战视角并发编程的笔试考点里线程池出现的频率非常高。我整理过一套必须烂熟于心的参数核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。线程池提交任务时的执行顺序是当前线程数小于核心线程数时直接创建新线程执行超过核心线程数时任务先进入阻塞队列排队队列满了且线程数还没到最大线程数才继续创建新线程连最大线程数都占满了就触发拒绝策略。有赞这种电商场景里线程池的队列我会建议用有界队列。无界队列看起来方便但一旦瞬时流量上来任务就会无限堆积在内存里最终把内存耗尽触发OOM而且系统恢复起来特别困难。有界队列配合合适的拒绝策略相当于给系统加了一个流量开关超出的请求快速失败或者降级总比拖垮整个应用强。这背后体现的其实就是服务降级和保护的思想也是面试官想从你嘴里听到的。ThreadLocal也是高频考点。它最经典的用法是解决SimpleDateFormat线程不安全的问题每个线程自己持有一个SimpleDateFormat实例各用各的互不干扰。笔试喜欢问的是ThreadLocal的内存泄漏问题——ThreadLocalMap里的Entry继承了WeakReferencekey是弱引用value是强引用。当外部强引用被移除后key会被GC回收但value还残留在线程的ThreadLocalMap里如果线程是长期存活的比如线程池里的线程value就永远不会被回收最终把堆内存耗尽。这种问题的标准做法是用完ThreadLocal后在finally块里主动调用remove()清理同时ThreadLocalMap在get/set时也会做一部分过期Entry的清理。笔试简答题考到这里你能说出“从ThreadLocalMap中remove掉当前ThreadLocal对应的Entry防止value一直被强引用”这个层面就已经比只会背概念的人强一个档次了。4. JVM与Spring Boot笔试里最容易被拉分的部分4.1 JVM内存结构、类加载与OOM排查思路JVM几乎是每套Java笔试的必考内容有赞的A卷也不例外。这部分最基础也最核心的是把JVM运行时内存区域彻底搞清楚程序计数器、虚拟机栈、本地方法栈、堆、方法区。栈管运行堆管存储这是最简洁的记忆方式。每个线程都有自己的虚拟机栈栈里是一个个栈帧每个方法调用对应一个栈帧所有线程共享堆和方法区对象实例放在堆里类信息、常量、静态变量放在方法区。类加载这块笔试爱考双亲委派模型。你从JDK自带的ClassLoader角度看应用类加载器会先把加载请求委派给扩展类加载器扩展类加载器再委派给启动类加载器。只有父加载器无法加载时子加载器才自己去加载。这种机制的核心目的是防止核心类库被篡改比如你自己写一个java.lang.String因为启动类加载器已经加载过标准String了你这个类根本加载不进来也就无法破坏JDK的类库。OOM是另一个重要考点结合网上常有人报的“java: outofmemoryerror: insufficient memory”这个报错我想展开说说实际排查思路。遇到OOM第一步不是去猜而是先看日志。如果日志里打印了“Java heap space”说明堆内存不够通常有两种可能要么是配置给堆的内存确实偏小要么就是代码里有内存泄漏对象一直被引用无法回收。我的排查习惯是先dump出堆快照然后打开分析工具从占用最大的对象路径入手顺着引用链看看到底被谁强引用了。印象里有一次线上OOM最后定位到一个静态Map不停往里塞数据没有清理机制几百个线程同时塞内存直接被打爆。这个问题在笔试里考到你能说出“静态集合生命周期和JVM一致存放数据必须评估容量上限和清理策略”就已经从“知道OOM是什么”升级成“能解决OOM”了。4.2 Spring Boot核心注解与接口安全设计Spring Boot是校招笔试里框架部分的重点虽然它不会像Java基础那样逐行问源码但核心注解的使用场景一定要熟。RestController用来声明一个返回JSON的控制器Service标记业务层组件Repository标记数据访问组件Autowired和Resource做依赖注入Configuration配合Bean声明配置类。笔试里经常给一段代码让你选择缺少的注解或者问某个注解的作用范围这些都是死分数记牢了就不会丢。Spring AOP的原理也值得花时间理解。AOP底层是动态代理——目标类实现了接口时Spring优先用JDK动态代理基于接口生成代理对象目标类没有实现接口时用CGLIB生成目标类的子类作为代理。笔试如果问“Spring事务为什么有时候会失效”很多答案指向的就是这个机制如果类使用了内部方法调用代理对象无法拦截到内部自调用事务注解就失效了。理解了代理帮你拦截你自然就能想到只有从外部通过代理对象调用的方法才会被事务增强。接口安全这块近年来越来越多公司会在笔试里结合实际场景考察。热词里有“java springboot apikey 安全对接”这在电商SaaS里是非常真实的痛点你的服务要开放给第三方商家调用怎么保证请求是合法的、内容没有被篡改常规做法是双方约定一对AppKey和AppSecret调用方请求时用AppSecret对参数做签名把签名值放在请求头里服务端用同样的规则计算签名并比对。签名算法要包含请求时间戳服务端对时间差超过一定阈值的请求直接拒绝用来防重放攻击。这套流程在笔试里不会要求你写完整实现但如果你能画出“密钥管理→签名生成→服务端验签”的链路面试官至少会认为你做过真实对外接口对接。5. 算法与手写代码笔试里的硬通货5.1 排序算法冒泡排序和快速排序的复杂度与实现算法部分通常是校招笔试里拉开差距的地方但别被吓到大多数公司考的不会太偏主要集中在排序、查找、链表操作和简单动态规划上。有赞A卷涉及的排序题属于最经典、最值得反复练的内容。先看冒泡排序。它的核心思路是相邻元素两两比较大的往后移每一轮都让当前未排序区间的最大值冒到末尾。两层循环外层控制轮数内层控制比较范围时间复杂度是O(n^2)。这个算法胜在简单直观笔试里偶尔让你手写但更多是作为“基础算法复杂度对比”的参照物出现。快速排序才是笔试里的常客。快排的核心是分治选一个基准值把数组分成小于基准值和大于基准值的两部分然后递归处理两部分。平均时间复杂度O(n log n)最坏情况O(n^2)但通过随机选择基准值可以大幅降低最坏情况出现的概率。下面给一个可以直接默写的实现public void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left (right - left) / 2]; int i left; int j right; while (i j) { while (arr[i] pivot) { i; } while (arr[j] pivot) { j--; } if (i j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; i; j--; } } quickSort(arr, left, j); quickSort(arr, i, right); }这段代码里最容易写错的地方是递归的边界条件。基准值取中间值后左半部分递归到left到j右半部分递归到i到right千万不能把j和i写反否则会陷入死循环。我当年就犯过这个错笔试时在草稿纸上推演了好几遍才发现边界弄错了。建议你在平时练习时专门写个测试用例用随机数组验证排序结果顺便检查递归会不会栈溢出。5.2 笔试编程题的拆解思路从题意到测试用例编程题是笔试的压轴题我的做题套路固定分三步读题确认输入输出边界设计核心数据结构和算法手写测试用例自测。不要一上来就写代码先花几分钟把题目彻底吃透想清楚“输入为空怎么办”“只有一个元素怎么办”“数值很大怎么处理”这些边界条件这些才是判分的关键。举个例子有赞这类公司笔试里可能出现的题目设计一个订单折扣计算器支持普通客户打95折VIP客户打9折企业客户满1000减200。这种题表面上考业务逻辑实际上考的是面向对象设计能力。如果只用if else硬写代码是能跑但扩展性为零如果按策略模式设计DiscountStrategy接口把不同等级的客户折扣逻辑隔离成独立类那后续加“大客户满10000减2000”之类的规则就是加一个类的事。这种区分度正是笔试想要的结果。写完代码后一定要有测试闭环意识。给计算器设计测试用例时要覆盖普通客户未达门槛、VIP客户达到门槛、企业客户超过门槛这几条典型分支还要考虑折扣后金额保留几位小数、是否四舍五入这类参数细节。校招笔试对这个环节的容忍度很低你代码写得再快没有测试用例验证正确性照样拿不到满分。6. 高频失分点与备考建议从刷题到实战6.1 高频失分点与考场避坑清单结合我平时刷题和复盘别人踩坑的经验整理一份高频失分点清单建议考前逐条对照自查。失分点典型场景解决建议字符串比较用比较两个String内容是否相等时直接写只有比较对象引用时才用内容比较一律用equalsHashMap遍历时一边遍历一边修改for-each遍历HashMap时调用put或remove用迭代器的remove方法或先收集再统一修改异常被空catch吞掉catch里什么都不做或者只打System.out记录日志或抛给上层保留异常链线程安全问题被忽略多个线程并发修改同一个ArrayList或HashMap要用线程安全集合或加锁明确并发的读写场景边界条件未处理数组长度为0、链表只有一个节点、递归到base case不对写代码前先列边界条件写完测试用例补全编译版本不匹配“源发行版17需要目标发行版17”这类报错先确认项目JDK版本检查构建工具的source和target配置吻合Lombok相关依赖异常编译时报Lombok注解处理器不生效确认annotationProcessor路径配置正确升级到匹配JDK版本编码问题导致运行乱码控制台输出、文件读取中文乱码统一项目文件编码为UTF-8配置JVM参数-Dfile.encodingutf-8这里我想单独说一下环境问题。很多人平时练习都是用IDE写完直接跑对编译打包、JVM参数、编码设置这些环境实验接触得少结果笔试环境一简朴就手忙脚乱。你平时打开命令行编译运行Java程序的操作一定练熟包括javac编译、java执行、通过javac -encoding指定编码、通过java -Xmx设置堆大小。这些基本功在笔试现场能救你命。6.2 冲刺备考路线把考点串成知识网如果你准备的时间有限我建议按下面这个优先级来复习顺序也可以理解为性价比从高到低。第一优先级是Java基础语法和集合框架。字符串、异常、面向对象、HashMap、ArrayList这些是抄底必考内容而且难度不高复习两天就能见效。第二优先级是并发编程和JVM这两块是区分度最高的模块理解ThreadLocal、线程池、内存区域这些核心概念需要的时间长但短时间记住结论也能应付选择题。第三是Spring Boot核心内容重点背注解和AOP动态代理的基本概念。第四是算法建议每天手动写两个排序算法加一道简单编程题保持手感。我不建议你刷那种“面试题大全”式的资料题目太多太杂反而把核心知识点稀释了。更有效的方式是找到一套经典题集按模块完整过一遍。每做错一道题就去查它对应的源码或官方文档把背后的原理弄清楚。这样做完一轮你收获的是一张知识网络而不是几百个孤立答案。还有个小建议把每个考点都尝试用自己的话讲出来。人有个特点以为懂了但讲不出来说明其实没懂。你找一个朋友或者自己录音把HashMap的put流程讲一遍、把ThreadLocal为什么内存泄漏讲一遍讲不顺的地方就是需要加深理解的地方。这个方法我亲测有效比机械刷题高效得多。写在最后的一点体会我准备校招的时候最大的教训就是太迷信面试八股文以为把网上的题目刷完就能稳过。后来被面试官从HashMap一路追问到红黑树、再追问到CAS操作原理时才意识到笔试只是第一关真正拉开差距的是你有没有把知识点理解到一个“能给别人讲清楚”的程度。这套有赞A卷里所有考点本质上都在逼你把Java基础重新过一遍。如果你正在准备校招不妨把文章里的每个模块当成一个自检清单逐个击破而不是急着背答案。能用代码讲清楚一个概念比记住一百个定义都管用。
返回列表