10 Java IO 流程入门

170 阅读10分钟

Java IO 流程入门:从概念到实践,手把手教你搞定文件读写

对于 Java 初学者来说,“IO” 这个词可能既熟悉又陌生 —— 我们知道它和 “数据传输” 有关,却常常被 “输入流”、“输出流”、“字节流”、“字符流”这些概念绕得晕头转向。其实,Java IO 的核心逻辑非常简单:就是实现 “数据源” 和 “程序” 之间的数据传递。今天这篇文章,我们会从最基础的概念讲起,用通俗的语言拆解 IO 流程,再通过实际代码示例,帮你彻底搞懂 Java IO 该怎么用。

一、先搞懂两个核心问题:IO 是什么?数据往哪流?

在开始学习流程前,我们必须先明确两个基础认知,这能帮你避免后续 90% 的 confusion(困惑)。

1. IO 的本质:数据的 “搬运工”

IO 是 “Input/Output” 的缩写,翻译过来就是 “输入 / 输出”。它的核心作用,就像一个 “搬运工”—— 把数据从一个地方搬到另一个地方。

在 Java 程序中,数据的 “搬运场景” 主要有两种:

  • 从外部读数据到程序:比如读取本地文件里的文字、读取键盘输入的内容,这叫 “输入(Input)”;
  • 从程序写数据到外部:比如把程序里的内容保存到本地文件、把结果打印到控制台,这叫 “输出(Output)”。

简单记: “读进来” 是输入,“写出去” 是输出,参照物永远是 “我们的 Java 程序”。

2. 流(Stream):数据的 “传输管道”

既然要 “搬运数据”,就需要一个 “管道”—— 这就是 Java 里的 “流(Stream)”。所有 IO 操作,本质上都是通过 “流” 来完成的:

  • 数据不会凭空出现在程序里,必须通过 “输入流” 一点点 “流” 进来;
  • 数据也不会凭空跑到文件里,必须通过 “输出流” 一点点 “流” 出去。

而且,Java 中的流是 “单向的”—— 一个流只能负责 “输入” 或 “输出”,不能同时干两件事。比如要读一个文件再写内容,就需要分别创建 “输入流” 和 “输出流”。

二、IO 流的两大分类:字节流和字符流

Java 的 IO 流有很多类,但本质上可以分为两大类:字节流(Byte Stream)字符流(Character Stream) 。初学者只要先掌握这两类的区别,就能避开大部分坑。

1. 字节流:处理 “所有数据” 的通用管道

字节流以 “字节(Byte)” 为单位传输数据(1 个字节 = 8 个二进制位),它的特点是 “万能”——任何数据(文本、图片、音频、视频)都可以用字节流处理

字节流的核心父类有两个(记住这两个,其他子类都是基于它们扩展的):

  • InputStream:所有输入字节流的 “爸爸”,负责从外部读字节到程序;
  • OutputStream:所有输出字节流的 “爸爸”,负责从程序写字节到外部。

比如我们要读写本地文件,最常用的字节流子类就是 FileInputStream(读文件)和 FileOutputStream(写文件)。

2. 字符流:专门处理 “文本数据” 的高效管道

字符流以 “字符(Character)” 为单位传输数据,它的特点是 “针对文本优化”——只用来处理文本文件(如.txt、.java 文件) ,因为它会自动处理 “字符编码”(比如 UTF-8、GBK),避免出现中文乱码问题。

字符流的核心父类也有两个:

  • Reader:所有输入字符流的 “爸爸”,负责从外部读字符到程序;
  • Writer:所有输出字符流的 “爸爸”,负责从程序写字符到外部。

同样,处理本地文本文件时,常用的子类是 FileReader(读文本)和 FileWriter(写文本)。

3. 怎么选?记住这个原则

很多初学者会纠结 “该用字节流还是字符流”,其实只要记住一句话:

  • 处理文本文件(.txt、.java 等)→ 优先用字符流(避免乱码,操作更方便);
  • 处理非文本文件(图片、视频、音频等)→ 必须用字节流(字符流无法处理)。

三、实战:手把手教你走一遍 IO 流程(以文件读写为例)

理论讲完,我们用最常见的 “文件读写” 场景,带你实战一遍 IO 的完整流程。这里会分别演示 “字符流读文本文件” 和 “字节流写文本文件”(覆盖核心场景),步骤都是通用的,学会后可以迁移到其他 IO 场景。

场景 1:用字节流读本地文本文件(FileInputStream)

需求:读取电脑上 D:\test.txt 文件里的内容,并打印到控制台。

完整流程(4 步):
  1. 创建流对象:创建FileInputStream对象,绑定要读取的文件路径;
  2. 读取数据:循环读取,将字节读入缓冲区,直到文件末尾;
  3. 处理数据:字节数组→字符串;
  4. 关闭流资源:用完流后必须关闭,释放电脑资源(非常重要!)。
代码示例(带详细注释):
import java.io.FileInputStream;
import java.io.IOException;

public class ReadFileDemo {

    public static void main(String[] args) {
        // 1. 声明字节流对象(放在try外面,确保finally能访问)
        FileInputStream fis = null;
        try {
            // 绑定要读取的文件路径(Windows路径需双反斜杠)
            fis = new FileInputStream("D:\test.txt");

            // 2. 读取数据:字节数组作为缓冲区(提高读取效率,避免逐个字节读取)
            byte[] buffer = new byte[1024]; // 缓冲区大小1KB(可根据文件大小调整)
            int readLen; // 每次实际读取的字节数(-1表示文件末尾)

            // 循环读取:将字节读入缓冲区,直到文件末尾
            while ((readLen = fis.read(buffer)) != -1) {
                // 3. 处理数据:字节数组→字符串(关键!需指定文件的编码,避免乱码)
                // 注意:要使用文件实际的编码(如UTF-8、GBK),否则会乱码
                String content = new String(buffer, 0, readLen, "UTF-8");
                System.out.print(content);
            }

        } catch (IOException e) {
            // 捕获IO异常(文件不存在、编码错误、权限不足等)
            e.printStackTrace();
        } finally {
            // 4. 关闭流资源(必须执行,释放系统资源)
            try {
                if (fis != null) { // 避免空指针异常(流创建失败时fis为null)
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

场景 2:用字节流写内容到本地文件(FileOutputStream)

需求:在电脑上 D:\output.txt 文件中,写入 “Hello Java IO!”(文件不存在则自动创建)。

完整流程(4 步):
  1. 创建流对象:创建FileOutputStream对象,绑定要写入的文件路径;
  2. 写入数据:通过流对象的write()方法把数据写入文件;
  3. 刷新流:确保数据从内存写入文件(避免数据滞留);
  4. 关闭流资源:释放资源,与读文件逻辑一致。
代码示例(带详细注释):
import java.io.FileOutputStream;
import java.io.IOException;

public class WriteFileDemo {

    public static void main(String[] args) {
        // 1. 声明流对象
        FileOutputStream outputStream = null;
        try {
            // 绑定文件路径(true表示“追加内容”,默认不写则覆盖原内容)
            outputStream = new FileOutputStream("D:\output.txt", true);
            // 2. 写入数据:字符串转字节数组(字节流仅支持字节传输)
            String content = "Hello Java IO!\n"; // \n表示换行
            byte[] contentBytes = content.getBytes(); // 字符串转字节数组
            outputStream.write(contentBytes); // 写入文件
            // 3. 刷新流:确保数据写入磁盘(部分流关闭时自动刷新,手动写更安全)
            outputStream.flush();
            System.out.println("写入成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流:关闭时会自动刷新,可省略手动flush()
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

### 小技巧:用 try-with-resources 简化代码(JDK 7+)

上述代码中 “关闭流” 的逻辑较繁琐且易遗漏,JDK 7 + 提供的try-with-resources语法可自动关闭流(无需写 finally),代码更简洁:

package primary;

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

public class ReadFileSimpler {

    public static void main(String[] args) {
        // 流对象放在try括号中,程序结束后自动关闭
        try (FileReader reader = new FileReader("D:\test.txt")) {
            int readChar;
            while ((readChar = reader.read()) != -1) {
                System.out.print((char) readChar);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 无需手动关闭流!
    }

}

### 场景 3:字符流写文本文件(FileWriter)

需求:向 D:\textWrite.txt 写入中文文本,支持追加内容,避免乱码。

完整流程(4 步):
  1. 创建流对象:实例化 FileWriter,绑定目标文件路径,指定 “追加模式”。
  2. 写入数据:通过 write() 方法写入字符串、字符数组或指定长度的字符串。
  3. 刷新流:显式调用 flush(),确保内存中的数据写入磁盘(字符流必需)。
  4. 关闭流:用 try-with-resources 自动关闭,释放资源(无需手动写 finally)。
代码示例(带步骤对应注释):
import java.io.FileWriter;
import java.io.IOException;

public class CharWriteDemo {
    public static void main(String[] args) {
        // 步骤1:创建流对象(try-with-resources 自动关闭,绑定路径+追加模式)
        try (FileWriter writer = new FileWriter("D:\textWrite.txt", true)) {
            // 步骤2:写入数据——3种常用方式
            // 方式1:直接写完整字符串(字符流专属优势,无需转字节)
            String content1 = "Java 字符流真方便,不会乱码!\n";
            writer.write(content1);

            // 方式2:写入字符数组(适合批量处理固定字符)
            char[] charArr = {'字', '符', '数', '组', '写', '入', '\n'};
            writer.write(charArr);

            // 方式3:写入字符串的部分内容(参数:原字符串、起始索引、写入长度)
            String content2 = "选择性写入指定内容";
            writer.write(content2, 2, 5); // 从索引2开始,取5个字符:"性写入指定"

            // 步骤3:刷新流——强制内存数据写入磁盘,避免数据滞留
            writer.flush();
            System.out.println("字符流写入成功!");

        } catch (IOException e) {
            // 捕获IO异常(文件权限不足、路径错误等)
            e.printStackTrace();
        }
        // 步骤4:关闭流——try-with-resources 自动执行,无需手动调用close()
    }
}

场景 4:字符流批量读文本文件(FileReader)

需求:高效读取 D:\textWrite.txt,每次批量读取 1024 个字符,避免单个读取的低效问题。

完整流程(4 步):
  1. 创建流对象:实例化 FileReader,绑定要读取的文件路径。
  2. 准备缓冲区:定义字符数组作为 “缓冲区”,指定每次读取的最大字符数。
  3. 批量读取 + 处理:循环调用 read(char[] buf) 读取数据,转成字符串并打印。
  4. 关闭流try-with-resources 自动关闭,释放文件资源。
代码示例(带步骤对应注释):
import java.io.FileReader;
import java.io.IOException;

public class CharBatchReadDemo {
    public static void main(String[] args) {
        // 步骤1:创建流对象(绑定读取文件路径,自动关闭)
        try (FileReader reader = new FileReader("D:\textWrite.txt")) {
            // 步骤2:准备缓冲区——字符数组,每次最多读1024个字符(可调整大小)
            char[] buf = new char[1024];
            int readLen; // 存储每次实际读取的字符数(-1表示文件末尾)

            // 步骤3:批量读取+处理数据——循环读取直到文件末尾
            while ((readLen = reader.read(buf)) != -1) {
                // 把缓冲区的字符转成字符串(仅取实际读取的长度,避免冗余空格)
                String content = new String(buf, 0, readLen);
                System.out.println("本次读取" + readLen + "个字符,内容:" + content);
            }

        } catch (IOException e) {
            // 捕获异常(文件不存在、读取权限不足等)
            e.printStackTrace();
        }
        // 步骤4:关闭流——try-with-resources 自动执行,无需手动处理
    }
}

## 四、初学者常见问题:避开这些坑

1. 文件路径错误导致 “文件找不到” 异常

  • 绝对路径:需写完整路径,如D:\test.txt(Windows 系统反斜杠需写两个,或用正斜杠D:/test.txt);
  • 相对路径:文件在项目根目录时,直接写文件名即可(如test.txt),无需完整路径。

2. 忘记关闭流导致资源泄漏

  • 流是 “稀缺资源”,不关闭会占用内存和文件句柄,长期可能导致程序卡顿 / 崩溃;
  • 推荐用try-with-resources语法,自动关闭流,避免手动遗漏。

3. 中文乱码问题

  • 字节流读文本时可能乱码(字节流不处理编码);
  • 解决方案:优先用字符流(FileReader/FileWriter),或创建流时指定编码(如new InputStreamReader(new FileInputStream("test.txt"), "UTF-8"))。

五、总结:IO 流程的核心逻辑

无论读文件、写文件还是网络传输,Java IO 的核心流程永远是 “3 步”:

  1. 打开流:创建流对象,绑定数据源(读)或目标地址(写);
  2. 操作流:通过流对象的方法读取 / 写入数据;
  3. 关闭流:释放资源(必须执行!)。

“字节流” 与 “字符流” 的选择仅需根据数据类型判断:文本用字符流,非文本用字节流。

对初学者而言,无需记忆所有 IO 类,先练熟 “文件读写” 流程、理解 “流” 的本质,后续学习缓冲流、对象流等场景会更轻松。现在就动手试试上述代码吧 —— 实践才是最好的学习方式!