Skip to content

字符流

概述

字符流字符(char)为单位读写数据,专门用于文本的输入输出。ReaderWriter 是字符流的两个抽象基类:前者表示字符输入源,后者表示字符输出目标。与 字节流 不同,字符流在读写时会按字符编码(如 UTF-8、GBK)进行解码或编码,因此适合处理文本文件;若处理图片、音频等二进制数据,应使用字节流。

掌握字符流后,可配合 缓冲流 中的 BufferedReader/BufferedWriter 实现按行读写和更高性能。


字符流体系概览

类型常用实现类说明
ReaderFileReaderInputStreamReaderBufferedReaderStringReader从文件、字节流(可指定编码)、缓冲、字符串等读取字符
WriterFileWriterOutputStreamWriterBufferedWriterStringWriter向文件、字节流(可指定编码)、缓冲、字符串等写入字符
  • FileReader / FileWriter:直接绑定文件,使用系统默认编码(与平台或 JVM 默认有关),读写简单文本时最常用。
  • InputStreamReader / OutputStreamWriter:在字节流之上做「字节 → 字符」或「字符 → 字节」的转换,可显式指定字符集(如 StandardCharsets.UTF_8),适合需要明确编码的场景。

说明

若需严格控制文本文件的编码(如统一使用 UTF-8),推荐使用 InputStreamReader/OutputStreamWriter 或 JDK 11+ 的 FileReader(path, Charset)/FileWriter(path, Charset),避免因默认编码不同导致乱码。详见下文「指定编码读写」。


基本用法

Reader 核心方法

方法说明
int read()读取一个字符,返回 0–65535;若已到末尾返回 -1
int read(char[] cbuf)将字符读入数组 cbuf,返回实际读取的字符数;末尾返回 -1
int read(char[] cbuf, int off, int len)将最多 len 个字符读入 cbuf,从 off 开始存放;末尾返回 -1
void close()关闭流并释放资源,必须调用

Writer 核心方法

方法说明
void write(int c)写出一个字符(低 16 位有效)
void write(char[] cbuf)写出整个字符数组
void write(char[] cbuf, int off, int len)写出数组 cbuf 中从 off 起的 len 个字符
void write(String str)写出整个字符串(Writer 独有,便于文本处理)
void write(String str, int off, int len)写出字符串的指定子串
void flush()刷新缓冲,确保数据写入底层流或文件
void close()关闭流并释放资源,必须调用(会先执行 flush)

说明

字符流的 readwrite 会抛出受检异常 IOException,必须用 try-catch 捕获或在方法上 throws IOException。推荐用 try-with-resources 自动关闭流。详见 异常处理


使用示例

示例 1:用 FileReader 读取文本文件(逐字符)

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

public class ReadTextByChar {
    public static void main(String[] args) {
        // try-with-resources 确保流在结束时自动关闭
        try (FileReader reader = new FileReader("data/hello.txt")) {
            int c;
            while ((c = reader.read()) != -1) {
                // c 为字符的 Unicode 码点范围,可直接转为 char 输出
                System.out.print((char) c);
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
    }
}

逐字符 read() 适合小文本或仅需「扫一遍」的场景;大文件更推荐按字符数组读取或使用 BufferedReader 按行读取。

示例 2:按字符数组读取(推荐)

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

public class ReadTextByBuffer {
    public static void main(String[] args) {
        char[] buffer = new char[4096];  // 按块读减少系统调用
        try (FileReader reader = new FileReader("data/hello.txt")) {
            int len;
            while ((len = reader.read(buffer)) != -1) {
                // len 为本轮实际读到的字符数
                String chunk = new String(buffer, 0, len);
                System.out.print(chunk);
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
    }
}

示例 3:用 FileWriter 写入文本文件

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

public class WriteTextDemo {
    public static void main(String[] args) {
        // 第二个参数 true 表示追加,默认 false 会覆盖原文件
        try (FileWriter writer = new FileWriter("output/result.txt", false)) {
            writer.write("Hello, 世界!\n");
            writer.write("第二行内容");
            writer.flush();  // 可选:在 close 前强制刷新到磁盘
        } catch (IOException e) {
            System.err.println("写入失败: " + e.getMessage());
        }
    }
}

提示

Writer 支持直接 write(String str),比字节流写文本更直观;大段输出时也可先拼好字符串再一次性写入。

示例 4:指定编码读写(避免乱码)

当文件是 UTF-8 而系统默认编码为 GBK 时,若直接用 FileReader 可能乱码。使用 InputStreamReader/OutputStreamWriter 并指定 Charset 可避免该问题:

java
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReadWithCharset {
    public static void main(String[] args) {
        Path path = Path.of("data/utf8.txt");
        // 用 Files.newBufferedReader 指定 UTF-8(JDK 8+),内部基于 InputStreamReader
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
    }
}

若使用「字节流 + InputStreamReader」显式指定编码:

java
// 按 UTF-8 解码文件内容
try (InputStreamReader isr = new InputStreamReader(
        new FileInputStream("data/utf8.txt"), StandardCharsets.UTF_8)) {
    char[] buf = new char[2048];
    int len;
    while ((len = isr.read(buf)) != -1) {
        System.out.print(new String(buf, 0, len));
    }
} catch (IOException e) {
    System.err.println("读取失败: " + e.getMessage());
}

版本说明

JDK 11 起,FileReader/FileWriter 提供了带 Charset 的构造方法,可直接指定编码,例如:new FileReader("data/utf8.txt", StandardCharsets.UTF_8)

示例 5:文本文件复制(字符流 + try-with-resources)

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

public class CopyTextDemo {
    public static void copy(String src, String dest) throws IOException {
        try (FileReader reader = new FileReader(src);
             FileWriter writer = new FileWriter(dest)) {
            char[] buffer = new char[8192];
            int len;
            while ((len = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, len);  // 只写出本轮读到的 len 个字符
            }
        }
    }

    public static void main(String[] args) {
        try {
            copy("data/source.txt", "output/copy.txt");
            System.out.println("复制完成");
        } catch (IOException e) {
            System.err.println("复制失败: " + e.getMessage());
        }
    }
}

注意

与字节流相同:read(buffer) 返回的 len 可能小于 buffer.length,写盘时必须用 write(buffer, 0, len),若写成 write(buffer) 会把数组中未读入的旧数据也写出。


何时用字符流、何时用字节流

场景推荐
文本文件(.txt、.json、.xml 等),需按字符或行处理字符流(Reader/Writer)
需要明确指定编码(如统一 UTF-8)InputStreamReader/OutputStreamWriter 或 JDK 11+ 的 FileReader/FileWriter(path, Charset)
图片、音频、视频、压缩包等二进制文件字节流(InputStream/OutputStream)
既读文本又关心原始字节(如协议解析)字节流,再按需用 InputStreamReader 解码

注意事项

注意

字符流操作会抛出受检异常 IOException(如文件不存在、无权限、编码错误)。必须用 try-catch 处理或在方法上 throws IOException,否则无法通过编译。

注意

必须关闭流。未关闭的流会占用文件句柄和系统资源。推荐使用 try-with-resources,在 try 块结束时自动调用 close()

易错点说明
read() 返回值read() 返回 int:0–65535 表示一个字符,-1 表示结束;不要当作 char 与 -1 比较时忽略类型。
write(buffer) 与 len按块读写时,写出时要用 write(buffer, 0, len),不要写出整个数组。
编码一致性读写的编码要与文件实际编码一致,否则出现乱码;跨平台或与前端对接时建议统一使用 UTF-8。
覆盖与追加FileWriter(path) 会覆盖原文件;FileWriter(path, true) 为追加模式。
flush 时机写完后若需立即在别的进程中读到,可在 close() 前调用 flush()close() 会先执行 flush。

相关链接

基于 VitePress 构建