Java IO 基础围绕两个问题:数据怎样进出程序,对象怎样转换成可传输或保存的表示。先掌握概念和典型场景即可。
字节流与字符流
字节流以 byte 为单位,常见基类是 InputStream、OutputStream,适合文件、图片、网络二进制数据。字符流以字符为单位,常见基类是 Reader、Writer,适合文本。
字符流需要按照字符编码进行字节与字符转换。InputStreamReader 可以把字节流包装为字符流,文本处理应明确使用 UTF-8 等编码,避免依赖环境默认值导致乱码。
BufferedInputStream、BufferedReader 等缓冲包装减少频繁访问底层资源的次数,但缓冲不等于异步。使用后仍要按资源生命周期关闭,写入时还要考虑 flush。
BIO、NIO、AIO
| 名称 | 基础模型 | 主要组件或用途 |
|---|---|---|
| BIO | 常见传统同步阻塞 IO | InputStream、OutputStream、阻塞 Socket |
| NIO | 提供 Buffer、Channel、Selector;支持非阻塞网络 IO | 一个线程可以通过 Selector 关注多个通道的就绪事件 |
| AIO | 异步完成通知 | 使用异步通道,以回调或 Future 获取完成结果 |
NIO 不等于“所有操作都非阻塞”,Channel 可以有阻塞模式,文件 IO 也不能直接套用网络 Selector 的模式。Selector 告诉程序哪些操作已就绪,后续仍需要应用执行读写,因此不能把就绪通知当作操作已经异步完成。
高并发网络服务常通过 IO 多路复用管理大量连接,但耗时业务仍需要合适的执行资源。想复习系统层面的概念,可以接着读 IO 模型。
序列化与反序列化
序列化把对象状态转换为字节或其他可存储、可传输的格式;反序列化恢复对应的数据结构。JSON、Protocol Buffers 等都是业务中常见的数据交换方案,不等于都使用 Java 原生对象序列化。
Java 原生序列化通常通过 Serializable 标记和 ObjectOutputStream/ObjectInputStream 完成。基础追问主要是:
serialVersionUID用于标识序列化版本兼容性,不是对象的业务 ID。- static 字段属于类,不作为普通实例状态被序列化。
- transient 字段默认不参加常规实例状态序列化,但自定义序列化逻辑可以另行处理它。
- 对不可信数据进行原生反序列化有安全风险,不能直接开放任意类型反序列化。
面试回答模板
字节流处理原始字节,字符流处理文本字符,二者转换时需要明确编码。传统 BIO 常采用同步阻塞方式,NIO 提供通道、缓冲区和选择器,可以用非阻塞与多路复用管理网络连接;AIO 关注异步完成通知。序列化用于保存或传输对象状态,业务可以选择 JSON 等格式,Java 原生序列化还涉及版本兼容性和反序列化安全。
自测
- 读取图片应使用字符流吗?不应,图片是二进制数据。
- Selector 通知可读是否表示完整业务消息已读完?不是,还需要读取、处理半包和消息边界。
- transient 能保证敏感字段绝不会被任何方式输出吗?不能,它只约束相应的默认序列化行为。