字符流 = 字节流 + 翻译官。字节流只管搬字节、看不懂内容;字符流读写时自动查编码表做"字节 ↔ 字符"转换——文本看得懂了,但只能碰文本。本节四件套:编码表常识、FileWriter/FileReader 写读、转换流指定编码、flush 与 close 的区别。
1. 引入:为什么需要翻译官
10.2 的实验三还记着:字节流逐字节读中文,打印出来一坨乱码。原因很简单——
- UTF-8 编码:一个汉字占 3 个字节;GBK 编码:一个汉字占 2 个字节。
- 字节流一次搬 1 个字节,等于把一个汉字拦腰截断,拿着"半个字"去查表——查出来的自然不是人话。
字符流的解法:读的时候按编码表把字节拼成字符再给你,写的时候把字符按编码表拆成字节再落盘。翻译的活儿它包了,你只跟"字"打交道。
乱码 ≠ 文件坏了。文件里的字节原封没动,坏的是"翻译"这一步——用什么编码写,就得用什么编码读。UTF-8 写、GBK 读,必乱;反过来也乱。所有乱码问题,先查两边编码是否一致。
2. 字符流家族:两大家 + 桥梁
写字用 Writer 家,读用 Reader 家(注意命名:都是 er 结尾,字节流是 Stream 结尾):
- OutputStreamWriter / InputStreamReader:转换流——字符流通向字节流的桥梁,可以指定编码。
- FileWriter / FileReader:转换流的便捷子类,用默认编码(Windows 中文系统默认 GBK,IDEA 里常设 UTF-8)。
日常写小文本用便捷款;要指定编码(比如明确存成 UTF-8),就套一层转换流:
Writer w = new OutputStreamWriter(new FileOutputStream("b.txt"), "UTF-8");
3. 写文本:FileWriter
FileWriter fw = new FileWriter("test\\c.txt"); // 三保命规矩同字节流:自动建/清空/true 追加
fw.write("波仔学 IO"); // 直接写字符串!不用 getBytes 了
fw.write("\r\n"); // 换行照旧
fw.write(98); // 写单个字符(按码表:98 = b)
fw.close(); // 关门(内含冲洗)
注意 write(int) 在字符流里的含义变了:字节流写的是"原始字节 97",字符流写的是"码表中 97 号字符"——翻译官上岗,同一句代码含义升级。
flush 与 close:冲一把 vs 关门
flush():把缓冲区里的字冲到硬盘上,流还能继续用——长文本边写边冲,防断电丢稿。close():先冲一把,再关门,之后流不能再用了。- 不 flush 也不 close → 数据滞在缓冲区,文件是空的。字符流尤其敏感(内置缓冲),字节流好歹直接写,字符流不关门等于白写。
4. 读文本:FileReader
FileReader fr = new FileReader("test\\c.txt");
int ch;
while ((ch = fr.read()) != -1) { // 一次读一个"字符",-1 哨兵照旧
System.out.print((char) ch); // 中文不乱码了!
}
fr.close();
小推车版(读得快,还能按段处理):
char[] cart = new char[1024];
int len;
while ((len = fr.read(cart)) != -1) {
System.out.print(new String(cart, 0, len));
}
5. 收网旧账
- 10.2 字节流:三保命规矩原样继承(自动建/清空/true 追加)、-1 哨兵、小推车——套路全通用,只是单位从"字节"换成"字符"。
- 10.1 File 类:路标继续指路,
new FileWriter(File对象)同款构造。 - 6.2 字符串类:字符流里
write(String)直接吃字符串——不再需要getBytes(),翻译官替你拆。 - 5.3 异常处理:IOException 照旧受检,close 照旧放 finally / try-with-resources。
- 10. 🔵 计算机基础/02 信息的数字化表示:当年学的"字符靠编码表变数字",今天正式用上了——GBK、UTF-8 就是两张不同的对照表。
6. 动手:Demo07 写中文、读中文、验乱码
import java.io.FileWriter;
import java.io.FileReader;
import java.io.IOException;
public class Demo07 {
public static void main(String[] args) throws IOException {
// 一、写中文(不用 getBytes,直接写字符串)
FileWriter fw = new FileWriter("story.txt");
fw.write("波仔在第十章学会了字符流\r\n");
fw.write("翻译官上岗,乱码退散");
fw.close();
// 二、读中文(-1 哨兵 + 小推车)
FileReader fr = new FileReader("story.txt");
char[] cart = new char[1024];
int len;
while ((len = fr.read(cart)) != -1) {
System.out.print(new String(cart, 0, len));
}
fr.close();
// 三、指定编码写(转换流上岗)
java.io.Writer w = new java.io.OutputStreamWriter(
new java.io.FileOutputStream("utf8.txt"), "UTF-8");
w.write("这份文件保证是 UTF-8 编码");
w.close();
}
}
三个 30 秒实验:
- 写完不 close 也不 flush,直接看 story.txt——空的!数据还压在缓冲区里。加上 flush 再看,有了而且流还能继续写。
- 用记事本"另存为"分别选 ANSI(GBK) 和 UTF-8 存两个文件,再分别用程序读——编码对上就正常,对不上就乱码。亲眼验证乱码铁律。
- 拿字符流去复制一张 jpg(read/write 走一遍)——副本打不开!字符流翻译时字节被重新分组,二进制文件当场损坏。复制活儿永远归字节流。
7. 常见误区
- ❌ 用字符流复制图片/视频 —— 翻译过程会破坏原始字节,副本损坏。字符流只碰文本,搬运一律字节流。
- ❌ 写完不 close 不 flush 就去开文件 —— 字符流带内置缓冲,不冲不关文件就是空的。
- ❌ 给 FileWriter 传编码参数 —— 它没有这个构造!要指定编码请用转换流
OutputStreamWriter(new FileOutputStream(...), "UTF-8")。 - ❌ 文件乱码就以为写坏了 —— 文件没坏,读写编码不一致而已。统一两边编码,乱码自愈。
- ❌ 以为字符流更快所以无脑用 —— 字符流多一道翻译工序,二进制反而更慢还危险。选型口诀:看内容还是看字节?人看文本用字符流,机器搬东西用字节流。
8. 可折叠自测
UTF-8 一个汉字 3 字节,GBK 2 字节。乱码根因:读写编码不一致,字节被按错误的表分组翻译——文件本身没坏。
flush 只把缓冲区冲到硬盘,流还能继续用(适合长文本边写边冲);close 先冲再关,流不能再用(收工必调)。不冲不关文件可能是空的。
用默认编码写小文本 → FileWriter 便捷款;要明确指定编码(如存成 UTF-8)→ 转换流套字节流:new OutputStreamWriter(new FileOutputStream(...), "编码")。
字节流写的是原始字节 97;字符流查码表写的是"97 号字符"(小写 b)编码后的字节。同一句代码,翻译官上岗后语义升级。
try (Reader r = new InputStreamReader(new FileInputStream("old.txt"), "GBK");
Writer w = new OutputStreamWriter(new FileOutputStream("new.txt"), "UTF-8")) {
char[] cart = new char[1024];
int len;
while ((len = r.read(cart)) != -1) {
w.write(cart, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
写出来,你就掌握了"乱码修复"的正确姿势——业内叫转码,本质就是换个表重新翻译。
9. 下一步学什么
- ☑ 10.4 转换流 ✅ —— 点此复习:第十章 I-O/10.4 转换流
相关笔记
- 第十章 I-O/10.2 字节流:翻译官底下的搬运工——三保命规矩、-1 哨兵全部继承
- 第十章 I-O/10.1 File类:路标继续指路,流负责搬
- 第五章 异常/5.3 异常处理及语法:close 放 finally / try-with-resources 的老规矩
- 第六章 Java API/6.2 字符串类:write(String) 直接吃字符串,getBytes 退休
- 10. 🔵 计算机基础/02 信息的数字化表示:编码表的雏形课——字符怎么变成数字
- 第五章 异常/5.2 运行时异常与编译时异常:IOException 照旧受检