传统流式 I/O 与装饰器体系

共 32 题
📑 题目列表 32 题
#
★★★

1. BufferedInputStream/BufferedReader 的缓冲机制与 mark/reset 语义的边界限制

BufferedInputStream/BufferedReader 的缓冲机制与 mark/reset 语义的边界限制是什么?

  • 缓冲机制
  • mark/reset 语义
  • 边界限制

BufferedInputStream 与 BufferedReader 通过内部缓冲数组减少底层 I/O 调用次数,提升性能。mark/reset 用于可回退读取:mark(readlimit) 记录当前位置,reset() 回到 mark 位置。边界限制:mark 的 readlimit 是"在 mark 之后最多可读多少字节仍能 reset",若读取超过 readlimit 字节,mark 可能失效,reset 抛 IOException;BufferedReader 的 readlimit 是字符数;缓冲大小有限,mark 后读取超过缓冲重填时,若超过 readlimit 可能失效。readlimit 需设置为足够大以保证 reset 有效。理解缓冲机制与 mark/reset 的 readlimit 边界限制(超过则失效)是本题关键。

核心是缓冲减少 I/O 调用,mark/reset 受 readlimit 限制(超过则失效)。理解边界限制是本题关键。

#
★★★

2. ByteArrayOutputStream 与 PipedInputStream/PipedOutputStream 的典型用途与线程死锁坑点

ByteArrayOutputStream 与 PipedInputStream/PipedOutputStream 的典型用途是什么?线程死锁坑点是什么?

  • ByteArrayOutputStream 用途
  • Piped 流用途
  • 线程死锁坑点

ByteArrayOutputStream 用于在内存中累积字节数据(可 toByteArray 提取),适合不确定大小的输出缓冲;PipedInputStream/PipedOutputStream 用于线程间传输数据(一个线程写 PipedOutputStream,另一线程读 PipedInputStream),类似管道。死锁坑点:Piped 流若同一线程读写(未连接正确)或读写线程阻塞且无缓冲,可能死锁;Piped 流默认有缓冲(约 1024 字节),若写线程填满而读线程未读,写线程阻塞;若读写线程交替依赖未正确设计,可能死锁。Piped 流必须一端连接另一端,且应避免单线程读写。理解 ByteArrayOutputStream 内存累积用途、Piped 流线程间管道与死锁坑点(缓冲满阻塞、线程依赖)是本题关键。

核心是 ByteArrayOutputStream 内存累积、Piped 流线程间传输,死锁坑点是缓冲满与线程依赖。理解用途与坑点是本题关键。

#
★★★

3. Java I/O 装饰器模式,BufferedInputStream/DataInputStream/GZIPInputStream 的组合原理

Java I/O 装饰器模式中 BufferedInputStream、DataInputStream、GZIPInputStream 的组合原理是什么?

  • I/O 装饰器模式
  • 流组合
  • 职责叠加

Java I/O 采用装饰器模式(Decorator),通过包装流叠加功能。BufferedInputStream 提供缓冲,DataInputStream 提供基本类型读取(readInt 等),GZIPInputStream 提供解压。组合原理:new DataInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(file)))),内层流负责底层 I/O,外层流逐步添加功能(缓冲、解压、类型读取)。装饰器模式让各功能独立、可任意组合,且遵循"顺序"(解压需在缓冲之上、类型读取在解压之上)。理解装饰器模式的组合原理(逐层包装叠加功能)与正确顺序是本题关键。

核心是装饰器模式逐层包装叠加缓冲/解压/类型读取功能,顺序影响正确性。理解组合原理是本题关键。

DataInputStream in = new DataInputStream(
    new GZIPInputStream(new BufferedInputStream(new FileInputStream(f))));
#
★★★

4. 在 Spring Boot 4.0 启动时 file.encoding 默认值与 Docker 镜像 LANG 环境变量的优先级如何

在 Spring Boot 4.0 启动时 file.encoding 默认值与 Docker 镜像 LANG 环境变量的优先级如何?

  • file.encoding 默认值
  • LANG 环境变量
  • 优先级

在 JDK 18+(含 JDK 25,Spring Boot 4.0 基于 JDK 17+)中,file.encoding 默认值为 UTF-8(JEP 400),优先级为:显式 -Dfile.encoding 参数 > JDK 默认 UTF-8(JEP 400)> LANG 环境变量(仅旧版 JDK 用于推断)。因此在 JDK 18+ 中,即使 Docker 镜像设置 LANG=C 或其他值,Spring Boot 4.0 的默认 file.encoding 仍为 UTF-8,除非显式通过 -Dfile.encoding 覆盖。LANG 不再影响默认编码(JDK 18+)。理解优先级(显式参数 > JDK 默认 UTF-8 > LANG)与 JDK 18+ 忽略 LANG 是本题关键。

核心是 JDK 18+ 默认 UTF-8,LANG 不再影响,显式参数优先。理解优先级是本题关键。

#
★★★

5. ObjectInputStream 读取对象图时循环引用如何通过句柄表识别,与普通字节流的顺序读取有何差异

ObjectInputStream 读取对象图时循环引用如何通过句柄表识别?与普通字节流的顺序读取有何差异?

  • 句柄表(handle)
  • 循环引用识别
  • 与顺序读取的差异

ObjectInputStream 在读取对象图时维护一个句柄表(handle table),记录已反序列化对象与对应句柄(序号)。当读取到引用时,通过句柄表识别是否已存在:若对象已被读取,则写入句柄引用而非重复序列化,反序列化时通过句柄表返回已创建的对象,从而解决循环引用(如 A<->B)并保持对象引用关系。这与普通字节流的顺序读取不同:普通字节流只是按顺序读取字节,无对象图/引用概念;ObjectInputStream 读取的是对象图协议(含类型、字段、句柄),需要句柄表维护引用关系。理解 句柄表识别循环引用、与普通字节流顺序读取的差异是本题关键。

核心是 ObjectInputStream 用句柄表识别循环引用并保持引用关系,普通字节流无此概念。理解差异是本题关键。

#
★★★

6. Reader 与 InputStream 的 read() 都返回 int,两者的取值范围与 EOF 语义有何不同

Reader 与 InputStream 的 read() 都返回 int,两者的取值范围与 EOF 语义有何不同?

  • read() 返回值
  • 取值范围
  • EOF 语义

InputStream.read() 返回一个字节(0-255),用 int 表示,读取到 EOF 返回 -1;Reader.read() 返回一个字符(0-65535,UTF-16 code unit),用 int 表示,读取到 EOF 返回 -1。两者都返回 int 以容纳 -1(EOF 标记),但取值范围不同:InputStream 是 0-255(字节),Reader 是 0-65535(字符)。EOF 语义相同(都返回 -1 表示流结束)。差异关键在于:InputStream 处理字节,Reader 处理字符(经过字符集解码),返回值范围反映字符/字节的范围。理解两者返回值范围(字节 0-255 vs 字符 0-65535)与 EOF 相同(-1)是本题关键。

核心是 InputStream 返回字节 0-255、Reader 返回字符 0-65535,EOF 都返回 -1。理解取值范围与 EOF 差异是本题关键。

#
★★

7. MappedByteBuffer 与内存映射的性能价值

MappedByteBuffer 与内存映射的性能价值是什么?

  • MappedByteBuffer 内存映射
  • 性能价值
  • 应用场景

MappedByteBuffer 是 FileChannel.map() 返回的内存映射缓冲,将文件映射到进程地址空间,访问文件如同访问内存。性能价值:减少系统调用(通过指针访问,无需 read/write 系统调用)、利用页缓存(随机访问命中页缓存)、顺序读写与随机读写性能高(尤其大文件)、适合大文件与频繁访问。应用场景:数据库存储、日志顺序写、大文件处理(RocketMQ 的 CommitLog)。注意:映射后写需 force 持久化、映射受地址空间限制、MappedByteBuffer 回收依赖 GC。理解 MappedByteBuffer 减少系统调用、利用页缓存、适合大文件/顺序写的性能价值是本题关键。

核心是内存映射减少系统调用、利用页缓存、适合大文件与顺序写,RocketMQ 等应用。理解性能价值是本题关键。

#
★★

8. 代理对被切在两个 ByteBuffer 或字符块之间时,增量解码器应如何保存并恢复未完成状态

代理对被切在两个 ByteBuffer 或字符块之间时,增量解码器应如何保存并恢复未完成状态?

  • 增量解码
  • 代理对/多字节分割
  • 状态保存与恢复

当多字节字符(UTF-8 多字节序列)或代理对(UTF-16 补充平面)被切分在两个 ByteBuffer/字符块之间时,增量解码器需保存未完成状态。方法:使用 CharsetDecoder 的流式解码(decode(ByteBuffer, CharBuffer, endOfInput)),decoder 内部会保存未完成的字节序列(等待后续输入补全);当读到块尾且未完成时,decoder 保留剩余字节,下次输入继续解码。对代理对,需在字符边界处理(若一个代理对的两个 char 被分割,需缓存前一 char 待下次组合)。实现:维护"待处理"状态(如未完成字节/字符缓冲区),在块边界时缓存未完成部分,下一块开始时恢复。理解增量解码器保存未完成状态(CharsetDecoder 内部状态/缓存)与恢复是本题关键。

核心是增量解码器需保存未完成的多字节/代理对状态,用 CharsetDecoder 流式解码或缓存待处理部分。理解保存与恢复是本题关键。

#
★★

9. BufferedOutputStream 与 BufferedWriter 的缓冲刷新时机,flush 与 close 在异常路径下的资源释放顺序

BufferedOutputStream 与 BufferedWriter 的缓冲刷新时机,flush 与 close 在异常路径下的资源释放顺序是什么?

  • 缓冲刷新时机
  • flush 与 close 顺序
  • 异常路径资源释放

BufferedOutputStream 与 BufferedWriter 有内部缓冲,数据先写入缓冲,缓冲满或调用 flush() 时刷到底层流。刷新时机:缓冲满、flush()、close()(close 会先 flush)。flush 与 close 在异常路径下的资源释放顺序:close() 会先 flush 再关闭底层流;若 flush 抛异常,close 应仍尝试关闭底层流释放资源。正确做法:用 try-with-resources 或 finally 确保无论异常都关闭流;close 内部应先 flush(若 flush 失败仍关闭);避免在 close 前忘记 flush 导致数据丢失。异常路径下,先清理资源(关闭底层流)再处理异常,使用 finally 保证关闭。理解刷新时机(缓冲满/flush/close)与异常路径资源释放顺序(先 flush 再关闭、finally 保证)是本题关键。

核心是缓冲满/flush/close 触发刷新,异常路径用 finally 保证先 flush 再关闭底层流。理解时机与顺序是本题关键。

#
★★

10. DataOutputStream 的 writeUTF 与 writeChars 在长度前缀和字节序上的差异,跨语言读取时为何容易踩坑

DataOutputStream 的 writeUTF 与 writeChars 在长度前缀和字节序上的差异,跨语言读取时为何容易踩坑?

  • writeUTF 与 writeChars 的差异
  • 长度前缀与字节序
  • 跨语言踩坑

DataOutputStream.writeUTF 写入 Modified UTF-8 字符串,带 2 字节长度前缀(无符号 short,大端),数据为 Modified UTF-8;writeChars 写入每个字符为 2 字节(大端 UTF-16),无长度前缀。差异:writeUTF 有长度前缀(2 字节大端)+ Modified UTF-8 变长;writeChars 无长度前缀、每字符固定 2 字节大端。跨语言读取易踩坑:Modified UTF-8 与标准 UTF-8 不同(0 字节编码为 C0 80、补充平面用代理对),他语言实现需按 Modified UTF-8 解析;长度前缀是无符号 short(上限 65535),超长字符串会抛异常;writeChars 的字节序(大端)与接收端预期可能不同。理解 writeUTF 的长度前缀+Modified UTF-8、writeChars 固定 2 字节,及跨语言解析的差异是本题关键。

核心是 writeUTF 带长度前缀+Modified UTF-8、writeChars 固定 2 字节无前缀,跨语言解析 Modified UTF-8/字节序易踩坑。理解差异是本题关键。

#
★★

11. FilterInputStream/FilterOutputStream 的模板方法模式与子类扩展点

FilterInputStream/FilterOutputStream 的模板方法模式与子类扩展点是什么?

  • 装饰器基类
  • 模板方法模式
  • 子类扩展点

FilterInputStream/FilterOutputStream 是装饰器模式的基类,包装一个底层流(in/out),并提供 read/write 等方法委托给底层流。子类扩展点:重写 read/write 等核心方法以添加功能(如缓冲、计数、转换),通过改变 read/write 行为实现装饰。这体现模板方法模式:基类定义方法骨架(委托底层),子类覆写扩展。扩展点包括 read()、read(byte[],int,int)、write()、skip、available、close 等。子类需正确委托底层流并保持语义(如 read 返回 -1 表示 EOF)。理解 Filter 流作为装饰器基类、子类通过覆写 read/write 扩展功能是本题关键。

核心是 Filter 流是装饰器基类,子类覆写 read/write 扩展功能,体现模板方法/装饰器模式。理解扩展点是本题关键。

#
★★

12. InputStreamReader 的缓冲大小与字符转换开销如何权衡,逐字符读取为何性能差

InputStreamReader 的缓冲大小与字符转换开销如何权衡?逐字符读取为何性能差?

  • 缓冲大小与转换开销
  • 逐字符读取性能差
  • 权衡

InputStreamReader 将字节流解码为字符流,缓冲大小影响性能:缓冲大则一次读取更多字节、减少解码/系统调用次数,但占用内存;缓冲小则频繁 I/O 与解码,性能差。逐字符读取(read() 每次返回一个字符)性能差的原因:每次 read() 都涉及底层读取、解码、方法调用开销,若未缓冲则每次触发系统调用,开销巨大。权衡:使用 BufferedReader 包装(大缓冲)减少 I/O 次数;合理设置缓冲大小(如 8KB);避免逐字符 read(),改用 read(char[],off,len) 批量读取。理解缓冲大小与转换开销权衡、逐字符读取频繁调用开销大是本题关键。

核心是缓冲大小影响 I/O/解码次数,逐字符 read() 频繁调用开销大,应用缓冲批量读取。理解权衡是本题关键。

#
★★

13. PrintStream/PrintWriter 的自动 flush 与错误吞没(checkError)在生产日志落盘中的坑

PrintStream/PrintWriter 的自动 flush 与错误吞没(checkError)在生产日志落盘中的坑是什么?

  • 自动 flush
  • checkError 错误吞没
  • 日志落盘坑

PrintStream/PrintWriter 的坑:自动 flush 行为(autoFlush 由构造参数决定,默认 System.out 可能不自动 flush,日志重定向时数据可能延迟/丢失);错误吞没(PrintStream/PrintWriter 的 write 出错不抛异常,而是设置内部错误标志,通过 checkError() 查询,但若未检查则错误被静默吞没,导致日志丢失不被察觉)。生产日志落盘坑:使用 System.out 打印日志时,异常路径不 flush 会丢日志;错误吞没导致磁盘写入失败(磁盘满、权限)不被发现。建议:使用专业日志框架(Logback)管理日志与刷新、错误处理;避免依赖 System.out;及时 flush 并检查错误。理解自动 flush 与错误吞没(checkError)在生产日志中的坑是本题关键。

核心是 PrintStream 不自动 flush 丢日志、错误吞没(checkError 不检查则静默),应用日志框架。理解坑是本题关键。

#
★★

14. OutputStreamWriter 编码错误时的默认行为(替换/报错),与 CharsetEncoder 的 REPORT 策略如何对接

OutputStreamWriter 编码错误时的默认行为(替换/报错)是什么?与 CharsetEncoder 的 REPORT 策略如何对接?

  • OutputStreamWriter 编码错误默认行为
  • CharsetEncoder 策略
  • 对接

OutputStreamWriter 编码时,若遇到无法编码的字符(如将中文编码到 ASCII),默认行为是替换(用 '?' 替换,CodingErrorAction.REPLACE),不抛异常,因此可能静默产生乱码。这与 CharsetEncoder 的 REPORT 策略(默认报错)不同:OutputStreamWriter 内部使用 CharsetEncoder 但默认配置为 REPLACE。若要对接 REPORT 策略(报错而非替换),需自定义 CharsetEncoder 并设置 onUnmappableCharacter(REPORT),通过 OutputStreamWriter(构造时传入 encoder)或使用 CharsetEncoder 显式编码。理解 OutputStreamWriter 默认 REPLACE(替换)与 CharsetEncoder REPORT(报错)的差异及对接(自定义 encoder 设 REPORT)是本题关键。

核心是 OutputStreamWriter 默认 REPLACE 替换产生乱码,要报错需自定义 CharsetEncoder 设 REPORT。理解默认行为与对接是本题关键。

#
★★

15. GZIPInputStream 与 BufferedInputStream 的叠加顺序为何影响解压性能,包装顺序错误会带来什么后果

GZIPInputStream 与 BufferedInputStream 的叠加顺序为何影响解压性能?包装顺序错误会带来什么后果?

  • 流叠加顺序
  • 解压性能影响
  • 包装顺序错误后果

GZIPInputStream 与 BufferedInputStream 的叠加顺序影响解压性能:正确顺序是 new GZIPInputStream(new BufferedInputStream(in))(缓冲在底层,解压在上层),因为 GZIPInputStream 需要从底层流读取压缩数据,缓冲能减少底层的系统调用;若顺序颠倒 new BufferedInputStream(new GZIPInputStream(in)),缓冲在解压之上,GZIPInputStream 每次读取压缩数据时仍频繁调用底层流(无缓冲),解压性能差。包装顺序错误的后果:底层流无缓冲导致每读一个压缩块都触发底层 I/O,CPU 与 I/O 开销大,性能显著下降(但通常仍能正确解压)。理解缓冲应在内层(底层)使 GZIP 读取压缩数据得到缓冲,顺序错误导致性能下降是本题关键。

核心是缓冲应在底层使 GZIP 读取压缩数据有缓冲,顺序颠倒导致底层 I/O 频繁性能下降。理解顺序影响是本题关键。

#
★★

16. 自定义 FilterInputStream 子类需要重写哪些方法才能保持装饰器链的语义完整性,常见错误委托有哪些

自定义 FilterInputStream 子类需要重写哪些方法才能保持装饰器链的语义完整性?常见错误委托有哪些?

  • FilterInputStream 重写方法
  • 语义完整性
  • 常见错误委托

自定义 FilterInputStream 子类需重写关键方法保持装饰器链语义完整性:read()、read(byte[],off,len)、skip、available、close、mark/reset 等。若只重写 read() 而不重写 read(byte[],off,len),则批量读取可能绕过自定义逻辑(因为基类 read(byte[]) 默认调用 read() 逐字节,但某些情况下可能不完整)。常见错误委托:重写 read(byte[],off,len) 时未正确委托底层(如没调用 super.read 或 in.read)、只重写部分方法导致其他方法不经过自定义逻辑、未处理 EOF(返回 -1)、close 未委托底层流导致资源泄漏。正确做法:完整重写核心方法并正确委托底层 in,保持语义(EOF、skip、available)。理解需重写的方法与常见错误委托是本题关键。

核心是子类需完整重写 read/skip/available/close 等并正确委托底层,避免部分方法绕过自定义逻辑。理解正确重写是本题关键。

#
★★

17. RandomAccessFile 的 seek 与 FileChannel 的 position 在并发读写时的线程安全差异

RandomAccessFile 的 seek 与 FileChannel 的 position 在并发读写时的线程安全差异是什么?

  • seek 与 position
  • 并发读写线程安全
  • 差异

RandomAccessFile 的 seek(long) 与 FileChannel 的 position(long) 都用于定位读写位置。并发差异:RandomAccessFile 的实例不是线程安全的,多个线程并发 seek/read/write 同一 RandomAccessFile 会导致位置竞争与数据错乱;FileChannel 的 position 是共享的,并发读写同一 FileChannel 时 position 会竞争,多个线程并发读写也会错乱。因此两者默认都不适合多线程并发读写同一实例(position 竞争)。要并发读写,需:每个线程使用独立 FileChannel(从同一文件打开多个通道)或使用绝对定位(如 FileChannel 的 read(b, position) 有 position 参数的重载,不改变当前位置,可并发);或外部同步。理解两者并发读写线程安全差异(position 竞争,需独立通道或绝对定位)是本题关键。

核心是 RandomAccessFile 与 FileChannel 都有位置竞争,并发需独立通道或绝对定位重载。理解差异是本题关键。

#
★★

18. StreamTokenizer 的语法解析局限,为什么现代协议解析改用 PushbackInputStream 手动分词

StreamTokenizer 的语法解析局限是什么?为什么现代协议解析改用 PushbackInputStream 手动分词?

  • StreamTokenizer 的局限
  • PushbackInputStream 手动分词
  • 现代协议解析

StreamTokenizer 用于把输入流解析为 token(单词、数字、符号),但局限:它的分词规则简单固定(按空白、限定符、数字识别),不支持复杂语法、正则、自定义上下文,难以处理现代协议(如 JSON、HTTP、自定义二进制协议)的复杂边界与转义。现代协议解析改用 PushbackInputStream 手动分词的原因:PushbackInputStream 支持 unread 回退,可读一个字节后根据情况放回重新读,实现精确的逐字节解析与边界检测(如检测分隔符、转义、引号),灵活处理复杂协议。因此现代协议(HTTP 头、自定义帧)用 PushbackInputStream 手动分词,比 StreamTokenizer 更精确可控。理解 StreamTokenizer 的局限(简单固定规则)与 PushbackInputStream 手动分词(unread 回退精确解析)是本题关键。

核心是 StreamTokenizer 规则简单不适用复杂协议,PushbackInputStream 的 unread 回退支持精确手动分词。理解局限与改进是本题关键。

#
★★

19. ByteArrayInputStream 与 ByteArrayOutputStream 的内存占用,大对象流式处理为何应改用文件流或管道

ByteArrayInputStream 与 ByteArrayOutputStream 的内存占用,大对象流式处理为何应改用文件流或管道?

  • ByteArray 流的内存占用
  • 大对象流式处理
  • 改用文件流/管道

ByteArrayInputStream 与 ByteArrayOutputStream 基于内存中的字节数组,数据全部驻留内存,内存占用等于数据大小。大对象(如大文件、大响应体)用 ByteArrayOutputStream 累积会占用大量内存,甚至 OOM。因此大对象流式处理应改用文件流(FileOutputStream 写到磁盘)或管道(PipedOutputStream 流式传输),避免一次性占用内存,实现流式处理(边读边写、分块处理)。ByteArray 流适合小数据(如构建小响应、内存缓冲);大对象用文件流/管道减少内存占用、支持流式。理解 ByteArray 流内存占用(全量驻留)与大对象改用文件流/管道流式处理是本题关键。

核心是 ByteArray 流全量驻留内存,大对象改用文件流/管道流式处理避免 OOM。理解内存占用与替代是本题关键。

#
★★

20. ObjectInputStream/ObjectOutputStream 的协议

ObjectInputStream/ObjectOutputStream 的协议是什么?

  • 序列化协议格式
  • 流头与数据
  • 对象图

ObjectOutputStream/ObjectInputStream 使用 Java 序列化协议(wire format):流以魔法头(STREAM_MAGIC 0xACED + STREAM_VERSION)开头,后跟对象图数据。对象图:类描述(类名、serialVersionUID、字段描述)、对象数据(字段值)、句柄(引用)。协议包含:类描述符(TC_CLASSDESC)、对象(TC_OBJECT)、字符串(TC_STRING)、数组(TC_ARRAY)、引用(TC_REFERENCE)、null(TC_NULL)、block data(TC_BLOCKDATA)等标记。序列化协议是自描述的(含类型信息),因此体积大但可重建对象图。理解协议格式(魔法头+类描述+对象数据+句柄)与自描述特性是本题关键。

核心是序列化协议以魔法头开头,含类描述、对象数据、句柄,自描述可重建对象图。理解协议格式是本题关键。

#
★★

21. ObjectStreamClass 与字段演进

ObjectStreamClass 与字段演进是什么?

  • ObjectStreamClass 的作用
  • 字段演进
  • 兼容性

ObjectStreamClass 描述类的序列化信息(类名、serialVersionUID、字段列表、方法),用于序列化/反序列化时匹配类。字段演进:当类字段变化(新增/删除/重命名)时,序列化数据的旧字段与当前类字段可能不匹配。演进规则:新增字段在反序列化旧数据时用默认值(null/0);删除字段时旧数据中该字段被忽略;serialVersionUID 匹配但字段不匹配时,按字段名匹配能找到的字段,找不到的用默认。字段演进需保持 serialVersionUID 一致(若兼容演进)或处理 InvalidClassException。理解 ObjectStreamClass 描述类序列化信息、字段演进规则(新增默认值、删除忽略)是本题关键。

核心是 ObjectStreamClass 描述序列化类,字段演进新增默认值、删除忽略,serialVersionUID 控制兼容。理解演进规则是本题关键。

#
★★

22. PushbackInputStream 的 unread 回退在协议解析(如 HTTP 头边界检测)中的应用

PushbackInputStream 的 unread 回退在协议解析(如 HTTP 头边界检测)中的应用是什么?

  • unread 回退
  • 协议解析
  • HTTP 头边界检测

PushbackInputStream 的 unread 方法将已读的字节放回缓冲区,使后续读取能重新读到。协议解析中用于边界检测:读入一个字节后,若不确定是否属于某边界(如 HTTP 头的 \r\n\r\n 分隔、分隔符、引号),可用 unread 放回,重新按另一种方式解析。HTTP 头边界检测:逐字节读取检测 \r\n\r\n(空行)作为头结束标记,读到可能属于边界或内容的字节时,用 unread 回退后做精确判断。unread 允许"读一个字节看看,不是就放回",实现前瞻(lookahead)解析,避免过度读取。理解 unread 回退实现前瞻解析、用于 HTTP 头等协议边界检测是本题关键。

核心是 unread 放回字节实现前瞻解析,用于检测协议边界(如 HTTP 头 \r\n\r\n)。理解应用是本题关键。

#
★★

23. SequenceInputStream 的流合并与多个数据源顺序读取的工程场景

SequenceInputStream 的流合并与多个数据源顺序读取的工程场景是什么?

  • SequenceInputStream 流合并
  • 多数据源顺序读取
  • 工程场景

SequenceInputStream 将多个 InputStream 按顺序合并为一个流,当第一个流读完自动读下一个,实现多数据源顺序读取。工程场景:合并多个文件的顺序读取(如日志文件分段拼接)、合并多个输入源(如多个数据块)作为单一流处理、配合其他流装饰(如 GZIP、Buffered)处理合并数据。它把多个逻辑流视为一个连续流,简化"顺序读取多个数据源"的处理。注意:SequenceInputStream 是同步非阻塞,适合顺序读取;构造函数可接受 Enumeration 或两个流。理解 SequenceInputStream 合并多流顺序读取、用于多文件/多源拼接是本题关键。

核心是 SequenceInputStream 按顺序合并多流,用于多文件/多源顺序读取的工程场景。理解应用是本题关键。

#
★★

24. StringReader/StringWriter 作为内存中字符流与 StringBuilder 的取舍

StringReader/StringWriter 作为内存中字符流与 StringBuilder 的取舍是什么?

  • StringReader/StringWriter
  • 与 StringBuilder 取舍
  • 内存字符流

StringReader 以字符串为字符流源读取,StringWriter 将字符流写入内存(内部用 StringBuffer),用于内存中字符流处理。与 StringBuilder 的取舍:StringReader/StringWriter 提供流式 API(Reader/Writer 接口),可与流式处理(如 Reader 包装、I/O 装饰)集成,适合需要"以流的方式"处理字符串的场景(如作为 Reader 传给解析器);StringBuilder 提供直接字符串拼接(append),更高效、更简单,适合直接构建字符串。取舍:需要流式/Reader 接口兼容用 StringReader/StringWriter;纯字符串构建用 StringBuilder。理解 StringReader/StringWriter 内存字符流与 StringBuilder 的取舍(流式 API vs 直接拼接)是本题关键。

核心是 StringReader/Writer 提供内存字符流 API 便于流式集成,StringBuilder 直接拼接更高效,按需选择。理解取舍是本题关键。

#
★★

25. readResolve/writeReplace 的单例保护

readResolve/writeReplace 的单例保护是什么?如何使用?

  • readResolve/writeReplace
  • 单例保护
  • 序列化替换

readResolve 与 writeReplace 用于序列化时的对象替换。readResolve() 在反序列化完成后被调用,返回的替换对象替代反序列化产物,用于单例保护(返回单例实例,避免反序列化创建新实例)与枚举/特殊对象保护。writeReplace() 在序列化前被调用,返回替换对象(如用代理对象序列化)。单例保护:单例类实现 readResolve 返回 INSTANCE,反序列化得到的是单例而非新对象,保证单例不被破坏。writeReplace 用于序列化时替换(如敏感字段脱敏、代理)。理解 readResolve 反序列化后替换保护单例、writeReplace 序列化前替换是本题关键。

核心是 readResolve 反序列化后返回替换对象保护单例,writeReplace 序列化前替换。理解机制是本题关键。

private Object readResolve() { return INSTANCE; }
#
★★

26. transient 字段与 writeObject/readObject 自定义

transient 字段与 writeObject/readObject 自定义是什么?如何使用?

  • transient 字段
  • writeObject/readObject 自定义
  • 序列化定制

transient 关键字标记的字段不参与默认序列化(不写入序列化流),用于排除需重新计算或敏感/不可序列化的字段。若需序列化 transient 字段或控制序列化逻辑,可自定义 writeObject/readObject 方法:writeObject 中用 defaultWriteObject 写默认字段,再用 writeObject 写 transient 字段;readObject 中用 defaultReadObject 读默认字段,再读 transient 字段。自定义 writeObject/readObject 用于:序列化 transient 的派生/敏感字段、控制字段格式、加密、校验。理解 transient 排除默认序列化与 writeObject/readObject 自定义(defaultWriteObject + 手动写 transient)是本题关键。

核心是 transient 不参与默认序列化,自定义 writeObject/readObject 可序列化 transient/控制逻辑。理解机制是本题关键。

#
★★

27. try-with-resources 与 Closeable/AutoCloseable 的资源关闭顺序(声明逆序)与 suppressed 异常

try-with-resources 与 Closeable/AutoCloseable 的资源关闭顺序(声明逆序)与 suppressed 异常是什么?

  • try-with-resources
  • 关闭顺序逆序
  • suppressed 异常

try-with-resources 自动关闭实现了 AutoCloseable/Closeable 的资源。关闭顺序:多个资源按声明顺序的逆序关闭(后声明的先关闭)。suppressed 异常:若 try 块主体抛异常,且资源关闭时也抛异常,关闭异常被加为已抛出异常的 suppressed 异常(通过 getSuppressed 获取),不覆盖主体异常。若主体未抛异常但关闭抛异常,则抛出关闭异常。理解 try-with-resources 自动关闭、逆序关闭、suppressed 异常(关闭异常被附加)是本题关键。

核心是 try-with-resources 逆序关闭、关闭异常作为 suppressed 附加不覆盖主体异常。理解语义是本题关键。

#
★★

28. Reader/Writer 与 InputStream/OutputStream 的 available() 语义差异,为什么字符流没有可靠的 available

Reader/Writer 与 InputStream/OutputStream 的 available() 语义差异是什么?为什么字符流没有可靠的 available?

  • available() 语义
  • 字符流无可靠 available
  • 原因

InputStream.available() 返回可读字节数的估计(不阻塞读取),可用于判断是否可读;但与字符流不同,Reader 没有可靠的 available()(Reader 有 ready() 但语义不同)。字符流没有可靠 available 的原因:字符流经过字符集解码,底层字节流可读的字节数不能直接映射为可读的字符数(多字节字符、可变长编码),且转换缓冲使可读字符数不确定;Reader 的 ready() 只表示缓冲区是否可读,不保证可读字符数。因此字符流无法像字节流那样可靠地报告可读数量。理解 available 字节流可估计、字符流因解码与缓冲无法可靠报告字符数是本题关键。

核心是字符流经字符集解码与缓冲,可读字节数无法映射为可读字符数,因此无可靠 available。理解原因是本题关键。

#
★★

29. 序列化流中 writeUTF 的 Modified UTF-8 与标准 UTF-8 的差异,超长字符串为何容易踩长度上限

序列化流中 writeUTF 的 Modified UTF-8 与标准 UTF-8 的差异,超长字符串为何容易踩长度上限?

  • Modified UTF-8 与标准 UTF-8
  • 长度上限
  • 超长字符串踩坑

序列化流(DataOutputStream.writeUTF、ObjectOutputStream.writeUTF)使用 Modified UTF-8,与标准 UTF-8 的差异:0 字节(U+0000)编码为 C0 80(标准 UTF-8 为 00);补充平面字符用代理对被编码为两个独立的三字节序列(而非标准 UTF-8 的四字节);因此 Modified UTF-8 对某些字符的字节数更多。长度上限:writeUTF 用 2 字节无符号 short 记录长度(上限 65535),但 Modified UTF-8 的编码(如代理对 6 字节、0 字节 2 字节)使"字符数"与"字节数"不同,超长字符串(编码后字节数超过 65535)会抛 UTFDataFormatException。因此超长字符串容易踩长度上限,需注意字节数而非字符数。理解 Modified UTF-8 与标准 UTF-8 的差异(0 字节、代理对)及 2 字节长度上限(字节数)是本题关键。

核心是 Modified UTF-8 编码差异(0 字节 C0 80、代理对 6 字节),writeUTF 2 字节长度上限按字节数,超长抛异常。理解差异与上限是本题关键。

#

30. LineNumberInputStream 的已弃用原因与 LineNumberReader 的替代方案

LineNumberInputStream 的已弃用原因与 LineNumberReader 的替代方案是什么?

  • LineNumberInputStream 弃用原因
  • LineNumberReader 替代
  • 字节流与字符流

LineNumberInputStream 已弃用,弃用原因:它是字节流(InputStream),但行号概念是字符/文本层面的,用字节流处理行号不准确(无法正确处理多字节编码的换行解析),且不能正确处理字符编码。替代方案是 LineNumberReader(字符流 Reader),它在字符层面处理行号,能正确处理 \n、\r\n 等换行与字符集,提供 getLineNumber/setLineNumber。因此推荐用 LineNumberReader 而非 LineNumberInputStream 处理需要行号的文本读取。理解 LineNumberInputStream 弃用(字节流不适合行号)与 LineNumberReader 替代(字符流正确处理行号)是本题关键。

核心是 LineNumberInputStream 是字节流不适合行号处理,LineNumberReader 字符流正确处理换行与编码。理解弃用与替代是本题关键。

#

31. 传统文件 I/O 的 read/write 路径与缓冲,为什么 FileInputStream 逐字节读性能远低于带缓冲读取

传统文件 I/O 的 read/write 路径与缓冲:为什么 FileInputStream 逐字节读性能远低于带缓冲读取?

  • 逐字节读 vs 缓冲读
  • 系统调用开销
  • 缓冲机制

FileInputStream 逐字节读(read() 每次返回一个字节)性能远低于带缓冲读取(BufferedInputStream 或 read(byte[]))的原因:每次 read() 一次都会触发一次系统调用(read 系统调用),逐字节读导致大量系统调用(如读 1MB 文件需 100 万次系统调用),系统调用开销巨大(用户态/内核态切换);而带缓冲读取(read(byte[] len) 一次读入大批数据)只需少量系统调用(如 1KB 缓冲 1000 次),减少系统调用次数。此外,内核页缓存命中、copy 效率也因批量读取更高。理解逐字节读的每次 read() 一次系统调用开销大、缓冲批量读减少系统调用是本题关键。

核心是逐字节读每次触发系统调用,缓冲批量读减少系统调用次数,性能差异巨大。理解原因是本题关键。

#

32. FileInputStream/FileOutputStream 的 finalize 已弃用与显式 close 的必要性

FileInputStream/FileOutputStream 的 finalize 已弃用与显式 close 的必要性是什么?

  • finalize 弃用
  • 显式 close
  • 资源管理

FileInputStream/FileOutputStream 早期通过 finalize() 在 GC 时兜底关闭文件(释放 fd),但 finalize() 已弃用(JDK 9+ 弃用,JDK 18 移除),因为 finalize 执行时机不确定、影响 GC、易泄漏。因此必须显式 close 释放文件描述符:使用 try-with-resources 或 finally 中 close。显式 close 的必要性:文件 fd 是有限资源,不及时 close 会 fd 泄漏,耗尽后无法打开新文件;finalize 兜底不可靠。现代实践:用 try-with-resources 自动关闭,确保异常路径也关闭。理解 finalize 弃用(时机不确定)与显式 close 必要性(避免 fd 泄漏)是本题关键。

核心是 finalize 弃用时机不确定,必须显式 close(try-with-resources)避免 fd 泄漏。理解必要性是本题关键。