📄 一句话概括
第十章 I/O 六节串一条线:File 管「在哪」,流管「怎么搬」——路标(10.1)→ 搬运工(10.2)→ 翻译官(10.3)→ 换词典(10.4)→ 打包快递(10.5),全家套娃,每层只干一件事。
六节串线:从路标到快递
| 节 | 角色 | 一句话职责 | 名场面 |
|---|---|---|---|
| 10.1 File | 路标 | 管"文件在哪、什么情况",不搬数据 | new File ≠ 创建文件;delete 不进回收站 |
| 10.2 字节流 | 搬运工 | 万能搬运,一个字节一个字节搬 | -1 哨兵;write(cart, 0, len) 复制图片成人礼 |
| 10.3 字符流 | 翻译官 | 专管文本,自动查编码表 | 乱码铁律:文件没坏,词典没对上 |
| 10.4 转换流 | 换词典的翻译官 | 指定编码读写字符 | GBK → UTF-8 转码器落成 |
| 10.5 对象流 | 快递员 | 整只对象寄走寄回 | Serializable 准入证 + serialVersionUID 身份证 |
概念速查(12 条)
- File 对象 = 路标:
new File("a.txt")只在内存建路标,磁盘上什么都不产生;createNewFile()才真建文件(抛受检IOException) - 绝对路径 vs 相对路径:从盘符写起 vs 从项目目录写起;
\要转义成\\,/跨平台通用 - File 四大族方法:判断(exists/isFile/isDirectory)、获取(getName/length/lastModified)、创建删除(mkdirs 一条龙、delete 直接删)、遍历(listFiles + FileFilter)
- 流的方向以内存为家:Out 出门写、In 进门读——
OutputStreamWriter名字里带 Out 却是字符流,指的是底层雇的字节流 - -1 哨兵:
read()返回int(32 位)所以能用 -1 表示读完了;byte8 位撞不了车 - 小推车:
read(byte[]/char[])返回本次个数,写出必须write(cart, 0, len)只写新货 - FileOutputStream 三规矩:文件不在自动建、存在默认清空、追加传第二参
true - close 必关:try-with-resources 最省心;字符流不 flush/close 文件是空的
- flush vs close:冲一把还能写 vs 先冲再关门
- 乱码铁律:编码不一致才会乱码,文件本身没坏;转换流不能自动探测编码
- 标记接口 Serializable:空接口当准入证;static 不参与、transient 字段跳过(读回 null/0)
- readObject 不走构造方法:JVM 按字节直接还原,拆包要类名对得上(ClassNotFoundException)
代码模板四段
① 字节流复制文件(万能,图片视频文本通吃)
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] cart = new byte[1024];
int len;
while ((len = fis.read(cart)) != -1) {
fos.write(cart, 0, len);
}
}
② 字符流写读文本(自动翻译)
try (FileWriter fw = new FileWriter("a.txt")) {
fw.write("你好 Java"); // 直接吃字符串
} // 关流 = 先 flush 再关门
try (FileReader fr = new FileReader("a.txt")) {
char[] cart = new char[1024];
int len;
while ((len = fr.read(cart)) != -1) {
System.out.print(new String(cart, 0, len));
}
}
③ 转换流指定编码(读 GBK / 存 UTF-8)
// 读:词典 = 文件实际编码
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk.txt"), "GBK")) { ... }
// 写:词典 = 目标编码
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8.txt"), "UTF-8")) { ... }
④ 对象流存取(整只快递)
// 类:implements Serializable + serialVersionUID = 1L + 敏感字段 transient
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("stu.txt"))) {
oos.writeObject(stu); // 寄出
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("stu.txt"))) {
Student s = (Student) ois.readObject(); // 签收,强转
}
五大名坑
- 对文件调
listFiles()→ 返回null,再.length直接 NPE——先isDirectory()再遍历 write(cart)忘带 len → 上次的旧货跟着写进文件(残渣坑),必须write(cart, 0, len)- 字符流复制图片 → 编码表把字节拦腰翻译,文件当场损坏——字符流只碰文本
- 编码名写错 / 词典不对 → 前者运行时
UnsupportedEncodingException,后者读出乱码——文件没坏 - serialVersionUID 不写 → 类一改旧文件全炸
InvalidClassException——实现 Serializable 顺手写上
收官暗线:IO 全家是套娃
回头看六章最惊讶的事:这些流的构造方法里传的全是另一个流——
new ObjectOutputStream( ← 快递员
new FileOutputStream( ← 搬运工
"stu.txt")) ← 路标指的文件
每一层只干一件事:搬运、翻译、换词典、打包。你缺什么能力,就在外面再包一层——new 套 new 不是套娃行为艺术,是「装饰叠加」的标准写法(这套路数以后学"装饰器模式"会正名)。加上 10.1 的分工——File 管「在哪」,流管「怎么搬」——整个 I/O 体系就两句话。
这条暗线还接住了前面所有章:Lambda 当过滤器(4.7)、受检异常(5.2)、finally 关流(5.3)、字符串进出(6.2)、编码表(计算机基础 02)、按名字找类(9.1)——IO 是前九章知识的综合考试场。
毕业大题:手写文件备份器
综合 10.1 + 10.2 + 10.3 + 4.7 的收官题:
写一个
backup(String srcDir, String destDir)方法:把 srcDir 里所有.java文件复制到 destDir(目录不存在就创建),控制台打印每个文件复制前后的字节数。
参考骨架:
public class Demo10 {
public static void main(String[] args) throws IOException {
backup("day11\\src", "day11\\backup");
}
static void backup(String srcDir, String destDir) throws IOException {
File src = new File(srcDir);
File dest = new File(destDir);
if (!dest.exists() && !dest.mkdirs()) { // 10.1:mkdirs 一条龙
throw new IOException("备份目录创建失败");
}
File[] files = src.listFiles(f -> // 10.1 + 4.7:Lambda 过滤器
f.isFile() && f.getName().endsWith(".java"));
for (File f : files) {
try (FileInputStream fis = new FileInputStream(f); // 10.2
FileOutputStream fos = new FileOutputStream(
new File(dest, f.getName()))) {
byte[] cart = new byte[8192];
int len;
while ((len = fis.read(cart)) != -1) {
fos.write(cart, 0, len); // 防残渣
}
}
System.out.println(f.getName() + ":" + f.length() + " → 备份完成");
}
}
}
自检清单(能全打勾这章就真毕业了):
- ☐ 能说清
new File和createNewFile()的区别 - ☐ 能默写字节流复制循环,并解释为什么
write要带len - ☐ 能解释 -1 哨兵为什么是
int而不是byte - ☐ 乱码了能按「哪边编码、哪边词典」排查,而不是怪文件坏了
- ☐ 能不查资料写出
implements Serializable+serialVersionUID的类定义 - ☐ 能说出「转换流和对象流构造方法里为什么要套一层字节流」
下一步学什么
- ☑ 10.6 本章小结 ✅ —— 点此复习:第十章 I-O/10.6 本章小结
- ☑ 第十一章 JDBC · 11.1 什么是JDBC ✅ —— 点此复习:第十一章 JDBC/11.1 什么是JDBC
相关笔记
- 第十章 I-O/10.1 File类 / 第十章 I-O/10.2 字节流 / 第十章 I-O/10.3 字符流 / 第十章 I-O/10.4 转换流 / 第十章 I-O/10.5 序列化和反序列化——本章五主角
- 第九章 反射机制和注解/9.5 本章小结:上一座大山
- 第十章 I-O/10.2 字节流:全章地基,复制套路是本章一切套娃的底层