字符流
概述
字符流以字符(char)为单位读写数据,专门用于文本的输入输出。Reader 与 Writer 是字符流的两个抽象基类:前者表示字符输入源,后者表示字符输出目标。与 字节流 不同,字符流在读写时会按字符编码(如 UTF-8、GBK)进行解码或编码,因此适合处理文本文件;若处理图片、音频等二进制数据,应使用字节流。
掌握字符流后,可配合 缓冲流 中的 BufferedReader/BufferedWriter 实现按行读写和更高性能。
字符流体系概览
| 类型 | 常用实现类 | 说明 |
|---|---|---|
| Reader | FileReader、InputStreamReader、BufferedReader、StringReader | 从文件、字节流(可指定编码)、缓冲、字符串等读取字符 |
| Writer | FileWriter、OutputStreamWriter、BufferedWriter、StringWriter | 向文件、字节流(可指定编码)、缓冲、字符串等写入字符 |
- 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) |
说明
字符流的 read、write 会抛出受检异常 IOException,必须用 try-catch 捕获或在方法上 throws IOException。推荐用 try-with-resources 自动关闭流。详见 异常处理。
使用示例
示例 1:用 FileReader 读取文本文件(逐字符)
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:按字符数组读取(推荐)
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 写入文本文件
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 可避免该问题:
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」显式指定编码:
// 按 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)
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。 |
相关链接
- File 类与文件操作 — 路径与文件元数据,字符流读写前常需配合使用
- 字节流 — InputStream/OutputStream,处理二进制或按字节读写
- 缓冲流与包装流 — BufferedReader/BufferedWriter,按行读取与性能优化
- 异常处理 — try-catch、throws、try-with-resources
- Oracle Java 教程 - Character Streams