Java IO 流详解:字符流(Reader/Writer)与字符编码那些事 - 实践

[an error occurred while processing the directive]

在 Java IO 体系中,流按处理数据类型可分为字节流和字符流。字节流以字节为单位处理数据(如图片、视频等二进制文件),而字符流以字符为单位处理数据(如文本文件),其核心优势是能直接处理 Unicode 字符,并解决了字节流处理文本时的编码转换问题。

本文将深入解析字符流的核心组件:Reader/Writer 抽象类、FileReader/FileWriter 实现类,以及开发中绕不开的字符编码(UTF-8/GBK)问题,配合实例代码和图解,让你彻底搞懂字符流的工作原理。

一、字符流的核心:Reader 与 Writer 抽象类字符流的顶层是两个抽象类:java.io.Reader(输入字符流)和 java.io.Writer(输出字符流)。它们定义了字符流的基本操作规范,所有字符流实现类都直接或间接继承这两个类。

1.1 Reader 抽象类:字符输入的 "模板"Reader 是所有字符输入流的父类,其核心方法用于从数据源读取字符:

方法签名功能说明int read()读取单个字符,返回字符的 Unicode 编码(0-65535),若已到达流末尾则返回 -1int read(char[] cbuf)读取字符到数组 cbuf 中,返回实际读取的字符数,末尾返回 -1int read(char[] cbuf, int off, int len)读取最多 len 个字符到数组 cbuf 中,从索引 off 开始存储void close()关闭流,释放资源(必须调用,否则可能导致资源泄露)注意:read() 方法是阻塞式的,若没有数据可读会一直等待。

1.2 Writer 抽象类:字符输出的 "模板"Writer 是所有字符输出流的父类,核心方法用于向目标写入字符:

方法签名功能说明void write(int c)写入单个字符(取 c 的低 16 位,因为 Unicode 字符占 2 字节)void write(char[] cbuf)写入字符数组 cbufvoid write(char[] cbuf, int off, int len)写入数组 cbuf 中从 off 开始的 len 个字符void write(String str)写入字符串 strvoid write(String str, int off, int len)写入字符串 str 中从 off 开始的 len 个字符void flush()刷新缓冲区,将数据立即写入目标(字符流默认有缓冲区,需手动刷新或关闭时自动刷新)void close()关闭流,关闭前会自动调用 flush()1.3 字符流与字节流的本质区别字符流的底层依然是字节流,但它多了一层编码 / 解码处理:

读取时:字节流读取字节 → 字符流通过编码表将字节转换为字符(解码)写入时:字符流将字符通过编码表转换为字节 → 字节流写入字节(编码)用一张图直观展示两者的关系:

二、文件字符流:FileReader 与 FileWriterFileReader 和 FileWriter 是 Reader/Writer 的直接实现类,专门用于读写文本文件(如 .txt、.java 等)。

2.1 FileReader:读取文件中的字符FileReader 继承自 InputStreamReader,它通过系统默认编码将文件字节转换为字符。构造方法如下:

// 根据文件路径创建FileReader

FileReader(String fileName) throws FileNotFoundException

// 根据File对象创建FileReader

FileReader(File file) throws FileNotFoundException

使用示例:读取文本文件内容

import java.io.FileReader;

import java.io.IOException;

public class FileReaderDemo {

public static void main(String[] args) {

// try-with-resources语法:自动关闭流,无需手动调用close()

try (FileReader reader = new FileReader("test.txt")) {

char[] buffer = new char[1024]; // 字符缓冲区,提高效率

int len;

// 循环读取,len为实际读取的字符数

while ((len = reader.read(buffer)) != -1) {

// 将字符数组转换为字符串输出

System.out.print(new String(buffer, 0, len));

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

2.2 FileWriter:向文件写入字符FileWriter 继承自 OutputStreamWriter,通过系统默认编码将字符转换为字节写入文件。构造方法如下:

// 根据文件路径创建FileWriter(覆盖写入)

FileWriter(String fileName) throws IOException

// 根据File对象创建FileWriter(覆盖写入)

FileWriter(File file) throws IOException

// 追加写入(第二个参数为true)

FileWriter(String fileName, boolean append) throws IOException

FileWriter(File file, boolean append) throws IOException

使用示例:向文件写入内容

import java.io.FileWriter;

import java.io.IOException;

public class FileWriterDemo {

public static void main(String[] args) {

try (FileWriter writer = new FileWriter("output.txt", true)) { // 追加模式

String content = "Hello, 字符流!\n";

writer.write(content); // 写入字符串

writer.flush(); // 手动刷新缓冲区(close()会自动刷新)

System.out.println("写入成功");

} catch (IOException e) {

e.printStackTrace();

}

}

}

2.3 注意事项默认编码问题:FileReader/FileWriter 使用系统默认编码(可通过 System.getProperty("file.encoding") 获取),跨平台时可能导致乱码(如 Windows 默认 GBK,Linux 默认 UTF-8)。缓冲区刷新:Writer 有缓冲区,若未调用 flush() 或 close(),数据可能留在缓冲区未写入文件。资源释放:务必关闭流,推荐使用 try-with-resources 语法(Java 7+),自动释放资源。三、字符编码:UTF-8/GBK 与乱码根源字符编码是字符流的核心知识点,也是乱码问题的根源。理解编码规则,才能避免在文件读写时出现乱码。

3.1 什么是字符编码?字符编码是一套规则,定义了字符(如 'A'、' 中 ')与字节(0-255)的映射关系。计算机只能存储字节,因此需要通过编码将字符转换为字节(写入时),或通过解码将字节转换为字符(读取时)。

常见编码方式:

ASCII:美国标准码,仅包含英文字母、数字和符号,1 字节表示(0-127)。GBK:国家标准扩展码,支持中文字符,中文占 2 字节,英文占 1 字节(兼容 ASCII)。UTF-8:Unicode 的可变长度编码,英文占 1 字节,中文占 3 字节,支持全球所有字符。3.2 乱码的产生原因乱码的本质是编码与解码使用的规则不一致。例如:

用 UTF-8 编码写入的 "中" 字(占 3 字节),若用 GBK 解码(按 2 字节解析),会得到错误字符。用 GBK 编码写入的 "中" 字(占 2 字节),若用 UTF-8 解码(按 3 字节解析),会因字节不足导致乱码。用示意图展示乱码过程:

3.3 如何指定编码读写文件?FileReader/FileWriter 的缺陷是无法指定编码(只能用系统默认),解决办法是使用 InputStreamReader 和 OutputStreamWriter(字符流与字节流的桥梁),它们可以指定编码。

示例:用 UTF-8 编码读写文件

import java.io.*;

public class EncodingDemo {

public static void main(String[] args) {

// 写入:指定UTF-8编码

try (OutputStreamWriter writer = new OutputStreamWriter(

new FileOutputStream("utf8.txt"), "UTF-8")) {

writer.write("你好,UTF-8!");

} catch (IOException e) {

e.printStackTrace();

}

// 读取:指定UTF-8编码

try (InputStreamReader reader = new InputStreamReader(

new FileInputStream("utf8.txt"), "UTF-8")) {

char[] buffer = new char[1024];

int len;

while ((len = reader.read(buffer)) != -1) {

System.out.print(new String(buffer, 0, len)); // 输出:你好,UTF-8!

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

注意:编码名称需严格匹配(如 "UTF-8" 而非 "utf8"),否则会抛出 UnsupportedEncodingException。

四、总结与最佳实践字符流适用场景:处理文本数据(如 .txt、.csv、.java),避免字节流处理文本时的手动编码转换。核心类关系:Reader/Writer 是抽象父类,FileReader/FileWriter 是文件处理实现类,InputStreamReader/OutputStreamWriter 是带编码控制的转换流。编码最佳实践:

优先使用 UTF-8 编码(跨平台兼容性好)。避免使用 FileReader/FileWriter(默认编码易导致乱码),推荐用 InputStreamReader/OutputStreamWriter 并显式指定编码。读写同一文件时,确保编码一致。资源管理:始终用 try-with-resources 语法关闭流,避免资源泄露。掌握字符流的核心原理和编码规则,能让你在处理文本文件时游刃有余,远离乱码烦恼。下一篇将讲解缓冲字符流(BufferedReader/BufferedWriter),进一步提升 IO 效率。

[an error occurred while processing the directive]
Copyright © 2088 1990年世界杯_世界杯球赛 - xqgjmy.com All Rights Reserved.
友情链接