上午 3h:转换流(解决中文乱码终极方案)

1. 编码问题铺垫(0.5h)

1.1 乱码是怎么来的?

  • 写的时候用 UTF-8
  • 读的时候用 GBK👉 编码不一致 = 乱码

1.2 常见编码

  • UTF-8:国际通用,中文占 3 字节
  • GBK:Windows 系统默认,中文占 2 字节
  • ASCII:英文

1.3 普通字符流的缺点

FileReader / FileWriter 只能用系统默认编码,不能手动指定。👉 遇到非系统编码文件 必乱码


2. 转换流核心作用(0.4h)

一句话记住

转换流 = 字节流 + 编码表 → 字符流

两大作用

  1. 把字节流 → 字符流
  2. 手动指定编码(UTF-8/GBK)
  3. 彻底解决中文乱码

两个类

  • InputStreamReader:字节输入流 → 字符输入流(读)
  • OutputStreamWriter:字节输出流 → 字符输出流(写)

3. 转换输入流:InputStreamReader(1.05h)

作用

以指定编码读取文件

构造方法

java

运行

InputStreamReader isr = new InputStreamReader(
    new FileInputStream("文件路径"),
    "GBK"       // 可以写 UTF-8
);

完整代码(指定 GBK 读取不乱码)

java

运行

import java.io.*;

public class ISRDemo {
    public static void main(String[] args) throws IOException {
        // 以 GBK 编码读取文件
        InputStreamReader isr = new InputStreamReader(
            new FileInputStream("D:\\JavaDay17\\gbk.txt"),
            "GBK"
        );

        char[] chs = new char[1024];
        int len;
        while ((len = isr.read(chs)) != -1) {
            System.out.print(new String(chs, 0, len));
        }

        isr.close();
    }
}

解释

  • 无论文件是什么编码,只要指定对,就不乱码
  • 这是解决乱码的标准方案

4. 转换输出流:OutputStreamWriter(1.05h)

作用

以指定编码写文件

构造方法

java

运行

OutputStreamWriter osw = new OutputStreamWriter(
    new FileOutputStream("路径"),
    "UTF-8"
);

完整代码(UTF-8 写入)

java

运行

import java.io.*;

public class OSWDemo {
    public static void main(String[] args) throws IOException {
        // 以 UTF-8 编码写入
        OutputStreamWriter osw = new OutputStreamWriter(
            new FileOutputStream("D:\\JavaDay17\\utf8.txt"),
            "UTF-8"
        );

        osw.write("我是UTF-8编码的中文");
        osw.close();
    }
}

5. 转换流使用场景总结

  • 读取老旧 GBK 文件
  • 跨平台文本传输
  • 必须指定编码的场景
  • 解决所有中文乱码

下午 2.5h:对象序列化流(存对象、读对象)

1. 序列化概念(0.4h)

序列化

对象 → 字节数组 → 保存到文件

反序列化

文件字节 → 还原成对象

应用

  • 保存用户登录状态
  • 本地缓存对象
  • 网络传输对象

2. 序列化流:ObjectOutputStream(1.05h)

必须满足

实体类必须实现 Serializable 接口

步骤

  1. 写一个类(如 Student)
  2. 实现 Serializable
  3. 使用 writeObject() 写入

完整代码

① Student.java

java

运行

import java.io.Serializable;

// 必须加这个接口
public class Student implements Serializable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", age=" + age + "}";
    }
}
② 序列化写入文件

java

运行

import java.io.*;

public class WriteObject {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("D:\\JavaDay17\\student.txt")
        );

        // 写入对象
        Student stu = new Student("张三", 20);
        oos.writeObject(stu);

        oos.close();
    }
}

3. 反序列化流:ObjectInputStream(1.05h)

方法

readObject()

完整代码

java

运行

import java.io.*;

public class ReadObject {
    public static void main(String[] args) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("D:\\JavaDay17\\student.txt")
        );

        // 读取并还原对象
        Student stu = (Student) ois.readObject();
        System.out.println(stu);

        ois.close();
    }
}

重要关键字:transient

java

运行

transient private int age;

被 transient 修饰的变量 → 不参与序列化 → 读到是默认值 0 /null


晚上 1.5h:IO 综合强化练习

1. 转换流读写 UTF-8、GBK(必做)

java

运行

// 读GBK
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");
// 写UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"),"UTF-8");

2. 序列化与反序列化(必做)

上面完整代码。

3. 文件夹复制(递归 + 字节流)

这是大综合案例,包含:

  • 文件判断
  • 文件夹创建
  • 递归
  • 字节流复制

完整代码

java

运行

import java.io.*;

public class CopyFolder {
    public static void main(String[] args) throws IOException {
        File src = new File("D:\\JavaDay17\\源文件夹");
        File dest = new File("D:\\JavaDay17\\目标文件夹");
        copyDir(src, dest);
    }

    // 复制文件夹
    public static void copyDir(File src, File dest) throws IOException {
        if (src.isFile()) {
            // 是文件 → 复制
            FileInputStream fis = new FileInputStream(src);
            FileOutputStream fos = new FileOutputStream(new File(dest, src.getName()));
            byte[] buf = new byte[8192];
            int len;
            while ((len = fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            fos.close();
            fis.close();
            return;
        }

        // 创建目标文件夹
        File newDir = new File(dest, src.getName());
        newDir.mkdirs();

        // 遍历子文件
        File[] files = src.listFiles();
        for (File f : files) {
            copyDir(f, newDir);
        }
    }
}

一、先告诉你:这段代码是干嘛的?

把一个文件夹(包括里面所有子文件夹、所有文件)完整复制到另一个地方,一模一样。

例子:

  • 源:D:\JavaDay17\源文件夹
  • 目标:D:\JavaDay17\目标文件夹运行后 → 源文件夹整个被复制到目标文件夹里

二、整体结构

java

运行

public class CopyFolder {
    public static void main(String[] args) throws IOException {
        // 1. 定义源、目标
        // 2. 调用方法复制
    }

    public static void copyDir(File src, File dest) throws IOException {
        // 核心递归方法
    }
}

三、逐行详细讲解

1️⃣ 主方法 main

java

运行

public static void main(String[] args) throws IOException {
    File src = new File("D:\\JavaDay17\\源文件夹");
    File dest = new File("D:\\JavaDay17\\目标文件夹");
    copyDir(src, dest);
}

知识点

  • File 类:不代表文件内容,代表路径 / 文件夹 / 文件的抽象
  • throws IOException:IO 操作可能出错(文件找不到、权限不足),声明抛出
  • 作用:
    • 告诉程序从哪复制
    • 告诉程序复制到哪
    • 调用 copyDir 开始干活

2️⃣ 核心方法:copyDir(递归复制)

重点:递归!递归!递归!

自己调用自己,直到把所有文件都复制完。


第一步:判断是不是文件

java

运行

if (src.isFile()) {
    // 是文件 → 复制
}

知识点

  • isFile():判断当前路径 是不是一个文件
  • 如果是文件 → 进入复制逻辑
  • 如果是文件夹 → 跳过,走后面逻辑

第二步:如果是文件,开始复制

java

运行

FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(new File(dest, src.getName()));

讲解

  • FileInputStream字节输入流 → 读文件
  • FileOutputStream字节输出流 → 写文件

java

运行

new File(dest, src.getName())

意思:在目标文件夹下,创建一个和源文件同名的文件

例子:源文件:a.jpg目标路径:目标文件夹/a.jpg


第三步:使用字节数组高速复制

java

运行

byte[] buf = new byte[8192];
int len;
while ((len = fis.read(buf)) != -1) {
    fos.write(buf, 0, len);
}

超级重要知识点

  • byte[] buf = new byte[8192]缓冲区,一次读 8KB 数据,速度极快

  • fis.read(buf)一次读一堆数据到数组里返回值:读到的有效字节数

  • len == -1:文件读完了

  • fos.write(buf,0,len)把读到的数据写入目标文件


第四步:关流

java

运行

fos.close();
fis.close();
  • 流使用完必须关闭
  • 释放资源
  • 防止文件被占用

第五步:如果是文件夹,创建文件夹

java

运行

File newDir = new File(dest, src.getName());
newDir.mkdirs();

讲解

  • 在目标位置,创建一个和源文件夹同名的文件夹
  • mkdirs()创建多级文件夹(最常用)

第六步:遍历文件夹里的所有内容

java

运行

File[] files = src.listFiles();
for (File f : files) {
    copyDir(f, newDir);
}

核心:递归!

  1. listFiles()获取文件夹里所有子文件 / 子文件夹
  2. 遍历每一个
  3. 再次调用 copyDir自己调用自己!

四、整套代码运行顺序(超级重要)

我给你走一遍流程,你马上懂递归!


运行顺序

  1. main 方法启动
  2. 进入 copyDir(源文件夹, 目标文件夹)
  3. 判断:源文件夹 → 是文件夹
  4. 创建:目标文件夹\源文件夹
  5. 获取里面所有子文件
  6. 遍历每一个:
    • 如果是文件 → 复制
    • 如果是文件夹 → 再次进入 copyDir重复 3~6 步骤!

一句话总结递归逻辑

是文件就复制

是文件夹就创建,然后进去继续遍历

文件 = 有内容 → 必须用流读写

文件夹 = 只是个空目录 → 只需要创建,不需要读写

今日核心复盘(必背)

  1. 转换流 = 字节流 + 编码 → 解决乱码
  2. InputStreamReader 读指定编码
  3. OutputStreamWriter 写指定编码
  4. 序列化:对象 → 文件
  5. 反序列化:文件 → 对象
  6. 必须实现 Serializable
  7. transient 不序列化
  8. 文件夹复制 = 递归 + 字节流

更多推荐