简介:一份聚焦2020年兴业数金Java在线笔试的文档型复习资料,面向应聘兴业数金、兴业银行技术岗位的Java开发候选人,可帮助快速梳理笔试常见考点。整包仅1个docx文件,体积约14KB,内容紧凑,便于随时查阅与打印。文档按知识模块展开,覆盖Java方法重载、JDK辅助工具(javadoc、jar、jconsole等)、计算机存储系统分层理解、多线程同步与互斥的实现方式(原子操作、临界区、信号量、互斥量)、MySQL中InnoDB引擎特性,以及Delete与Truncate的差别、堆的概念等高频问题,并附有编程题“输入正整数计算距离最近的对称数”的解题思路。目前已有8734人学习浏览,适合准备Java方向在线笔试、需要集中过一遍基础概念的求职者使用。
1. 一份 Java 笔试题,考的不是“会背”,而是“会推导”
有人把 Java 笔试当成面经复读机,觉得背熟 HashMap、JVM、Spring 就能过关,但真正打开一份 “2020 某金融科技公司 Java 笔试题.docx” 动笔时才发现,那些“背过”的答案经不起推敲。“HashMap 是数组加链表”谁都能说出来,可一旦把 key 为 null 时哈希怎么算、负载因子为什么默认 0.75、链表转红黑树的临界条件连在一起问,很多人当场断片。
这份 docx 典型的地方在于没有偏题怪题,全是日常开发里高频使用的集合、并发、Spring 生命周期、MySQL 索引和手写代码,考的是能否把零散知识点串成完整推理链。它适合两类人:准备校招或跳槽 Java 开发岗的候选人,以及想系统自测一遍基础知识是否扎实的从业者。接下来按“考点拆解 → 解题套路 → 避坑 → 复习节奏”展开,把这类笔试题背后的考点变成可执行的准备路径,不是猜题,而是建立一套应对 Java 笔试的通用打法。
2. 把考点拆开来看:九个模块里哪些是必考区
2.1 HashMap 与 ConcurrentHashMap:集合考点里最常翻车的角落
HashMap 在笔试题里几乎必出,而且不会单独问“数据结构”,而是把初始化、扩容、哈希计算、树化条件串起来考。我一般建议按下面这段流程来组织答案:
// 以 JDK 1.8 为例,put 一个键值对的过程 Object put(K key, V value) { if (table == null) table = resize(); // 1. 首次插入,先扩容初始化 int hash = spread(key.hashCode()); // 2. 对 hashCode 做扰动 int i = (n - 1) & hash; // 3. 与运算代替取模 Node first = tab[i]; if (first == null) tab[i] = newNode(key, value); // 4. 桶空,直接放 else if (first.hash == hash && key.equals(first.key)) oldValue = first.value; // 5. 命中已有 key else if (first instanceof TreeNode) // 6. 红黑树走树插入 insertNode((TreeNode) first, key, value); else { // 7. 链表尾插,并判断树化 // binCount >= 7 时调用 treeifyBin(tab, hash) } }这里有几个笔试高频追问点。第一,JDK 1.8 之后链表插入从“头插”变成“尾插”,要能答出原因是避免并发扩容时链表成环。第二,负载因子默认 0.75 不是拍脑袋定的,它是对空间浪费和查询耗时权衡后的结果,如果只想背数字,被追问“为什么是 0.75”时容易露馅。第三,树化阈值是 8,但树化之前还有一个条件:数组长度不小于 64,如果数组还太小,会优先扩容而不是直接转红黑树,这是最容易丢分的一句话。
ConcurrentHashMap 的考点同样集中在两个方向:锁粒度和读操作。JDK 1.8 取消了 Segment 分段锁,改成对桶头节点加 synchronized,锁粒度从“多段”缩小到“单桶”;读操作依赖 volatile 修饰 Node 的 val 和 next,扩容时通过 ForwardingNode 让读写线程协作。答题主线建议按“锁范围变小 → 读写并发度提升 → size() 不需要全局锁”来展开,最后补一句“弱一致性的迭代器”,这句话是加分项。
2.2 JVM 内存区域与 GC 判活:按九宫格记才不会串台
JVM 的题最常见问法是:new 出来的对象放堆,局部变量放栈,那静态变量、常量池、Class 对象放在哪?这种题容易答混,先记住下面这张表:
| 内存区域 | 存放内容 | 典型异常 |
|---|---|---|
| 堆 | 对象实例、数组 | OutOfMemoryError: Java heap space |
| 虚拟机栈 | 栈帧、局部变量表、操作数栈 | StackOverflowError |
| 方法区 / 元空间 | 类元信息、静态变量、运行时常量池 | OutOfMemoryError: Metaspace |
| 本地方法栈 | native 方法调用栈 | StackOverflowError |
| 程序计数器 | 当前线程执行的字节码行号 | 无 |
答题时不要只背“存什么”,还要补上“线程私有还是线程共享”的维度:堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程私有的。这道题在 docx 里常常以选择题出现,干扰项就设置在“静态变量放在堆还是方法区”。注意,JDK 7 之后字符串常量池被挪到了堆,JDK 8 之后永久代被元空间替代,这三个时间点能说清楚,基本可以秒杀这类题。
GC 的题目一般不会问回收算法实现,而是问“哪些对象算垃圾”“GC Roots 包括什么”。红线答法是:通过可达性分析,从 GC Roots 出发不可达的对象判定为可回收;GC Roots 包括栈帧中的局部变量、静态变量、常量池引用、JNI 引用。如果题目问“System.gc() 调用后对象一定被回收吗”,答案是否定的,它只是建议触发 GC,对象真正回收还要看是否重写了 finalize 方法,以及 finalize 是否被执行过。
2.3 Spring 与 Spring Boot:最容易被轻视的配置区
准备 Java 笔试题的人常把重心放在集合和 JVM 上,Spring 相关的题反而容易翻车。笔试题里 Spring 考的通常不是 IOC、AOP 的定义,而是 Bean 生命周期、依赖注入方式、以及 Spring Boot 自动配置的触发条件。Bean 生命周期建议按七个节点记:实例化 → 属性填充 → Aware 回调 → BeanPostProcessor 前置处理 → 初始化方法(@PostConstruct、InitializingBean、自定义 init-method)→ BeanPostProcessor 后置处理 → 销毁。其中 @PostConstruct 和 InitializingBean 的执行顺序、BeanPostProcessor 的前后位置都是选择题常客。
配置类题目会给一段残缺的配置文件让你挑错,比如下面这个片段:
# Spring Boot 数据源与连接池配置 spring: datasource: url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 timeout: 3s lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0这类题有三个高频坑:一是密码如果是纯数字,YAML 会尝试解析成数字,所以加引号是良好习惯;二是 driver-class-name 从旧版的 com.mysql.jdbc.Driver 改成了 com.mysql.cj.jdbc.Driver,版本不同容易写错;三是 serverTimezone 在旧版驱动是必填,新版驱动不填也能跑,但时区不对会导致日期读写偏移。答配置题时,不要只盯着某一个参数对不对,要按“连接四要素 → 连接池参数 → 时区与编码 → 日志配置”的顺序逐行排查。
提示:考试时如果题目没指定 MySQL 版本,默认讨论 InnoDB 引擎与 MySQL 5.7+,并在答案开头注明假设版本,避免后续概念被阅卷人认定混淆。
3. 高频题型的解题套路:先把“会写”变成“会答”
3.1 String、Integer、枚举的“背题重灾区”
只要考 Java 基础,String 和 Integer 的题几乎年年出现。下面是笔试题里出现频率最高的一段代码:
public class StringTrap { public static void main(String[] args) { String a = "hello"; String b = new String("hello"); System.out.println(a == b); // false,一个在常量池,一个在堆 System.out.println(a.equals(b)); // true,比的是内容 Integer x = 127; Integer y = 127; Integer m = 128; Integer n = 128; System.out.println(x == y); // true,IntegerCache 缓存 [-128, 127] System.out.println(m == n); // false,超出缓存范围各自 new } }String 的答题要点是区分“字符串常量池里的对象”和“堆上 new 出来的对象”,== 比较的是引用,equals 比较的是内容。还要能解释 intern() 方法:如果常量池中有相同内容的字符串,直接返回池中引用,否则先将字符串放入池中再返回引用。Integer 的题记住自动装箱会走 IntegerCache,默认缓存范围 -128 到 127,超出范围后每次装箱都会 new 新对象,因此数值比较必须用 equals。
这类题是送分题,但错误率极高。如果是线上答题系统,不允许本地跑代码,建议先在草稿纸上写出对象的创建位置,再选答案。准备时可以自己敲一遍这段代码,把输出结果记牢,同时把 LongCache、CharacterCache 的存在也扫一眼,很多选择题会把 IntegerCache 的范围换成 Character 或 Long 来设陷阱。
3.2 并发编程:synchronized 与 volatile 的边界条件
并发题不是背定义就能得分,而是看你能不能快速判断“一段代码是不是线程安全的”。我习惯用三条线索去拆:共享资源是否需要原子更新?线程间是否需要可见性通信?能否容忍阻塞与上下文切换开销?
看这个经典例子:
public class Counter { private volatile int count = 0; public void increment() { count++; // 读-改-写三步,volatile 不保证原子性 } }count++ 在字节码层面是 getstatic、add、putstatic 三步,多线程同时执行时会发生丢失更新。volatile 只能保证修改后的值能立即被其他线程看到,不能把三步合成一步。正确做法是把 count 换成 AtomicInteger,调用 incrementAndGet();或者用 synchronized 修饰方法。笔试题很喜欢把这三个选项放在一起,A 说 volatile 能保证原子性,B 说 AtomicInteger 能保证原子性,C 说 synchronized 不能保证原子性,正确答案只有 B。顺带记住 Atomic 类的底层用到了 CAS,而 CAS 在竞争激烈时会自旋,自旋会占用 CPU,这是延伸问答的得分点。
答并发题时还要注意“synchronized 是悲观锁、CAS 是乐观锁”这句总结,以及“Lock 接口比 synchronized 多出可中断、可超时、可多条件等待”的能力。笔试题不要求写出 AQS 源码,但至少要知道 AQS 的核心是一个 volatile 的 state 加一个双向等待队列。
3.3 手写排序:快排在笔试中的写法与取舍
docx 里的手写算法题一般不会出偏题,快排和归并是最常见的。我建议背一套稳定、带注释的快排模板,既能写代码,又能撑起复杂度分析:
void quickSort(int[] arr, int L, int R) { if (L >= R) return; // 递归中止条件 int i = L, j = R; int pivot = arr[(L + R) >>> 1]; // 取中间值做基准,避免有序数组退化 while (i <= j) { while (arr[i] < pivot) i++; // 左侧找比基准大的 while (arr[j] > pivot) j--; // 右侧找比基准小的 if (i <= j) { swap(arr, i, j); // 交换并收拢指针 i++; j--; } } quickSort(arr, L, j); // 递归处理左区间 quickSort(arr, i, R); // 递归处理右区间 }这里有两个容易被追问的点。第一,基准取中间位置而不是取 arr[L],是为了规避数组本身有序时每次都选到极值,导致递归深度退化成 O(n),栈溢出也不是不可能。第二,交换完指针要 i++、j--,并且外层判断 i <= j,否则会出现死循环。手写题写完后,一定要顺手写出复杂度:平均 O(n log n),最坏 O(n^2),空间 O(log n),属于不稳定排序。不要觉得这行字多余,阅卷时这行字就是“思路分”。
如果题目要求写“稳定排序”,优先写归并而不是快排,并且要说明稳定性的定义是“相同值元素的相对顺序不改变”。这个区分本身就值一分。
3.4 MySQL 索引与隔离级别:两张表解决大部分问题
数据访问类题目主要考两件事:索引会不会失效、事务隔离级别怎么选。索引失效是一个高频坑,失效场景包括:对索引列做函数运算、隐式类型转换、like 前缀模糊匹配、联合索引不满足最左前缀、or 条件中包含非索引列。答题时要给出判断理由,不要只写“会失效”三个字。
事务隔离级别要能默写这张表:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能(InnoDB 用间隙锁规避) |
| 串行化 | 不可能 | 不可能 | 不可能 |
MySQL 默认的隔离级别是可重复读,但要补充一句:InnoDB 通过 next-key lock(记录锁加间隙锁)在可重复读级别下基本解决了幻读问题,这也是为什么很多生产环境敢直接用默认级别。笔试题如果给出“脏读、不可重复读、幻读”的定义让你对号入座,记住一个生活化例子:脏读是读到别人没提交的修改,不可重复读是同一行两次读不一样,幻读是两次查同一范围但行数变了。
注意:答 SQL 优化题时,不要一上来就提分库分表。先排查慢 SQL 是否走了索引、是否回表过多、能否用覆盖索引消除回表,这三个方向才是面试官想听到的第一层答案。
4. 从笔试延伸到面试:三道开放设计题的答题主线
4.1 秒杀系统设计:别被“限流”两个字带跑
开放设计题一般长这样:“请设计一个秒杀系统”。很多人的答案只有“用 Redis 限流”,这是最大的误区。设计题打分看的是模块划分是否完整、每个环节的失败如何处理。我一般按四个层展开:接入层限流 → 业务校验层扣减预库存 → MQ 异步下单 → 数据库最终扣减。每一层回答时都补一句“为什么放这层”,而不是只堆名词。
| 模块 | 职责 | 关键点 |
|---|---|---|
| 接入层 | 限流、防重 | 令牌桶限流,同一用户请求去重 |
| 业务校验 | 库存预检查 | 读 Redis 库存,不直接读库 |
| 异步下单 | 削峰、解耦 | 收到请求后立刻返回“排队中” |
| 数据层 | 最终扣减 | 数据库事务校验库存并扣减 |
限流参数怎么定?令牌桶容量和恢复速率要按系统压测结果和预估并发来算,一般先把桶容量设置成平时峰值的 3 倍,恢复速率根据下游数据库能承受的事务 TPS 来折算,宁可抢不到,也不能让数据库被打挂。库存预扣减这一步,如果用 Redis 的 decrement 命令,要配合 Lua 脚本保证“查库存、扣减、记录用户”三步的原子性:
-- Redis Lua 脚本:判断库存并预扣 local stock = redis.call('get', KEYS[1]) if tonumber(stock) <= 0 then return 0 end redis.call('decr', KEYS[1]) redis.call('sadd', KEYS[2], ARGV[1]) return 1需要解释清楚:为什么要用 Lua?因为 EVAL 执行期间 Redis 是单线程的,脚本内多个命令不会被打断,所以预扣不会超卖。脚本返回 0 时业务端要立刻释放用户排队状态,返回 1 时才允许发送下单消息。这样回答,既展示了方案,也展示了原子性的实现深度。
4.2 消息队列与分布式事务:先讲一致性模型再讲实现
如果开放题问“怎么保证消息不丢失”,不要直接回答“把 MQ 集群部署高可用”。标准三层答案:生产端用 confirm 机制,发送后确认 ACK;MQ 端开启持久化,刷盘机制保证宕机不丢;消费端手动 ack,处理成功后才提交 offset。每一层都可以扩展一个问题:生产端发送失败怎么办?重试;消费端重复消费怎么办?业务幂等表。
分布式事务的答题切入点是“三种模型的取舍”,而不是记忆某种中间件命令。第一种是最终一致性,业务通过本地记录补偿来对账;第二种是可靠消息一致性,本地消息表和 MQ 配合,先写业务表再写消息表,投递成功后才删除消息;第三种是事务消息,通过半消息和回查机制实现。笔试题里如果允许你画流程,建议画出“业务表、消息表、MQ 三者的先后顺序”,并能说清楚“为什么先写业务表后投递”。
提示:遇到这类题,宁可写“业务补偿”也不写“强一致分布式事务”。分布式场景下强一致带来的性能问题远比想象中严重,阅卷人期待的是候选人对一致性级别有务实的判断。
4.3 线上 CPU 飙高排查:把 JVM 命令用到笔试题里
开放题里还有一类“线上故障排查”,给定现象“CPU 使用率飙到 200%,接口变慢”,让你写排查思路。这种题虽然没有真实现场,但答题要按“命令 + 定位 + 解决”三段来写:
# 1. 找到 CPU 占用最高的进程 top # 2. 查看进程内哪个线程占用 CPU 高 top -H -p <pid> # 3. 把线程 ID 转为十六进制 printf '%x\n' <threadId> # 4. 导出线程栈并定位问题代码 jstack <pid> | grep -A 30 '<hex>'定位到线程栈后,常见的凶手有四类:业务代码死循环、频繁 Full GC、锁竞争导致线程阻塞、线程池配置过大导致创建大量线程。这四类问题在 jstack 输出里的特征各不相同:死循环能看到业务类名一直处于 RUNNABLE;Full GC 能看到大量线程处于 Blocked 状态等待 GC 完成,配合 jstat -gcutil 查看 GC 次数和耗时就能确认;锁竞争能看到线程一直停留在 synchronized 的阻塞点。
这一节要体现的不是“会用某个命令”,而是“有排查顺序”。先看系统层 top,再看进程内线程 top -H,再用 jstack 定位栈,最后转为代码层分析。把顺序写对,分数就拿到了一半。若还有余力,补一句“jmap -dump 导出堆转储后用 MAT 分析对象引用”用于内存泄漏场景,这个补充能让答案更完整。
5. Java 笔试避坑手册:翻车现场与血泪经验
5.1 “背过的答案”经不起推导:HashMap 死循环的版本差异
现象:候选人回答“HashMap 是线程不安全的,并发会导致死循环”,被追问“JDK 1.8 还死循环吗”之后直接卡住,只能反复重复第一句话。
原因:很多人背的是网上老版本面经。死循环主要发生在 JDK 1.7 扩容时的头插法,并发扩容会让两个线程同时操作链表引用,最终形成循环链表;JDK 1.8 改成尾插后,链表成环问题大幅缓解,但并发下仍然存在数据丢失问题,并不是变成线程安全了。
解决:回答时按三层结构组织——先说 HashMap 不是线程安全;再说 JDK 1.7 头插法扩容在并发下会成环;最后强调 JDK 1.8 尾插法消除了成环风险,但并发写仍会丢数据,所以并发场景必须用 ConcurrentHashMap。这样既答了历史问题,又没把版本记混。
5.2 SQL 题卡了 20 分钟:先写过滤条件再管排序
现象:笔试题里一道“查每个分组内最新一条记录”的 SQL,用关联子查询来回试了 20 分钟没写出来,后面的算法题时间严重不足。
原因:拿到 SQL 题没有先拆需求,直接上手写 JOIN,把“分组、排序、取值”揉在一起,越试越乱。其实这类题考的是窗口函数的基本用法,不是复杂关联。
解决:建议先理清三件事:分组键是什么、需要按哪一列排序、要输出的字段有哪些。对应到 MySQL 8.0 就是 ROW_NUMBER() OVER (PARTITION BY 分组列 ORDER BY 排序列 DESC),取 rn=1。如果题目环境不支持窗口函数,再退到关联子查询或临时变量方案,把子查询的关联条件写清楚。
5.3 手写代码没注释:阅卷人看不到你的思路
现象:手写排序题用了归并排序,代码逻辑没问题,但整段代码没有一行注释,最终得分比预期低。
原因:docx 笔试大多是人工阅卷,阅卷人需要在短时间内判断候选人代码是否可靠。没有注释的代码,只能靠猜测判断意图;一旦关键字拼写有误或边界条件写错,连“思路对”都看不出来,分数直接被压。
解决:每一段关键逻辑都写注释,代码块正文之前用一两句话写明思路,比如“采用快排,取中间值为基准,递归分区”。我在每次笔试时都会在代码开头写一行注释说明算法名称和复杂度,这行注释就是给阅卷人看的解题过程。
5.4 时间分配失误:拿到题目先全卷浏览,不要从第一题死磕
现象:候选人习惯从第一题按顺序做,结果前两道概念题花了半小时,最后分值最高的手写算法只剩 10 分钟,草草写几行,思路不完整。
原因:没有估算分值分布。笔试题的开头通常是概念题,中间是 SQL 或设计题,末尾才是算法题,但每个人的擅长模块不一样,按顺序做题天然吃亏。
解决:拿到 docx 后先花 2 分钟浏览全部题目,用“会做、可能想一下、完全不会”给每道题打标记。先做完全有把握的题,再做需要想一想的题,最后攻坚算法和开放设计题。我一般是按“基础题 30% 时间 → 中等题 50% 时间 → 压轴题 20% 时间”来分配,最后无论如何都要留出 10 分钟检查。
6. 最后 30 分钟怎么用:这是我的笔试收尾习惯
6.1 留 10 分钟做“读题干、追选项、补注释”三步收尾
检查不是把答案从头读一遍,而是只盯三类关键点:数字题是否回答了结论,比如“负载因子默认是多少”先说答案再解释;代码题是否每段关键逻辑都有注释;SQL 题是否写了分组和排序条件。这时候第一反应往往是最准的,不要因为犹豫把对的答案改成错的,除非你确认原答案是错的。
6.2 用“错题回放”代替盲目刷题
笔试结束后,趁记忆还热,把不能确定的题记成错题记录,包含五项:题干、我的答案、正确答案、错误原因、对应知识点。我一般每周回放一次,只重做错误率高的模块。准备一次 Java 笔试,与其刷大量面经题,不如把典型题拆成“概念题、代码题、设计题”三类各练透二十道,再针对薄弱模块定点补。
这两年我带过的人里,栽跟头最狠的往往不是知识面不够,而是把力气花在背题上。等到笔试现场才发现,题目问的是“为什么”而不是“是什么”。Java 笔试不像想象中那么难,但也绝不是背背就能过的;如果你能把 HashMap 尾插出现的原因讲清楚,能把 JVM 内存区域画成一张九宫格,能顺着“进程 → 线程 → 堆栈”的链路定位线上问题,那这份 docx 对你来说就是一次查漏补缺的信号。希望这些踩坑经验能帮到你,祝你笔试顺利。
本文还有配套的精品资源,点击获取