Java IO 流编程实战

一、IO 流的核心概念

1. 什么是 IO?

IO 即 Input/Output(输入 / 输出),是 Java 程序和外部设备(文件、网络、控制台)之间数据传输的通道。

  • 输入流(Input):数据从外部设备 → 程序,用来读数据。
  • 输出流(Output):数据从程序 → 外部设备,用来写数据。

2. 核心特性

  • 单向性:流是单向的,输入流只能读、输出流只能写。
  • 顺序性:数据按顺序读写,像水管里的水流,不能随机跳着读(随机访问文件流除外)。
  • 资源性 :流会占用系统资源,使用完必须关闭(close()),否则会造成资源泄漏。

二、IO 流的分类体系

Java IO 流有三种主流分类方式,掌握这个分类就掌握了 70% 的 IO 知识。

表格

分类维度 类别 特点 代表类
按数据单位 字节流 以字节(byte)为单位,可处理所有类型数据(文本 / 图片 / 视频) InputStreamOutputStream
字符流 以字符(char)为单位,仅处理文本数据,自动适配编码 ReaderWriter
按流向 输入流 读数据,从外部到程序 InputStreamReader
输出流 写数据,从程序到外部 OutputStreamWriter
按功能 节点流 直接和数据源相连,基础的读写流 FileInputStreamFileReader
处理流(包装流) 包装节点流,增强读写功能(缓冲、转换、对象序列化) BufferedReaderObjectOutputStream

三、核心类详解(必背)

1. 字节流(InputStream / OutputStream)

核心抽象类
  • InputStream:所有字节输入流的父类,核心方法:read()(读一个字节 / 字节数组)、close()
  • OutputStream:所有字节输出流的父类,核心方法:write()(写一个字节 / 字节数组)、flush()(刷新缓冲区)、close()
常用子类
  1. **文件字节流:FileInputStream / FileOutputStream**直接读写文件,是最基础的节点流,适合处理二进制文件。

    java 复制代码
    // 复制文件(字节流实现,支持所有类型文件)
    public static void copyFile(String srcPath, String destPath) throws IOException {
        try (InputStream in = new FileInputStream(srcPath);
             OutputStream out = new FileOutputStream(destPath)) {
            byte[] buffer = new byte[1024]; // 缓冲区,一次读1KB
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        }
    }
  2. **缓冲字节流:BufferedInputStream / BufferedOutputStream**包装节点流,自带缓冲区,减少 IO 次数,大幅提升读写效率。

    java 复制代码
    // 带缓冲的文件复制,效率比直接用FileInputStream高很多
    public static void copyFileWithBuffer(String srcPath, String destPath) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcPath));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath))) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.flush(); // 强制刷新缓冲区,确保数据全部写入文件
        }
    }
  3. 对象流:ObjectInputStream / ObjectOutputStream 实现对象的序列化 / 反序列化,把对象写入文件或网络,需要对象实现 Serializable 接口。

    java 复制代码
    // 序列化对象到文件
    public static void writeObject(User user, String path) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) {
            oos.writeObject(user);
        }
    }
    
    // 从文件反序列化对象
    public static User readObject(String path) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
            return (User) ois.readObject();
        }
    }
    
    // 序列化的对象必须实现Serializable接口
    static class User implements Serializable {
        private static final long serialVersionUID = 1L; // 序列化版本号,防止兼容性问题
        private String username;
        private int age;
        // getter/setter/构造器省略
    }

2. 字符流(Reader / Writer)

核心抽象类
  • Reader:所有字符输入流的父类,核心方法:read()(读一个字符 / 字符数组 / 字符串)、close()
  • Writer:所有字符输出流的父类,核心方法:write()(写字符 / 字符数组 / 字符串)、flush()close()
常用子类
  1. **文件字符流:FileReader / FileWriter**直接读写文本文件,按字符处理,自动适配平台默认编码,不适合处理非文本文件。

    java 复制代码
    // 读取文本文件
    public static void readTextFile(String path) throws IOException {
        try (Reader reader = new FileReader(path)) {
            char[] buffer = new char[1024];
            int len;
            while ((len = reader.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));
            }
        }
    }
    
    // 写入文本文件
    public static void writeTextFile(String content, String path) throws IOException {
        try (Writer writer = new FileWriter(path, true)) { // true表示追加模式
            writer.write(content);
        }
    }
  2. **转换流:InputStreamReader / OutputStreamWriter**字节流和字符流的桥梁,可指定编码格式,解决乱码问题(比如读取 GBK 编码的文件)。

    java 复制代码
    // 读取GBK编码的文本文件,避免乱码
    public static void readGBKFile(String path) throws IOException {
        // 把字节流转换成字符流,指定编码为GBK
        try (Reader reader = new InputStreamReader(new FileInputStream(path), "GBK")) {
            char[] buffer = new char[1024];
            int len;
            while ((len = reader.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));
            }
        }
    }
  3. **缓冲字符流:BufferedReader / BufferedWriter**包装字符流,自带缓冲区,支持按行读写,是文本文件处理最常用的类。

    java 复制代码
    // 按行读取文本文件,适合处理大文本
    public static void readFileByLine(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
    
    // 按行写入文本文件
    public static void writeFileByLine(List<String> lines, String path) throws IOException {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(path))) {
            for (String line : lines) {
                bw.write(line);
                bw.newLine(); // 写入换行符,适配不同系统
            }
        }
    }

3. 打印流(PrintStream / PrintWriter

  • 特点:自动刷新、格式化输出、不会抛出 IO 异常(异常会被捕获),我们常用的 System.out 就是 PrintStream 类型。

  • 常用场景:控制台输出、日志打印、文件格式化写入。

    // 用PrintWriter写入格式化数据
    public static void printToFile(String path) throws IOException {
    try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(path)))) {
    pw.println("用户信息:");
    pw.printf("用户名:%s,年龄:%d%n", "张三", 18); // 格式化输出
    pw.println("结束");
    }
    }

四、经典场景与避坑指南

1. 流的关闭:必须使用 try-with-resources

Java 7 之后推荐使用 try-with-resources 语法,自动关闭流,不用手动写 finally,避免资源泄漏。

java 复制代码
// 正确写法:try-with-resources自动关闭流
try (InputStream in = new FileInputStream("a.txt");
     OutputStream out = new FileOutputStream("b.txt")) {
    // 读写操作
}
// 不用手动写close(),JVM会自动关闭流

2. 缓冲区问题:必须 flush()

缓冲流的数据会先写到内存缓冲区,close() 会自动刷新,但如果流还没关闭,必须手动调用 flush() 才能把数据写入文件,否则会出现数据丢失。

3. 乱码问题:必须指定编码

  • FileReader/FileWriter 默认使用平台编码(Windows 是 GBK,Linux 是 UTF-8),跨平台读写容易乱码。
  • 解决:用 InputStreamReader/OutputStreamWriter 显式指定编码,或者使用 Files.readAllLines(path, StandardCharsets.UTF_8)

4. 序列化注意事项

  • 序列化的类必须实现 Serializable 接口,否则会抛出 NotSerializableException
  • 建议显式定义 serialVersionUID,避免类修改后反序列化失败。
  • transient 修饰的字段不会被序列化,适合临时字段或敏感数据。

5. 大文件处理:必须用缓冲流

直接用 FileInputStream 读写大文件,会频繁触发 IO 操作,效率极低,必须用 BufferedInputStream/BufferedOutputStream 包装,或者用 Files.copy()

五、NIO 补充(进阶)

Java 1.4 引入的 NIO(New IO)是同步非阻塞 IO,核心是通道(Channel)、缓冲区(Buffer)、选择器(Selector),比传统 IO 效率更高,适合高并发网络编程。

  • 传统 IO:面向流、阻塞、单线程处理一个连接。
  • NIO:面向缓冲区、非阻塞、单线程处理多个连接。
java 复制代码
// NIO文件复制示例
public static void copyFileWithNIO(String srcPath, String destPath) throws IOException {
    try (FileChannel inChannel = new FileInputStream(srcPath).getChannel();
         FileChannel outChannel = new FileOutputStream(destPath).getChannel()) {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (inChannel.read(buffer) != -1) {
            buffer.flip(); // 切换为读模式
            outChannel.write(buffer);
            buffer.clear(); // 清空缓冲区,切换为写模式
        }
    }
}

补充:

模型 全称 阻塞? 线程模式 核心特点
BIO 阻塞 IO 阻塞 1 线程 1 连接 排队死等,低效
NIO 同步非阻塞 IO 非阻塞 1 线程多连接 轮询多路复用
AIO 异步非阻塞 IO 异步 回调通知 系统干完主动通知

六、总结:怎么选择流?

场景 推荐流 理由
处理文本文件 BufferedReader/BufferedWriter 按行读写,效率高,支持编码
处理二进制文件(图片 / 视频) BufferedInputStream/BufferedOutputStream 字节流处理所有数据类型,缓冲提升效率
对象序列化 ObjectOutputStream/ObjectInputStream 直接读写对象,方便持久化
解决乱码 InputStreamReader/OutputStreamWriter 可指定编码,适配不同文本文件
高并发网络编程 NIO 通道 / 缓冲区 非阻塞模型,支持多连接
相关推荐
skywalk81632 小时前
编程里碰到的柯里化是什么意思?
开发语言
lly2024062 小时前
Julia 正则表达式
开发语言
历程里程碑2 小时前
MySQL数据类型全解析 + 代码实操讲解
大数据·开发语言·数据库·sql·mysql·elasticsearch·搜索引擎
ZC跨境爬虫2 小时前
Python Django开发者转向微信小程序:从架构理解到第一行代码的完整准备指南
开发语言·python·ui·微信小程序·django
沐知全栈开发2 小时前
Eclipse 首选项(Preferences)详解
开发语言
Rust研习社2 小时前
Weak 弱引用:如何用 Weak 打破 Rc 与 Arc 的循环引用
开发语言·后端·rust
iCxhust2 小时前
在 emu8086 中可以直接编译运行的完整汇编程序,演示数组的定义、遍历、求和、求最大值。
开发语言·前端·javascript·汇编·单片机·嵌入式硬件·算法
Brilliantwxx2 小时前
【C++】认识vector(概念+题目OJ)
开发语言·c++·笔记·算法
逻辑驱动的ken2 小时前
Java高频面试考点场景题22
java·开发语言·jvm·面试·职场和发展·求职招聘·春招