☕ 第十章 I/O
📄 一句话概括

第十章 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 条)

  1. File 对象 = 路标:new File("a.txt") 只在内存建路标,磁盘上什么都不产生;createNewFile() 才真建文件(抛受检 IOException)
  2. 绝对路径 vs 相对路径:从盘符写起 vs 从项目目录写起;\ 要转义成 \\,/ 跨平台通用
  3. File 四大族方法:判断(exists/isFile/isDirectory)、获取(getName/length/lastModified)、创建删除(mkdirs 一条龙、delete 直接删)、遍历(listFiles + FileFilter)
  4. 流的方向以内存为家:Out 出门写、In 进门读——OutputStreamWriter 名字里带 Out 却是字符流,指的是底层雇的字节流
  5. -1 哨兵:read() 返回 int(32 位)所以能用 -1 表示读完了;byte 8 位撞不了车
  6. 小推车:read(byte[]/char[]) 返回本次个数,写出必须 write(cart, 0, len) 只写新货
  7. FileOutputStream 三规矩:文件不在自动建、存在默认清空、追加传第二参 true
  8. close 必关:try-with-resources 最省心;字符流不 flush/close 文件是空的
  9. flush vs close:冲一把还能写 vs 先冲再关门
  10. 乱码铁律:编码不一致才会乱码,文件本身没坏;转换流不能自动探测编码
  11. 标记接口 Serializable:空接口当准入证;static 不参与、transient 字段跳过(读回 null/0)
  12. 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();        // 签收,强转
}

五大名坑

  1. 对文件调 listFiles() → 返回 null,再 .length 直接 NPE——先 isDirectory() 再遍历
  2. write(cart) 忘带 len → 上次的旧货跟着写进文件(残渣坑),必须 write(cart, 0, len)
  3. 字符流复制图片 → 编码表把字节拦腰翻译,文件当场损坏——字符流只碰文本
  4. 编码名写错 / 词典不对 → 前者运行时 UnsupportedEncodingException,后者读出乱码——文件没坏
  5. 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() + " → 备份完成");
        }
    }
}

自检清单(能全打勾这章就真毕业了):

下一步学什么

相关笔记