【Java】Java IO 知识体系:从原理到实战,一文吃透流操作

大家好,我是云扬~ 今天来和大家深入聊聊 Java IO 这个基础又核心的知识体系。作为后端开发中数据读写的核心技术,IO 操作贯穿了文件处理、网络通信等诸多场景,但不少小伙伴会被字节流、字符流、各种流实现类搞得晕头转向。这篇文章就从基础概念出发,结合实际代码示例,带大家系统性掌握 Java IO 的核心知识点。

一、初识 Java IO:流是数据传输的桥梁

Java 通过流(Stream) 来处理 IO 操作,流就像一根连接程序与数据源(或目的地)的管道,数据以 “先进先出” 的方式在管道中传输。比如我们读取本地文件时,程序会开启一条通向文件的输入流;写入数据到数据库时,会开启一条通向数据库的输出流。

流有三个核心特性,大家一定要牢记:

  1. 先进先出:最先写入的数据会最先被读取,就像排队买票一样,顺序不能乱;
  2. 顺序存取:默认只能按顺序读写数据,无法直接跳转到中间位置(RandomAccessFile 是特例,后面会提到);
  3. 单向性:每个流要么是输入流(读数据),要么是输出流(写数据),不能同时兼具两种功能。

二、按传输方式划分:字节流 vs 字符流

Java IO 流最核心的分类就是按传输数据的单位,分为字节流字符流,两者的适用场景和核心类各不相同。

1. 字节流:处理所有二进制数据

字节流以 “字节(byte)” 为单位传输数据,适用于所有文件类型,尤其是图片、音频、视频等二进制文件。核心抽象类是 InputStream(输入字节流)和 OutputStream(输出字节流)。

核心方法示例

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamDemo {
    public static void main(String[] args) {
        // 字节流读取文件(输入流)
        try (FileInputStream fis = new FileInputStream("test.jpg")) {
            byte[] buffer = new byte[1024]; // 缓冲区,每次读1024字节
            int len;
            // read()方法返回读取的字节数,-1表示读取结束
            while ((len = fis.read(buffer, 0, buffer.length)) != -1) {
                System.out.println("读取到 " + len + " 字节数据");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 字节流写入文件(输出流)
        try (FileOutputStream fos = new FileOutputStream("test.txt")) {
            String content = "这是二进制文件的测试内容";
            byte[] data = content.getBytes();
            // 从数组0位置开始,写入全部字节
            fos.write(data, 0, data.length);
            fos.flush(); // 强制刷新缓冲区,确保数据写入
            System.out.println("数据写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里用到了try-with-resources语法,会自动关闭流,避免手动关闭遗漏导致的资源泄露,这是 Java 7 + 的推荐写法。

2. 字符流:专门处理文本文件

字符流以 “字符(char)” 为单位传输数据,本质是字节流 + 编码转换(如 UTF-8、GBK),适用于文本文件(.txt、.java 等)。核心抽象类是 Reader(输入字符流)和 Writer(输出字符流)。

核心方法示例

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharStreamDemo {
    public static void main(String[] args) {
        // 字符流读取文本文件
        try (FileReader fr = new FileReader("test.txt")) {
            char[] buffer = new char[1024];
            int len;
            // read()返回读取的字符数,-1表示结束
            while ((len = fr.read(buffer, 0, buffer.length)) != -1) {
                // 将字符数组转为字符串输出
                System.out.print(new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 字符流写入文本文件
        try (FileWriter fw = new FileWriter("output.txt")) {
            String content = "Java IO 字符流实战\n换行测试";
            // 写入字符数组的指定部分
            fw.write(content.toCharArray(), 0, content.length());
            fw.flush();
            System.out.println("\n文本写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:字符流处理文本时会自动处理编码问题,而字节流需要手动指定编码(后面转换流会讲到),这是两者的核心区别。

三、按操作对象划分:常用流实现类实战

除了传输方式,IO 流还可以按操作对象(数据源 / 目的地)分类,下面介绍开发中最常用的几种实现类及代码示例。

1. 文件操作:FileInputStream/FileOutputStream、FileReader/FileWriter

这是最基础的文件读写流,前面的示例已经用到,核心是操作本地文件。这里补充一个 “文件复制” 的完整示例(字节流,适用于所有文件):

public class FileCopyDemo {
    public static void main(String[] args) {
        String sourcePath = "source.pdf"; // 源文件路径
        String targetPath = "target.pdf"; // 目标文件路径

        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(targetPath)) {

            byte[] buffer = new byte[4096]; // 4KB缓冲区,提升效率
            int len;
            long startTime = System.currentTimeMillis();
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("文件复制完成,耗时:" + (endTime - startTime) + "ms");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. 缓冲流:BufferedInputStream/BufferedOutputStream、BufferedReader/BufferedWriter

缓冲流是对基础流的包装,通过缓冲区减少磁盘 IO 次数,大幅提升读写效率。开发中推荐优先使用缓冲流

字符缓冲流示例(带换行读取)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedStreamDemo {
    public static void main(String[] args) {
        // 缓冲字符流读取(支持按行读取)
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
            String line;
            // readLine()方法读取一行数据,返回null表示结束
            while ((line = br.readLine()) != null) {
                System.out.println("读取到一行:" + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 缓冲字符流写入(支持换行)
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("buffered_output.txt"))) {
            bw.write("第一行数据");
            bw.newLine(); // 换行操作,跨平台兼容
            bw.write("第二行数据");
            bw.flush();
            System.out.println("缓冲流写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 转换流:InputStreamReader/OutputStreamWriter

转换流是字节流和字符流的桥梁,用于处理编码转换。比如用指定编码(如 UTF-8)读取文本文件。

编码转换示例

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;

public class ConvertStreamDemo {
    public static void main(String[] args) {
        // 字节流 -> 字符流,指定UTF-8编码
        try (InputStreamReader isr = new InputStreamReader(
                new FileInputStream("utf8_file.txt"), "UTF-8")) {

            char[] buffer = new char[1024];
            int len;
            while ((len = isr.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 对象流:ObjectInputStream/ObjectOutputStream

对象流用于实现对象的序列化与反序列化,即将 Java 对象转换为字节流(序列化),或从字节流恢复为 Java 对象(反序列化)。

对象序列化示例

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.IOException;

// 序列化的类必须实现Serializable接口(标记接口,无抽象方法)
class User implements Serializable {
    private static final long serialVersionUID = 1L; // 序列化版本号,避免版本冲突
    private String name;
    private int age;

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

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

public class ObjectStreamDemo {
    public static void main(String[] args) {
        // 对象序列化:将User对象写入文件
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {

            User user = new User("云扬", 28);
            oos.writeObject(user);
            System.out.println("对象序列化成功");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 对象反序列化:从文件恢复User对象
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.ser"))) {

            User user = (User) ois.readObject();
            System.out.println("对象反序列化结果:" + user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意:序列化类必须实现Serializable接口,且类中的所有属性也需支持序列化(基本类型默认支持,引用类型需同样实现接口)。

四、IO 流使用核心注意事项

  1. 资源关闭:流操作完成后必须关闭,否则会导致资源泄露。推荐使用try-with-resources语法(Java 7+),自动关闭实现AutoCloseable接口的资源。
  2. 缓冲区刷新:输出流(尤其是缓冲流)需调用flush()方法,确保缓冲区数据全部写入目的地,避免数据丢失。
  3. 编码一致:字符流读写时需保证编码一致(如均使用 UTF-8),否则会出现乱码问题。
  4. 异常处理:IO 操作可能抛出IOException,必须进行捕获或声明抛出,避免程序崩溃。

总结

Java IO 知识体系的核心是 “流”,通过字节流处理所有二进制数据,字符流专门处理文本数据,再结合不同操作对象的流实现类,就能满足各种 IO 场景需求。掌握本文中的核心概念、常用类及代码示例,就能轻松应对开发中的 IO 操作问题~

如果大家有疑问或补充,欢迎在评论区留言交流!后续还会分享 Java IO 的进阶知识点(如 NIO),记得关注哦~

Tags:

发表回复

Your email address will not be published. Required fields are marked *.

*
*