0%

IO操作

Java IO 全解析:从基础流到序列化

Java 的 I/O(输入 / 输出)机制是程序与外部世界(文件、网络、控制台等)交互的核心,其设计围绕 “流(Stream)” 展开 —— 通过流的方式有序传输数据。本文将从 I/O 操作模式出发,详细解析 Java IO 体系的核心类、使用方法及最佳实践。

I/O 操作模式:五种基本类型

I/O 操作的本质是 “数据从设备到内核缓冲区,再从缓冲区到用户进程” 的过程。根据等待数据的方式不同,分为五种模式:

模式 核心特点 适用场景
阻塞 I/O 进程发起请求后阻塞,直到数据复制完成才唤醒。 简单场景(如单线程读取小文件)
非阻塞 I/O 进程不阻塞,定期轮询缓冲区状态,数据就绪后再复制(复制时可能阻塞)。 需快速响应的场景
I/O 复用 单进程监控多个 I/O 通道,数据就绪后通知进程处理(如 select/epoll)。 高并发网络编程(如 NIO)
信号驱动 I/O 进程不阻塞,数据就绪后内核通过信号通知,再处理复制。 实时性要求高的场景
异步 I/O 进程发起请求后完全不阻塞,内核自动完成全流程,完成后通知进程。 高性能 I/O 场景(如磁盘操作)

注:Java 传统 IO(java.io)主要基于阻塞 I/O,而 NIO(java.nio)引入了 I/O 复用机制。

Java IO 核心体系:流的分类

Java IO 包(java.io)的类按功能可分为四大类,核心是 “字节流” 和 “字符流”:

类型 核心接口 / 类 处理数据类型 典型用途
字节流 InputStream(输入)、OutputStream(输出) 二进制数据(字节) 图片、视频、压缩文件等
字符流 Reader(输入)、Writer(输出) 文本数据(字符) 文本文件、配置文件等
磁盘操作 File 文件 / 目录元数据 创建 / 删除文件、获取路径
网络操作(java.net SocketServerSocket 网络字节流 客户端 / 服务器通信

字节流:处理二进制数据

字节流以字节(8 位)为单位传输数据,是所有 I/O 操作的基础。核心基类为抽象类 InputStream(输入)和 OutputStream(输出)。

字节输入流(InputStream

所有字节输入流均继承自 InputStream,用于从数据源读取字节。

核心子类及功能
数据源 核心功能 构造器参数示例
ByteArrayInputStream 内存字节数组 从内存缓冲区读取数据,无需磁盘 I/O。 new byte[] {1,2,3}
FileInputStream 本地文件 从文件读取字节,是文件输入的基础类。 "/data/test.bin"new File(...)
PipedInputStream 管道输出流 PipedOutputStream 配合,实现线程间通信。 new PipedOutputStream()
SequenceInputStream 多个输入流 合并多个流为一个,按顺序读取。 new InputStream[] {in1, in2}
装饰器子类(FilterInputStream

通过 “装饰器模式” 为基础流添加功能(如缓冲、数据类型转换):

增强功能 适用场景
BufferedInputStream 增加缓冲区,减少磁盘 I/O 次数(默认 8KB)。 大文件读取(提升性能 10-100 倍)
DataInputStream 读取基本数据类型(int/double 等)。 DataOutputStream 配合读写结构化数据
PushbackInputStream 支持 “回退” 已读取的字节(如解析协议)。 编译器、协议解析器
核心方法
  • 读取数据

    1
    2
    3
    4
    5
    6
    7
    8
    // 读取单个字节(返回值:0~255,-1 表示结束)
    int read() throws IOException;

    // 读取字节到数组 b 中,返回实际读取的字节数
    int read(byte[] b) throws IOException;

    // 读取字节到数组 b 的 off 位置,长度为 len
    int read(byte[] b, int off, int len) throws IOException;
  • 控制流位置

    1
    2
    3
    4
    5
    6
    7
    8
    // 跳过 n 个字节(返回实际跳过的字节数)
    long skip(long n) throws IOException;

    // 标记当前位置(readlimit:允许重复读取的最大字节数)
    void mark(int readlimit);

    // 重置到标记位置(需流支持 mark,如 BufferedInputStream)
    void reset() throws IOException;
  • 关闭流void close()(释放资源,1.7+ 推荐用 try-with-resources 自动关闭)。

字节输出流(OutputStream

所有字节输出流均继承自 OutputStream,用于将字节写入目标。

核心子类及功能
目标位置 核心功能 构造器参数示例
ByteArrayOutputStream 内存字节数组 数据写入内存缓冲区,可通过 toByteArray() 获取。 初始容量(默认 32 字节)
FileOutputStream 本地文件 字节写入文件,是文件输出的基础类。 "/data/test.bin"new File(...)
PipedOutputStream 管道输入流 PipedInputStream 配合,线程间通信。 new PipedInputStream()
装饰器子类(FilterOutputStream
增强功能 适用场景
BufferedOutputStream 缓冲区减少磁盘 I/O,flush() 强制写入。 大文件写入(提升性能)
DataOutputStream 写入基本数据类型(int/double 等)。 DataInputStream 配合保存结构化数据
PrintStream 格式化输出(println() 等方法)。 标准输出(System.out)、日志输出
核心方法
  • 写入数据

    1
    2
    3
    4
    5
    6
    7
    8
    // 写入单个字节(仅取 int 的低 8 位)
    void write(int b) throws IOException;

    // 写入整个字节数组
    void write(byte[] b) throws IOException;

    // 写入数组 b 的 off 位置开始的 len 个字节
    void write(byte[] b, int off, int len) throws IOException;
  • 刷新缓冲区void flush()(缓冲流需手动调用,确保数据写入目标)。

  • 关闭流void close()(关闭前自动调用 flush())。

字符流:处理文本数据

字符流以字符(16 位 Unicode)为单位传输,自动处理字节与字符的编码转换(如 UTF-8、GBK),适合文本数据。核心基类为 Reader(输入)和 Writer(输出)。

核心类及转换流

  • 转换流:字节流与字符流的桥梁(需指定编码):
    • InputStreamReader:将 InputStream 转为 Reader(如 new InputStreamReader(System.in, "UTF-8"))。
    • OutputStreamWriter:将 OutputStream 转为 Writer(如 new OutputStreamWriter(out, "GBK"))。
  • 缓冲字符流
    • BufferedReader:提供 readLine() 方法(逐行读取文本),效率更高。
    • BufferedWriter:提供 newLine() 方法(跨平台换行)。
  • 文件字符流:简化版转换流(使用默认编码):
    • FileReader = new InputStreamReader(new FileInputStream(...))
    • FileWriter = new OutputStreamWriter(new FileOutputStream(...))

示例:字符流读写文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 读取文本文件(指定 UTF-8 编码)
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("test.txt"), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) { // 逐行读取
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}

// 写入文本文件(指定 UTF-8 编码)
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8"))) {
bw.write("Hello, 世界"); // 写入字符
bw.newLine(); // 跨平台换行
bw.flush(); // 缓冲流需刷新
} catch (IOException e) {
e.printStackTrace();
}

RandomAccessFile:随机访问文件

RandomAccessFile 是特殊的 I/O 类,不继承字节流 / 字符流,但实现了 DataInputDataOutput 接口,支持随机读写文件(通过 seek() 定位位置)。

核心特性

  • 模式"r"(只读)、"rw"(读写,文件不存在则创建)。
  • 随机访问void seek(long pos) 定位文件指针(单位:字节)。
  • 读写基本类型:如 readInt()writeDouble() 等。

示例:修改文件中间内容

1
2
3
4
5
6
7
8
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) {
// 定位到第 8 字节(跳过前 8 字节)
raf.seek(8);
// 写入新数据(覆盖原有内容)
raf.writeInt(1024);
} catch (IOException e) {
e.printStackTrace();
}

适用场景:固定格式的记录文件(如日志、数据库文件),可直接定位到某条记录修改。

标准 IO:与系统交互

Java 提供三个标准流与系统交互,由 System 类管理:

类型 默认目标 / 源 用途
System.in InputStream 键盘输入 读取用户输入
System.out PrintStream 控制台输出 打印正常信息
System.err PrintStream 控制台输出 打印错误信息

标准输入处理

System.in 是字节流,需转为字符流处理文本:

1
2
3
4
5
6
7
8
9
10
// 读取控制台输入(输入 "end" 退出)
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
String input;
while ((input = br.readLine()) != null) {
if ("end".equals(input)) break;
System.out.println("你输入了:" + input);
}
} catch (IOException e) {
e.printStackTrace();
}

重定向标准流

通过 System 类的静态方法重定向流(如将输出写入文件):

1
2
3
4
5
6
7
try (PrintStream fileOut = new PrintStream(new FileOutputStream("log.txt"))) {
// 重定向标准输出到文件
System.setOut(fileOut);
System.out.println("这条消息会写入 log.txt"); // 不再打印到控制台
} catch (FileNotFoundException e) {
e.printStackTrace();
}

序列化与反序列化

序列化是将对象转为字节序列(便于存储 / 传输),反序列化是将字节序列恢复为对象。

核心类与接口

  • Serializable:标记接口(无方法),只有实现该接口的类才能被序列化。
  • ObjectOutputStream:序列化流,通过 writeObject(Object) 写入对象。
  • ObjectInputStream:反序列化流,通过 readObject() 恢复对象。

关键规则

  1. transient 关键字:修饰的字段不参与序列化(如敏感信息)。
  2. 静态变量:不序列化(属于类,而非对象实例)。
  3. 反序列化:不执行构造函数(直接恢复对象状态)。
  4. serialVersionUID:显式定义版本号(如 private static final long serialVersionUID = 1L),避免类结构修改后反序列化失败。

示例:对象序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 可序列化的类
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient String password; // 不序列化

public User(String name, String password) {
this.name = name;
this.password = password;
}
}

// 序列化与反序列化
public class SerialDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化到文件
User user = new User("Alice", "123456");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
}

// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserialized = (User) ois.readObject();
System.out.println(deserialized.name); // Alice(正确恢复)
System.out.println(deserialized.password); // null(transient 字段未序列化)
}
}
}

最佳实践

  1. 选择合适的流类型
    • 二进制数据(图片、视频)→ 字节流(FileInputStream/BufferedInputStream)。
    • 文本数据 → 字符流(BufferedReader/BufferedWriter),指定编码(如 UTF-8)。
  2. 使用缓冲流提升性能:缓冲流(BufferedXXX)通过减少底层 I/O 调用,性能提升显著(尤其是大文件)。
  3. 优先使用 try-with-resources:自动关闭流,避免资源泄露(无需手动调用 close())。
  4. 序列化注意事项
    • 敏感字段用 transient 修饰。
    • 显式定义 serialVersionUID 保证版本兼容性。
  5. 避免重复创建流:流的创建成本较高,频繁创建会降低性能(如循环中避免创建流)

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10