异常体系、IO 与 NIO 专属复习笔记

目录

[模块一:异常体系(项目架构核心 + 面试高频场景题)](#模块一:异常体系(项目架构核心 + 面试高频场景题))

[1. 异常体系整体架构与分类](#1. 异常体系整体架构与分类)

核心定义

个人理解

项目实际使用场景

面试考点标注

[2. try-catch-finally 执行规则(含 return 场景)](#2. try-catch-finally 执行规则(含 return 场景))

核心定义

个人理解

项目实际使用场景

面试考点标注

[3. try-with-resources 语法与底层原理](#3. try-with-resources 语法与底层原理)

核心定义

个人理解

项目实际使用场景

面试考点标注

[4. 自定义异常与全局异常处理最佳实践](#4. 自定义异常与全局异常处理最佳实践)

核心定义

个人理解

项目实际使用场景

面试考点标注

[模块二:IO 流体系(文件操作核心)](#模块二:IO 流体系(文件操作核心))

[1. IO 流整体分类与核心区别](#1. IO 流整体分类与核心区别)

核心定义

个人理解

项目实际使用场景

面试考点标注

[2. 核心处理流:缓冲流、转换流、序列化流](#2. 核心处理流:缓冲流、转换流、序列化流)

核心定义

个人理解

项目实际使用场景

面试考点标注

[3. Serializable 接口与 serialVersionUID](#3. Serializable 接口与 serialVersionUID)

核心定义

个人理解

项目实际使用场景

面试考点标注

[模块三:NIO 核心(高性能 IO + 面试进阶考点)](#模块三:NIO 核心(高性能 IO + 面试进阶考点))

[1. NIO 三大核心组件](#1. NIO 三大核心组件)

核心定义

个人理解

项目实际使用场景

面试考点标注

[2. BIO/NIO/AIO 核心对比与适用场景](#2. BIO/NIO/AIO 核心对比与适用场景)

核心定义

个人理解

项目实际使用场景

面试考点标注

[3. 零拷贝的概念与应用场景](#3. 零拷贝的概念与应用场景)

核心定义

个人理解

项目实际使用场景

面试考点标注

当日验收清单

[异常体系、IO 与 NIO 面试模拟题(实习面试专属,附标准答案)](#异常体系、IO 与 NIO 面试模拟题(实习面试专属,附标准答案))

[一、基础必考题(8 道,中小厂实习 100% 覆盖)](#一、基础必考题(8 道,中小厂实习 100% 覆盖))

[1. Error 和 Exception 的核心区别是什么?受检异常和非受检异常的区别是什么?](#1. Error 和 Exception 的核心区别是什么?受检异常和非受检异常的区别是什么?)

[2. finally 代码块是不是一定会执行?什么情况下不会执行?](#2. finally 代码块是不是一定会执行?什么情况下不会执行?)

[3. try-with-resources 的好处是什么?底层实现原理是什么?](#3. try-with-resources 的好处是什么?底层实现原理是什么?)

[4. 字节流和字符流的核心区别是什么?各自的适用场景是什么?](#4. 字节流和字符流的核心区别是什么?各自的适用场景是什么?)

[5. 缓冲流(BufferedXXX)为什么能大幅提升 IO 性能?](#5. 缓冲流(BufferedXXX)为什么能大幅提升 IO 性能?)

[6. serialVersionUID 的作用是什么?不手动指定会有什么问题?](#6. serialVersionUID 的作用是什么?不手动指定会有什么问题?)

[7. BIO、NIO、AIO 的核心区别和适用场景是什么?](#7. BIO、NIO、AIO 的核心区别和适用场景是什么?)

[8. 零拷贝的原理是什么?有什么核心优势?](#8. 零拷贝的原理是什么?有什么核心优势?)

[二、场景坑点题(6 道,大厂实习高频业务题 / 代码题)](#二、场景坑点题(6 道,大厂实习高频业务题 / 代码题))

[1. 以下代码的输出结果是什么?为什么?](#1. 以下代码的输出结果是什么?为什么?)

[2. 你的校园二手平台要实现商品图片上传功能,应该用字节流还是字符流?为什么?用什么优化手段?](#2. 你的校园二手平台要实现商品图片上传功能,应该用字节流还是字符流?为什么?用什么优化手段?)

[3. 你的校园二手平台怎么设计异常体系?全局异常处理器怎么实现?](#3. 你的校园二手平台怎么设计异常体系?全局异常处理器怎么实现?)

[4. 你的校园二手平台把商品对象存入 Redis 缓存,修改了 Goods 类加了一个字段后,反序列化失败,是什么原因?怎么解决?](#4. 你的校园二手平台把商品对象存入 Redis 缓存,修改了 Goods 类加了一个字段后,反序列化失败,是什么原因?怎么解决?)

[5. 以下代码读取商品的 TXT 描述文件,输出中文乱码,是什么原因?怎么解决?](#5. 以下代码读取商品的 TXT 描述文件,输出中文乱码,是什么原因?怎么解决?)

[6. 你的校园二手平台要实现商品大视频的下载功能,怎么优化下载性能?](#6. 你的校园二手平台要实现商品大视频的下载功能,怎么优化下载性能?)

[三、进阶原理题(4 道,中大厂实习拔高题)](#三、进阶原理题(4 道,中大厂实习拔高题))

[1. 为什么现代 Java 框架(Spring、MyBatis)都把受检异常转为运行时异常?受检异常有什么设计弊端?](#1. 为什么现代 Java 框架(Spring、MyBatis)都把受检异常转为运行时异常?受检异常有什么设计弊端?)

[2. NIO 的多路复用原理是什么?为什么单线程就能管理成千上万的连接?](#2. NIO 的多路复用原理是什么?为什么单线程就能管理成千上万的连接?)

[3. 为什么 NIO 的性能远高于 BIO?](#3. 为什么 NIO 的性能远高于 BIO?)

[4. Java 序列化是浅拷贝还是深拷贝?怎么实现深拷贝?](#4. Java 序列化是浅拷贝还是深拷贝?怎么实现深拷贝?)

实习面试答题加分技巧(绑定你的校园二手平台项目)


模块一:异常体系(项目架构核心 + 面试高频场景题)

1. 异常体系整体架构与分类

核心定义

Java 异常的顶层父类是Throwable,分为两大分支:

(1)核心层级划分
顶层类 分类 定义与特点 代表示例
Throwable Error 程序无法处理的严重错误,JVM 层面的问题,开发中无需捕获 OutOfMemoryError(OOM)、StackOverflowErrorNoClassDefFoundError
Exception 程序可以捕获处理的异常,分为两类:
① 非受检异常(运行时异常RuntimeException 编译期不强制捕获,运行时抛出,绝大多数是代码逻辑问题 NullPointerExceptionIndexOutOfBoundsExceptionIllegalArgumentException
② 受检异常(Checked Exception) 编译期强制捕获 / 抛出,否则编译不通过 IOExceptionSQLExceptionInterruptedException
(2)核心设计原则

异常是用来处理程序运行时的意外情况,不是用来控制业务流程的;业务异常统一继承运行时异常,避免强制捕获的代码冗余。

个人理解

Error 是致命错误,程序无法恢复,只能提前预防;受检异常是 Java 的历史设计,强制开发者处理可能的异常,但会造成大量的 try-catch 冗余,现代框架(Spring)都统一转为运行时异常处理;业务开发中 99% 的场景都是自定义运行时异常 + 全局统一捕获。

项目实际使用场景

结合校园二手平台开发实践:

  1. JVM 错误预防:项目启动时配置 JVM 堆内存参数,避免商品图片上传、大订单导出时出现 OOM;递归处理商品分类树时控制递归深度,避免栈溢出;
  2. 运行时异常处理:空指针、数组越界等代码异常,由全局异常处理器统一捕获,返回统一的错误码和提示;
  3. 受检异常转换 :文件操作的IOException、数据库的SQLException,在 Service 层捕获后转为自定义业务异常,抛给全局异常处理器,避免代码里大量 try-catch。
面试考点标注

✅ 必问:Error 和 Exception 的核心区别;

✅ 必问:受检异常和非受检异常的区别,受检异常的设计弊端;

✅ 场景题:常见的运行时异常有哪些,怎么避免空指针异常。

不用 try-catch 也能编译,运行时才报错,面试高频:

  • **NullPointerException(空指针 NPE)**调用 null 对象的方法 / 属性

  • **IndexOutOfBoundsException(下标越界)**数组 / 集合访问了不存在的下标

  • **IllegalArgumentException(非法参数)**传了错误参数

  • **IllegalStateException(状态异常)**方法调用时机不对

  • **ClassCastException(类型转换异常)**强制类型转换失败

  • **ArithmeticException(算术异常)**除 0 等

  • **NoSuchElementException(没有元素)**迭代器、集合取空元素

  • **UnsupportedOperationException(不支持操作)**调用了不支持的方法

空指针异常是最常见的运行时异常,避免它的核心:调用前判空、常量放前面、返回空集合不返回 null、善用 Optional 和工具类。


2. try-catch-finally 执行规则(含 return 场景)

核心定义

核心执行规则:

  1. finally代码块一定会执行 ,唯一不执行的情况:调用System.exit(0)终止 JVM、JVM 崩溃、线程死亡;
  2. 执行顺序:try → 异常则 catch → finally;无异常则 try → finally;
  3. 带 return 的核心规则
    • try/catch 中的 return 会先保存返回值的快照,再执行 finally,最后返回快照值;
    • finally 中修改基本类型的返回值无效,修改引用类型的内容有效;
    • finally 中写 return 会覆盖 try/catch 的返回值,开发中绝对禁止。
个人理解

finally 的设计初衷是释放资源(关流、释放锁),不是用来处理业务逻辑;开发中不要在 finally 里写业务代码,更不要写 return,否则会出现逻辑混乱。

项目实际使用场景
  1. 旧代码资源释放:早期 JDK7 之前的文件流操作,在 finally 里手动关闭流,避免流泄漏;
  2. 锁释放:分布式锁、本地锁的释放,必须放在 finally 里,保证异常情况下也能释放锁,避免死锁;
  3. 避坑实践:绝对不在 finally 里写 return,之前踩过坑,finally 的 return 覆盖了业务异常,导致异常无法抛出。
面试考点标注

✅ 必问:finally 是不是一定会执行?什么情况下不执行?

✅ 高频场景题:给出带 return 的 try-catch-finally 代码,问输出结果;

✅ 坑点:finally 中修改返回值的生效场景。


3. try-with-resources 语法与底层原理

核心定义

Java7 引入的语法糖,用来自动释放资源,核心规则:

  1. 所有实现了AutoCloseable接口的资源(流、连接、锁等),都可以放在 try 后的括号里;
  2. 代码执行完后,自动调用资源的close()方法,无需手动写 finally 关流;
  3. 底层原理:编译期自动生成 finally 块,按资源声明的逆序调用 close () 方法,比手动写 finally 更安全,不会漏关流。
个人理解

try-with-resources 解决了手动 finally 关流的两大问题:一是嵌套流的代码冗余(地狱式缩进),二是手动关流容易漏关、顺序错误导致资源泄漏,是 IO 操作的最佳实践。

项目实际使用场景
  1. 商品图片上传下载:文件输入输出流、缓冲流全部用 try-with-resources 管理,自动关流,代码简洁,不会出现流泄漏导致的文件占用问题;
  2. 数据库连接、Redis 连接:连接资源用 try-with-resources 管理,自动释放连接,避免连接池耗尽;
  3. 订单 Excel 导出:POI 的工作簿、输出流,用 try-with-resources 管理,避免大文件导出时的内存泄漏。
面试考点标注

✅ 必问:try-with-resources 的好处,底层实现原理;

✅ 对比:和手动 finally 关流的区别,为什么更安全;

✅ 细节:资源关闭的顺序是声明的逆序,避免依赖问题。


4. 自定义异常与全局异常处理最佳实践

核心定义
(1)自定义异常设计规范
  • 业务异常统一继承RuntimeException,避免受检异常的强制捕获冗余;
  • 区分不同业务场景的异常:OrderExceptionUserExceptionPayExceptionFileException等;
  • 异常中携带错误码、错误信息,方便全局处理。
(2)全局异常处理

基于 Spring 的@RestControllerAdvice + @ExceptionHandler实现,统一捕获所有异常,返回统一格式的响应,避免每个 Controller 单独处理异常。

个人理解

自定义异常是业务分层的核心,把业务错误和系统错误区分开,全局异常处理统一收口,代码更整洁,异常处理更规范,是企业级项目的标准架构。

项目实际使用场景
  1. 自定义业务异常
    • 订单模块:OrderException,抛出「库存不足」「订单状态异常」「无权操作订单」等业务错误;
    • 用户模块:UserException,抛出「用户名已存在」「密码错误」「用户未登录」等错误;
    • 文件模块:FileException,抛出「文件过大」「格式不支持」「上传失败」等错误;
  2. 全局异常处理器
    • 统一捕获自定义业务异常,返回对应错误码和提示;
    • 捕获参数校验异常、空指针等系统异常,返回通用错误提示,打印错误日志;
    • 异常信息统一上报日志平台,方便排查问题。
面试考点标注

✅ 场景题:项目中怎么设计异常体系,自定义异常的好处;

✅ 必问:Spring 全局异常处理的实现方式;

✅ 设计题:怎么区分业务异常和系统异常,怎么处理异常日志。


模块二:IO 流体系(文件操作核心)

1. IO 流整体分类与核心区别

核心定义

IO 流是 Java 中数据传输的通道,按不同维度分类:

(1)按数据类型划分(核心区别)
分类 核心操作 适用场景 顶层抽象类 实现类示例
字节流 操作二进制字节数据,无编码问题 图片、视频、音频、Excel、压缩包等所有非文本文件 InputStream/OutputStream FileInputStreamFileOutputStream
字符流 操作文本字符数据,自带编码处理 纯文本文件、配置文件、字符串等文本内容 Reader/Writer FileReaderFileWriter
(2)按功能划分
  • 节点流:直接操作数据源的流(FileInputStream、FileReader);
  • 处理流(包装流):包装节点流,增加额外功能(缓冲流、转换流、序列化流)。
个人理解

字节流是万能流,可以操作所有类型的文件;字符流是专门处理文本的,解决了字节流读取中文乱码的问题;开发中优先用处理流(缓冲流),性能比节点流高几十倍。

项目实际使用场景
  1. 商品图片 / 视频上传下载:用字节流操作,避免编码问题导致的文件损坏;
  2. 订单 Excel 导出、商品批量导入:用字节流操作 Excel 文件;
  3. 配置文件读取、接口响应文本处理:用字符流操作,避免中文乱码;
  4. 性能优化 :所有 IO 操作都加缓冲流,减少磁盘 IO 次数,比如商品大图片上传用BufferedInputStream/BufferedOutputStream,性能提升 10 倍以上。
面试考点标注

✅ 必问:字节流和字符流的核心区别,各自的适用场景;

✅ 坑点:字节流读取中文会乱码,为什么?怎么解决?

✅ 细节:字符流的底层还是字节流,只是做了编码转换。


2. 核心处理流:缓冲流、转换流、序列化流

核心定义
(1)缓冲流(BufferedXXX)
  • 原理:内置 8KB 的缓冲区,先把数据写到缓冲区,缓冲区满了再一次性写到磁盘 / 读取到内存,减少磁盘 IO 次数,大幅提升性能;
  • 实现类:BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
(2)转换流(InputStreamReader/OutputStreamWriter)
  • 作用:字节流和字符流的桥梁,可以指定编码(UTF-8、GBK),解决不同编码的文本乱码问题;
  • 场景:读取 GBK 编码的老系统文件,转换为 UTF-8。
(3)序列化流(ObjectInputStream/ObjectOutputStream)
  • 作用:把 Java 对象转为字节序列(序列化),或者把字节序列转回 Java 对象(反序列化),用于对象的持久化、网络传输。
个人理解

缓冲流是 IO 性能优化的必选项,几乎所有 IO 操作都要加缓冲流;转换流是处理编码乱码的终极方案;序列化是对象持久化、分布式缓存的基础。

项目实际使用场景
  1. 缓冲流:商品图片上传、大文件导出,全部用缓冲流,避免频繁磁盘 IO,提升上传下载速度;
  2. 转换流:处理用户上传的 GBK 编码的商品批量导入 CSV 文件,转为 UTF-8,避免中文乱码;
  3. 序列化流:把用户对象、商品对象序列化后存入 Redis,实现分布式缓存;微服务之间的对象传输,底层也是序列化。
面试考点标注

✅ 必问:缓冲流为什么能提升 IO 性能?

✅ 场景题:怎么解决 IO 读取中文乱码问题?

✅ 细节:缓冲流的 flush () 方法,手动把缓冲区数据刷到磁盘。


3. Serializable 接口与 serialVersionUID

核心定义
  1. Serializable 接口:是一个标记接口,没有任何方法,作用是告诉 JVM 这个类可以被序列化;
  2. serialVersionUID :序列化的版本号,是序列化和反序列化的唯一标识:
    • 序列化时会把 serialVersionUID 写入字节序列;
    • 反序列化时会对比字节序列的 serialVersionUID 和类的 serialVersionUID,不一致会抛出InvalidClassException
    • 不手动指定的话,JVM 会根据类的属性、方法自动生成,修改类的结构会导致版本号变化,反序列化失败。
个人理解

serialVersionUID 是序列化的兼容性保障,手动指定版本号可以控制类修改后的反序列化兼容性;不指定的话,类加个字段就会导致反序列化失败,是开发中的高频坑。

项目实际使用场景
  1. Redis 缓存对象 :所有存入 Redis 的实体类(Goods、User、Order)都实现Serializable接口,手动指定serialVersionUID,避免类修改后 Redis 缓存反序列化失败;
  2. Session 序列化:用户 Session 对象序列化,实现分布式 Session 共享;
  3. 避坑实践 :所有需要序列化的类,都手动加private static final long serialVersionUID = 1L;,绝对不依赖 JVM 自动生成。
面试考点标注

✅ 必问:serialVersionUID 的作用是什么?不指定会有什么问题?

✅ 坑点:哪些字段不会被序列化?(static 静态字段、transient 修饰的字段)

✅ 细节:序列化是浅拷贝,对象的引用属性也需要实现 Serializable 接口。


模块三:NIO 核心(高性能 IO + 面试进阶考点)

1. NIO 三大核心组件

核心定义

NIO(New IO/Non-blocking IO)是 Java1.4 引入的非阻塞 IO,面向缓冲区,三大核心组件:

  1. Buffer(缓冲区) :数据的容器,底层是数组,所有数据都通过缓冲区读写,核心实现:ByteBufferCharBuffer等;核心模式:读模式、写模式,通过flip()切换。
  2. Channel(通道) :双向的数据传输通道,可以同时读写,对应 IO 的流,核心实现:FileChannel(文件通道)、SocketChannel(网络通道)。
  3. Selector(多路复用器):一个线程可以管理多个 Channel,监听多个 Channel 的事件(连接、读、写),实现单线程管理大量连接,是 NIO 高并发的核心。
个人理解

传统 IO 是面向流,单向,阻塞;NIO 是面向缓冲区,双向,非阻塞;核心优势是用少量线程管理大量连接,适合高并发网络场景,是 Tomcat、Netty 等容器 / 框架的底层核心。

项目实际使用场景
  1. 文件操作 :用 NIO 的Files工具类实现文件的快速拷贝、删除、读取,比传统 IO 性能更高;
  2. 大文件传输 :商品大图片、视频的下载,用FileChanneltransferTo()方法实现零拷贝,大幅提升传输速度;
  3. 底层框架:项目用的 Spring Boot 内嵌 Tomcat,底层就是 NIO 实现,支持高并发请求。
面试考点标注

✅ 必问:NIO 和传统 IO(BIO)的核心区别;

✅ 原理:NIO 三大组件的作用;

✅ 细节:Buffer 的 flip ()、clear () 方法的作用。


2. BIO/NIO/AIO 核心对比与适用场景

核心定义
类型 全称 核心特点 线程模型 性能 适用场景
BIO 阻塞 IO 每个连接一个线程,读写阻塞 一个连接对应一个线程 低,连接多了线程数爆炸 连接数少、固定的场景,传统老项目
NIO 非阻塞 IO 单线程管理多个连接,多路复用 一个线程管理 N 个连接 高,线程开销小 连接数多、高并发场景(互联网项目、网关、RPC)
AIO 异步 IO 完全非阻塞,操作系统完成 IO 后通知应用 无阻塞,事件回调 最高 连接数多、长连接场景,实际应用较少
个人理解

BIO 是同步阻塞,简单但性能差;NIO 是同步非阻塞,是目前的主流,Tomcat、Netty 都是基于 NIO;AIO 是异步非阻塞,理论上性能最高,但实际开发中用的很少,Windows 下实现成熟,Linux 下底层还是用 NIO 模拟。

项目实际使用场景
  1. 项目容器:Spring Boot Tomcat 用 NIO 模式,支持高并发接口请求;
  2. 文件操作:本地文件操作多用 NIO 的工具类,性能更高;
  3. 避坑实践:BIO 只适合小工具、测试代码,生产环境高并发场景绝对不用 BIO。
面试考点标注

✅ 必问:BIO、NIO、AIO 的核心区别、各自的适用场景;

✅ 原理:NIO 的多路复用是什么?

✅ 扩展:Netty 是基于 NIO 封装的,解决了 NIO 的原生缺陷。


3. 零拷贝的概念与应用场景

核心定义

传统 IO 的文件传输,需要经过 4 次拷贝:磁盘→内核缓冲区→用户缓冲区→Socket 缓冲区→网卡,还有 2 次用户态和内核态的切换,性能差;零拷贝是减少数据在用户态和内核态的拷贝次数 ,Java 中通过FileChannel.transferTo()实现,底层是 Linux 的sendfile系统调用,数据直接从内核缓冲区拷贝到 Socket 缓冲区,不需要经过用户态,只有 2 次拷贝,性能提升数倍。

个人理解

零拷贝是大文件传输、静态资源服务器的核心优化,本质是减少 CPU 的拷贝开销,解放 CPU,提升吞吐量,是大厂面试的高频考点。

项目实际使用场景
  1. 商品大文件下载:商品视频、高清图片的下载接口,用零拷贝实现,下载速度提升 3-5 倍,服务器 CPU 占用大幅降低;
  2. 静态资源服务:项目的图片、CSS、JS 等静态资源,用零拷贝传输,提升静态资源访问性能。
面试考点标注

✅ 必问:零拷贝的原理是什么?有什么优势?

✅ 场景题:大文件下载怎么优化?

✅ 细节:Java 中零拷贝的实现方式。


当日验收清单

  1. 不看资料口述:异常的完整分类、BIO 和 NIO 的核心差异、try-with-resources 的优势;
  2. 结合项目口述:你的项目中自定义了哪些业务异常,全局异常处理器怎么设计的,文件上传用了什么流;
  3. 避坑确认:serialVersionUID 不指定的坑、finally 中写 return 的坑、字节流读取中文乱码的问题。

异常体系、IO 与 NIO 面试模拟题(实习面试专属,附标准答案)


一、基础必考题(8 道,中小厂实习 100% 覆盖)

1. Error 和 Exception 的核心区别是什么?受检异常和非受检异常的区别是什么?

【标准答案】

(1)Error 和 Exception 的区别
对比维度 Error Exception
层级 是 Throwable 的子类 是 Throwable 的子类
定义 JVM 层面的严重错误,程序无法处理和恢复 程序运行时的意外情况,可以捕获和处理
示例 OutOfMemoryError、StackOverflowError NullPointerException、IOException、SQLException
处理方式 无需捕获,只能提前预防和优化 可以通过 try-catch 捕获处理
(2)受检异常和非受检异常(运行时异常)的区别
对比维度 受检异常(Checked Exception) 非受检异常(RuntimeException)
编译期检查 编译期强制捕获 / 抛出,否则编译不通过 编译期不强制检查,运行时抛出
继承关系 继承 Exception,不继承 RuntimeException 继承 RuntimeException
设计意义 强制开发者处理可预期的外部异常(IO、数据库) 代码逻辑错误,开发者应该提前避免
示例 IOException、SQLException、InterruptedException NullPointerException、IndexOutOfBoundsException

【考点对应】模块一:异常体系分类


2. finally 代码块是不是一定会执行?什么情况下不会执行?

【标准答案】finally 代码块绝大多数情况一定会执行,只有 3 种极端情况不会执行:

  1. 调用System.exit(0)/System.exit(1)主动终止 JVM;
  2. JVM 崩溃、进程被强制杀死(比如 kill -9);
  3. 执行 finally 的线程死亡(比如线程被 interrupt 终止)。

补充:return、抛出异常都不会阻止 finally 执行。

【考点对应】模块一:try-catch-finally 执行规则


3. try-with-resources 的好处是什么?底层实现原理是什么?

【标准答案】

核心好处
  1. 自动释放资源:无需手动写 finally 关流,避免资源泄漏(流、连接未关闭);
  2. 代码简洁:避免多层 finally 嵌套的地狱式缩进;
  3. 更安全:编译期自动生成关闭逻辑,不会出现漏关、顺序错误的问题。
底层原理

是 Java7 引入的语法糖,编译期自动做两件事:

  1. 给所有实现了AutoCloseable接口的资源,自动生成 finally 代码块;
  2. 按资源声明的逆序 调用close()方法,保证依赖的资源先关闭。

【考点对应】模块一:try-with-resources 语法


4. 字节流和字符流的核心区别是什么?各自的适用场景是什么?

【标准答案】

对比维度 字节流 字符流
操作单元 二进制字节(1 字节) Unicode 字符(2 字节)
编码处理 无编码,不会自动转换 自带编码转换,处理文本自动适配编码
乱码问题 读取中文会乱码 读取纯文本无乱码
性能 更高,无编码转换开销 略低,有编码转换开销
适用场景 所有二进制文件:图片、视频、音频、Excel、压缩包、可执行文件 纯文本文件:TXT、配置文件、代码、字符串

【考点对应】模块二:IO 流核心分类


5. 缓冲流(BufferedXXX)为什么能大幅提升 IO 性能?

【标准答案】核心原理是减少磁盘 IO 次数

  1. 缓冲流内置了默认 8KB 的缓冲区,读写数据时先写到缓冲区,缓冲区满了再一次性和磁盘交互;
  2. 传统节点流每次读写都要和磁盘交互,磁盘 IO 的速度比内存慢几个数量级;
  3. 缓冲流把大量小的磁盘 IO,合并成少量大的磁盘 IO,性能可以提升 10-100 倍。

【考点对应】模块二:缓冲流核心原理


6. serialVersionUID 的作用是什么?不手动指定会有什么问题?

【标准答案】

作用

serialVersionUID 是 Java 序列化的版本唯一标识

  1. 序列化时,会把 serialVersionUID 写入字节序列中;
  2. 反序列化时,JVM 会对比字节序列中的 serialVersionUID 和本地类的 serialVersionUID,不一致会抛出InvalidClassException,反序列化失败。
不手动指定的问题

不手动指定时,JVM 会根据类的属性、方法、结构自动生成版本号:

  • 只要修改类的结构(加字段、改方法、删属性),版本号就会变化;
  • 导致之前序列化的对象(比如 Redis 缓存、本地文件)反序列化失败,是生产环境高频坑。

最佳实践:所有需要序列化的类,手动指定private static final long serialVersionUID = 1L;

【考点对应】模块二:序列化核心机制


7. BIO、NIO、AIO 的核心区别和适用场景是什么?

【标准答案】

类型 全称 核心特点 线程模型 性能 适用场景
BIO 同步阻塞 IO 每个连接占用一个线程,读写全程阻塞 1 连接 = 1 线程 低,连接多了线程数爆炸,OOM 风险 连接数少、固定的简单场景、测试代码
NIO 同步非阻塞 IO 单线程通过多路复用器管理大量连接,读写非阻塞 1 线程 = N 个连接 高,线程开销极小 高并发、连接数多的互联网场景(Tomcat、Netty、网关、RPC)
AIO 异步非阻塞 IO 完全非阻塞,IO 操作由操作系统完成,完成后回调通知 无阻塞,事件驱动 理论最高 长连接、高并发场景,实际生产应用极少

【考点对应】模块三:NIO 核心对比


8. 零拷贝的原理是什么?有什么核心优势?

【标准答案】

传统 IO 的问题

传统文件传输需要 4 次数据拷贝 + 2 次用户态内核态切换:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡,大量 CPU 时间浪费在拷贝上。

零拷贝原理

零拷贝是消除用户态和内核态之间的数据拷贝 ,Java 中通过FileChannel.transferTo()实现,底层是 Linux 的sendfile系统调用:数据直接从内核缓冲区拷贝到 Socket 缓冲区,不需要经过用户缓冲区,只有 2 次拷贝,全程在内核态完成,无用户态切换。

核心优势
  1. 大幅提升大文件传输性能,速度提升 3-5 倍;
  2. 降低 CPU 占用,解放 CPU 去处理其他业务;
  3. 减少内存占用,不需要在用户态开辟缓冲区。

【考点对应】模块三:零拷贝核心原理


二、场景坑点题(6 道,大厂实习高频业务题 / 代码题)

1. 以下代码的输出结果是什么?为什么?

java 复制代码
public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        int a = 10;
        try {
            a = 20;
            return a;
        } catch (Exception e) {
            a = 30;
            return a;
        } finally {
            a = 40;
        }
    }
}

【标准答案】输出结果:20原因:

  1. try 中的 return 执行时,会先把 a 的当前值(20)保存到返回值快照中;
  2. 再执行 finally 代码块,修改 a 的值为 40,但修改的是局部变量 a,不会影响已经保存的返回快照;
  3. 最后返回快照中的 20。

补充:如果 a 是引用类型,finally 中修改对象的内容会生效,因为快照保存的是对象的地址,指向同一个堆对象。

【考点对应】模块一:带 return 的 try-catch-finally 执行规则


2. 你的校园二手平台要实现商品图片上传功能,应该用字节流还是字符流?为什么?用什么优化手段?

【标准答案】

  1. 必须用字节流:原因:图片是二进制文件,字符流是处理文本的,用字符流读取二进制文件会破坏文件结构,导致图片损坏;字符流的编码转换会篡改字节内容,无法还原。
  2. 优化手段
    • 加缓冲流BufferedInputStream/BufferedOutputStream,减少磁盘 IO 次数,提升上传速度;
    • 用 try-with-resources 管理流,自动关闭,避免流泄漏;
    • 大图片用 NIO 零拷贝优化,提升传输性能。

【考点对应】模块二:IO 流选型、性能优化


3. 你的校园二手平台怎么设计异常体系?全局异常处理器怎么实现?

【标准答案】

异常体系设计
  1. 基础异常类 :自定义BusinessException继承RuntimeException,携带错误码、错误信息,所有业务异常的父类;
  2. 业务细分异常 :按模块拆分OrderExceptionUserExceptionPayExceptionFileException,继承BusinessException,区分不同业务场景的错误;
  3. 避免受检异常:所有业务异常都是运行时异常,避免强制捕获的代码冗余。
全局异常处理器实现

基于 Spring Boot 的@RestControllerAdvice + @ExceptionHandler实现:

  1. 捕获自定义BusinessException,返回对应错误码和提示信息;
  2. 捕获参数校验异常、空指针等系统异常,返回通用友好提示,打印错误日志;
  3. 捕获 Throwable 兜底,避免未捕获异常直接返回给前端,保证接口响应格式统一。

【考点对应】模块一:自定义异常与全局异常处理


4. 你的校园二手平台把商品对象存入 Redis 缓存,修改了 Goods 类加了一个字段后,反序列化失败,是什么原因?怎么解决?

【标准答案】

原因

Goods 类没有手动指定serialVersionUID,JVM 自动根据类结构生成版本号;加字段后类结构变化,版本号改变,Redis 中旧的序列化对象的版本号和新类的版本号不一致,反序列化抛出InvalidClassException

解决方案
  1. 给所有需要序列化的实体类手动指定固定的serialVersionUID,比如private static final long serialVersionUID = 1L;,类修改后版本号不变,兼容旧的序列化对象;
  2. 清空 Redis 中旧的缓存数据,重新写入新的序列化对象;
  3. 扩展字段加transient修饰,不参与序列化,避免版本号变化。

【考点对应】模块二:serialVersionUID 坑点


5. 以下代码读取商品的 TXT 描述文件,输出中文乱码,是什么原因?怎么解决?

java 复制代码
FileInputStream fis = new FileInputStream("goods.txt");
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
    System.out.println(new String(bytes, 0, len));
}
fis.close();

【标准答案】

乱码原因

用字节流读取中文文本,中文在 UTF-8 中占 3 个字节,字节流每次读取固定长度的字节,会把一个中文的字节拆分,导致编码解析错误,出现乱码。

解决方案
  1. 方案 1:用字符流BufferedReader读取,指定 UTF-8 编码,自动处理中文编码;
  2. 方案 2:用转换流InputStreamReader把字节流转为字符流,指定编码,解决不同编码文件的乱码问题;
  3. 方案 3:一次性读取文件的所有字节,再转字符串,避免字节拆分。

【考点对应】模块二:IO 中文乱码问题


6. 你的校园二手平台要实现商品大视频的下载功能,怎么优化下载性能?

【标准答案】核心优化手段是零拷贝,具体实现:

  1. 用 NIO 的FileChannel.transferTo()方法实现零拷贝,避免用户态和内核态的拷贝开销,下载速度提升 3-5 倍,CPU 占用大幅降低;
  2. 开启浏览器缓存,设置静态资源的缓存头,避免重复下载;
  3. 大文件分片下载,支持断点续传,提升用户体验;
  4. 用 CDN 加速静态资源,降低服务器压力。

【考点对应】模块三:零拷贝应用场景、大文件优化


三、进阶原理题(4 道,中大厂实习拔高题)

1. 为什么现代 Java 框架(Spring、MyBatis)都把受检异常转为运行时异常?受检异常有什么设计弊端?

【标准答案】受检异常的核心设计弊端:

  1. 代码冗余:强制开发者捕获 / 抛出,每个方法都要写大量 try-catch,代码可读性差,嵌套严重;
  2. 破坏封装:上层调用者需要知道底层抛出的受检异常,底层修改异常会影响所有上层调用,违反开闭原则;
  3. 无实际意义:绝大多数受检异常(IOException、SQLException)上层调用者无法处理,只能打印日志抛给前端,强制捕获没有意义;
  4. Lambda/Stream 不兼容:函数式编程中无法抛出受检异常,需要额外处理,代码繁琐。

现代框架统一转为运行时异常,由全局异常处理器统一处理,代码更简洁,解耦性更强,符合企业级开发的最佳实践。

【考点对应】模块一:受检异常设计弊端


2. NIO 的多路复用原理是什么?为什么单线程就能管理成千上万的连接?

【标准答案】NIO 多路复用的核心是Selector 多路复用器 ,底层依赖操作系统的select/poll/epoll系统调用:

  1. 所有 Channel 都注册到 Selector 上,Selector 监听 Channel 的读写事件;
  2. 没有事件时,Selector 线程阻塞,不占用 CPU;
  3. 有事件时,操作系统通知 Selector,Selector 线程只需要处理有事件的 Channel,不需要轮询所有连接;
  4. 全程只有一个线程处理所有连接的事件,线程开销极小,因此可以管理成千上万的连接。

对比 BIO:每个连接一个线程,连接多了线程数爆炸,NIO 用单线程就可以处理大量连接,这就是高并发的核心。

【考点对应】模块三:NIO 多路复用原理


3. 为什么 NIO 的性能远高于 BIO?

【标准答案】核心差异在三个维度:

  1. 线程模型:BIO 是 1 连接 = 1 线程,连接多了线程上下文切换开销极大,容易 OOM;NIO 是 1 线程管理 N 个连接,线程开销几乎可以忽略;
  2. 阻塞模式:BIO 读写全程阻塞,线程只能等待 IO 完成,无法做其他事;NIO 非阻塞,没有事件时线程可以处理其他任务,CPU 利用率极高;
  3. 数据处理:BIO 面向流,每次读写一个字节 / 字符,频繁 IO;NIO 面向缓冲区,批量读写数据,IO 次数少,性能更高。

【考点对应】模块三:NIO 性能优势原理


4. Java 序列化是浅拷贝还是深拷贝?怎么实现深拷贝?

【标准答案】

  1. 默认序列化是浅拷贝:序列化对象时,只会拷贝对象本身和对象中的基本类型属性;如果对象中有引用类型属性,只会拷贝引用的地址,不会拷贝引用指向的对象,两个对象会共享同一个引用对象。
  2. 实现深拷贝的方式
    • 方式 1:所有引用属性也实现 Serializable 接口,递归序列化;
    • 方式 2:重写 writeObject 和 readObject 方法,手动序列化引用属性;
    • 方式 3:用 JSON 序列化(Jackson、Gson)把对象转 JSON 再转回对象,实现深拷贝;
    • 方式 4:用 Apache Commons Lang 的 SerializationUtils 工具类实现深拷贝。

【考点对应】模块二:序列化深浅拷贝问题


实习面试答题加分技巧(绑定你的校园二手平台项目)

  1. 所有异常题都绑定项目架构
    • 问异常体系:主动说「我做校园二手平台的时候,按模块拆分了订单、用户、支付等自定义业务异常,全部继承运行时异常,用 Spring 的全局异常处理器统一捕获,代码里没有冗余的 try-catch」;
  2. 所有 IO 题都绑定项目实践
    • 问 IO 优化:主动说「我做商品图片上传的时候,用缓冲流 + try-with-resources,大文件下载用零拷贝优化,之前踩过字节流读取中文乱码、serialVersionUID 不指定导致 Redis 反序列化失败的坑」;
  3. 答题逻辑固定为「结论→原理→我的项目踩坑 / 实践」,比单纯背知识点的候选人认可度高很多。
相关推荐
郝学胜-神的一滴7 分钟前
完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
java·开发语言·数据结构·c++·python·算法
WangN29 分钟前
【通识】宇树G1_29DOF速度跟踪训练—逐章学习手册
人工智能·python·学习·机器人·具身智能
农民小飞侠10 分钟前
[leetcode] 165. Compare Version Numbers
java·算法·leetcode
砍材农夫21 分钟前
物联网实战|Spring Boot + Netty 搭建 MQTT 消息路由与流转层
java·spring boot·后端·物联网·spring
لا معنى له22 分钟前
NeoVerse: Enhancing 4D World Model with in-the-wild Monocular Videos
人工智能·笔记·机器学习·语言模型
黄毛火烧雪下24 分钟前
Java 基础笔记:文件、递归与字符编码
java·开发语言·笔记
学计算机的计算基26 分钟前
链表算法上篇:LeetCode 206/234/141/142/160/21 题解与易错点
java·笔记·算法·链表
信也科技布道师28 分钟前
从Istio 503 NC 错误深入理解 Mesh 路由全链路原理
java·服务器·网络
swordbob32 分钟前
3 大 I/O 模型BIO / NIO / AIO
java·linux·spring
Pluto_CSND34 分钟前
Cron表达式使用说明
java