Преглед изворни кода

feat: 新增20260807课程内容——IO缓冲流与字符流(BufferedStreamTest/BufferedCopy/BufferedWriteReadTest字节缓冲流,FileReader/FileWriter字符流,Copy01/CopyFile/Copy02接口化文件复制,ScoreSort成绩排序),含课后作业(接口化文件复制效率对比器)/课堂笔记/课堂练习

WanJL пре 1 недеља
родитељ
комит
0e21d90ba9

+ 32 - 0
授课代码/c260807/src/course/BufferedCopy.java

@@ -0,0 +1,32 @@
+package course;
+
+import java.io.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BufferedCopy
+ * @description
+ * @create 2026/8/7
+ */
+public class BufferedCopy implements CopyFile{
+    @Override
+    public String copy(String src, String desc) throws IOException {
+        //1、创建文件字节输出流和缓冲流对象
+        FileOutputStream fos=new FileOutputStream(desc);
+        BufferedOutputStream bos=new BufferedOutputStream(fos);
+        //2创建文件字节输入流和缓冲流对象
+        FileInputStream fis=new FileInputStream(src);
+        BufferedInputStream bis=new BufferedInputStream(fis);
+        byte[] bytes=new byte[8192];
+        int count;
+        while ((count=bis.read(bytes))!=-1){
+            bos.write(bytes,0,count);
+        }
+        bis.close();
+        fis.close();
+        bos.close();
+        fos.close();
+        return "复制成功";
+    }
+}

+ 44 - 0
授课代码/c260807/src/course/BufferedStreamTest.java

@@ -0,0 +1,44 @@
+package course;
+
+import java.io.*;
+import java.util.Random;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BufferedStreamTest
+ * @description
+ * @create 2026/8/7
+ */
+public class BufferedStreamTest {
+    public static void main(String[] args) throws IOException {
+        /*
+        //1、创建文件字节输出流
+        FileOutputStream fos=new FileOutputStream("D:/abc.txt");
+        //2、使用缓冲输出流封装文件字节输出流
+        BufferedOutputStream bos=new BufferedOutputStream(fos);
+        Random ran=new Random();
+        //3、执行BufferedOutputStream的写操作
+        for (int i = 0; i <1000000 ; i++) {
+            int c=ran.nextInt(26)+65;
+            bos.write(c);
+            if (i%500==0){
+                bos.write("\r\n".getBytes());
+                bos.flush();
+            }
+        }
+        bos.close();
+        fos.close();
+        */
+        FileInputStream fis=new FileInputStream("D:/abc.txt");
+        BufferedInputStream bis=new BufferedInputStream(fis);
+        int b;
+        while ((b=bis.read())!=-1){
+            System.out.print((char) b);
+        }
+        bis.close();
+        fis.close();
+
+
+    }
+}

+ 40 - 0
授课代码/c260807/src/course/Copy01.java

@@ -0,0 +1,40 @@
+package course;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Copy01
+ * @description
+ * @create 2026/8/7
+ */
+public class Copy01 implements CopyFile {
+    @Override
+    public String copy(String src, String desc) {
+        FileOutputStream fos = null;
+        FileInputStream fis = null;
+        try {
+            fos = new FileOutputStream(desc);
+            fis = new FileInputStream(src);
+            byte[] bytes = new byte[8192];
+            int byteCount;
+            while ((byteCount = fis.read(bytes)) != -1) {
+                fos.write(bytes, 0, byteCount);
+            }
+        } catch (IOException e) {
+            return "文件复制失败";
+        }finally {
+            try {
+                fos.close();
+                fis.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return "文件复制成功";
+    }
+}

+ 20 - 0
授课代码/c260807/src/course/CopyFile.java

@@ -0,0 +1,20 @@
+package course;
+
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title CopyUtil
+ * @description
+ * @create 2026/8/7
+ */
+public interface CopyFile {
+    /**
+     * 复制文件的方法
+     * @param src 原路径
+     * @param desc 新路径
+     * @return
+     */
+    String copy(String src,String desc) throws IOException;
+}

+ 55 - 0
授课代码/c260807/src/course/Demo01.java

@@ -0,0 +1,55 @@
+package course;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Scanner;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description
+ * @create 2026/8/7
+ */
+public class Demo01 {
+    /*
+        字节缓冲流
+            BufferedOutputStream:这个类是实现缓冲输出流的类,通过设置这样的输出流,程序可以向底层输出流写入字节,
+            也就是说它针对的不是真正的文件,而是文件的输出流。
+            BufferedInputStream:这个类是实现缓冲输入流的类。通过设置输入流,可以向底层输入流读取数据。
+            这个类里面维护了一个字节缓冲区,内部缓冲区会根据需要,从包含的输入流中获取数据填充到缓冲区。一次可以读取多个字节。
+
+            public BufferedOutputStream(OutputStream out)   缓冲字节输出流的构造方法,参数是字节输出流
+            public BufferedInputStream(InputStream out)   缓冲字节输入流的构造方法,参数是字节输入流
+
+        FileInputStream和FileOutputStream属于基础流,他们是直接接触和操作文件的。
+        BufferedOutputStream和BufferedInputStream属于扩展流,他们是用来操作基础流的。
+
+
+
+
+
+     */
+
+
+    public static void main(String[] args) throws IOException {
+//        String src="E:\\哈哈哈哈.zip";
+//        String desc="D:\\哈哈哈哈.zip";
+//        long start = System.currentTimeMillis();
+//        String message = new Copy01().copy(src, desc);
+//
+//        long end = System.currentTimeMillis();
+//
+//        System.out.println(message+"耗时"+(end-start)+"毫秒");
+
+        FileOutputStream fos=new FileOutputStream("D:abc.txt");
+        Scanner sc=new Scanner(System.in);
+        while (true){
+            System.out.println("请写入内容:");
+            String s = sc.nextLine();
+            fos.write(s.getBytes());
+        }
+
+    }
+}

+ 62 - 0
授课代码/c260807/src/course/Demo02.java

@@ -0,0 +1,62 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo02
+ * @description 字符流
+ * @create 2026/8/7
+ */
+public class Demo02 {
+    /*
+        由于字节流操作字符(特别是中文)不方便,所以Java提供了字符流
+        字符流 = 字节流 + 字符集编码表
+
+        什么是字符集编码表?
+        是一个系统支持的所有的字符的集合 ,包括各个国家的文字、符号、图像符号、数字等等
+        计算机要像识别这些文字和符号,就需要存储一套编码表,然后按照表中的对应关系,存储对应的字符编码。
+        常见的字符编码:ASCII、GB-2312、GBK、Unicode(UTF-8)
+
+        字符转转对于的字节编码的方式:
+        这些都是String类提供的编码方式
+        public byte[] getBytes()    使用平台(操作系统)默认的字符集把这个字符串转换为一系列字节
+        public byte[] getBytes(Charset charset) 使用指定的字符集把这个字符串转换为一系列字节
+        public byte[] getBytes(String charsetName)使用指定的字符集名称把这个字符串转换为一系列字节
+
+        String类提供的解码方式
+        public String(byte[] bytes) 使用平台(操作系统)默认的字符集把这组字节数组解码为字符串
+        public String(byte[] bytes, Charset charset) 使用指定的字符集把这组字节数组解码为字符串
+        public String(byte[] bytes, String charsetName) 使用指定的字符集把这组字节数组解码为字符串
+
+
+        字符流:
+            |-输入字符流 :Reader 是输入字符流的抽象类
+                |- 文件输入字符流  FileReader
+            |-输出字符流 :Writer 是输出字符流的抽象类
+                |- 文件输出字符流 :FileWriter
+        字符流自带缓冲区,可以使用flush()刷新缓冲区,强制把缓冲区的内容写入到文件中。
+            文件字符输入输出流的创建对象方式:
+            public FileReader(String fileName)  根据文件名创建文件字符输入流,使用的是系统默认字符集
+            public FileReader(File file)    根据file对象创建文件字符输入流,使用的是系统默认字符集
+            public FileReader(String fileName, Charset charset)根据文件名创建文件字符输入流,使用的是指定字符集
+
+            public FileWriter(String fileName)根据文件名创建文件字符输出流,使用的是系统默认字符集
+            public FileWriter(File file)根据file对象创建文件字符输出流,使用的是系统默认字符集
+            public FileWriter(File file, boolean append)根据file对象创建文件字符输出流,使用的是系统默认字符集,设置可以追加
+            public FileWriter(String fileName, Charset charset)根据文件名创建文件字符输出流,使用的是指定字符集
+
+            读取和写入的方法:
+
+        write(int c)    写一个字符
+        write(char[] c) 写一个字符数组的内容
+        write(char[] c, int off, int len)   写一个字符数组的内容,从off开始,长度为len
+        write(String str) 写一个字符串
+        write(String str, int off, int len)   写一个字符数组的内容,从off开始,长度为len
+        flush() 刷新流,把把缓冲区的内容写入到文件中。
+        close() 关闭流
+
+        read () 读取一个字符
+        read (char[] c, int off, int len) 读取一部分字符数组
+
+     */
+}

+ 74 - 0
授课代码/c260807/src/course/Demo03.java

@@ -0,0 +1,74 @@
+package course;
+
+import java.io.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo03
+ * @description 字符缓冲流
+ * @create 2026/8/7
+ */
+public class Demo03 {
+    /*
+        BufferedWriter  把文本写入字符输出流,缓冲字符。提供高效写入。可以指定缓冲区的大小,或者可以接受默认大小。默认值足够大,可以用在大多数用途中。
+        BufferedReader  从字符输入流中读取文本,缓冲字符。提供高效读取。可以指定缓冲区的大小,或者可以接受默认大小。默认值足够大,可以用在大多数用途中。
+
+         public BufferedReader(Reader in)   创建缓冲字符输入流对象
+         public BufferedReader(Reader in, int sz)   创建缓冲字符输入流对象,指定缓冲区的大小
+
+        public BufferedWriter(Writer out)   创建缓冲字符输出流对象
+        public BufferedWriter(Writer out, int sz)   创建缓冲字符输出流对象,指定缓冲区的大小
+
+
+        字符缓冲流特有的方法:
+        BufferedWriter:
+        public void newLine() 写一行行分隔符,行分隔符字符串根据系统属性进行定义
+        BufferedReader:
+        public String readLine() 读取一行字符串,包含行的内容的字符串,不包括任何终止字符,
+        如果已经到了流的末尾,则为null
+
+     */
+    public static void main(String[] args) throws IOException {
+        BufferedWriter bw = new BufferedWriter(new FileWriter("D:/abc.txt"));
+        BufferedReader br = new BufferedReader(new FileReader("D:/abc.txt"));
+
+        bw.write(97);
+        bw.write("hello world\r\n");
+
+        bw.write("白日依山尽");
+        bw.newLine();
+        bw.write("黄河入海流");
+        bw.newLine();
+        bw.write("哈哈哈哈哈");
+        bw.newLine();
+        bw.write("自挂东南枝");
+        bw.newLine();
+
+        bw.flush(); //写完,还没有关闭流,缓冲区还没有满,那么这时候是不会写入到文件中的。
+        //所以才需要刷新,强制刷新到文件中。
+        /*
+        int ch;
+        while ((ch=br.read())!=-1){
+            System.out.print((char) ch);
+        }
+
+        char[] chars=new char[4096];
+        int count;
+        while ((count=br.read(chars))!=-1){
+            System.out.print(new String(chars,0,count));
+        }
+        */
+
+        String line;    //行
+        while ((line=br.readLine())!=null){
+            System.out.println(line);
+        }
+
+
+
+        bw.close();
+        br.close();
+    }
+
+}

+ 31 - 0
授课代码/c260807/src/course/Demo04.java

@@ -0,0 +1,31 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo04
+ * @description 小结
+ * @create 2026/8/7
+ */
+public class Demo04 {
+    /*
+        基础流两种:
+            字节流:	处理是可以任意类型的文件
+                输出字节流	OutputStream
+                    文件输出字节流:FileOutputStream
+                输入字节流	InputStream
+                    文件输入字节流:FileInputStream
+            字符流:	处理的是纯文本文件
+                输入字符流	Reader
+                    文件输入字符流:FileReader
+                输出字符流	Writer
+                    文件输出字符流:FileWriter
+        处理流(扩展流)-缓冲流两种:
+            缓冲字节流:	处理的是字节输入流和字节输出流。
+                缓冲字节输入流:BufferedInputStream
+                缓冲字节输出流:BufferedOutputStream
+            缓冲字符流:	处理的是字符输入流和字符输出流。
+                缓冲字符输入流:BufferedReader
+                缓冲字符输出流:BufferedWriter
+     */
+}

+ 32 - 0
授课代码/c260807/src/course/FileReaderTest.java

@@ -0,0 +1,32 @@
+package course;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title FileReaderTest
+ * @description
+ * @create 2026/8/7
+ */
+public class FileReaderTest {
+    public static void main(String[] args) throws IOException {
+        FileReader fr=new FileReader("D:/abc.txt");
+        //read () 读取一个字符
+//        int ch;
+//        while ((ch=fr.read())!=-1){
+//            System.out.print((char) ch);
+//        }
+
+        //read (char[] c, int off, int len) 读取一部分字符数组
+        char[] chars=new char[1024];
+        int count;
+        while ((count=fr.read(chars))!=-1){
+            System.out.print(new String(chars,0,count));
+        }
+        //释放资源
+        fr.close();
+    }
+}

+ 34 - 0
授课代码/c260807/src/course/FileWriterTest.java

@@ -0,0 +1,34 @@
+package course;
+
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title FileWriterTest
+ * @description
+ * @create 2026/8/7
+ */
+public class FileWriterTest {
+    public static void main(String[] args) throws IOException {
+        FileWriter fw=new FileWriter("D:/abc.txt");
+        //write(int c)    写一个字符
+        fw.write(97);
+        //write(char[] c) 写一个字符数组的内容
+        char[] chars={'a','b','c','d','e'};
+        fw.write(chars);
+        //write(char[] c, int off, int len)   写一个字符数组的内容,从off开始,长度为len
+        fw.write(chars,3,2);
+        //flush() 刷新流,把把缓冲区的内容写入到文件中。
+        fw.flush();
+        //write(String str) 写一个字符串
+        fw.write("Hello world");
+        //write(String str, int off, int len)   写一个字符数组的内容,从off开始,长度为len
+        fw.write("qwertyuiopasdfghjklxcvbnm",3,6);
+
+        //close() 关闭流
+
+        fw.close();
+    }
+}

+ 23 - 0
授课代码/c260807/src/course/MyTest.java

@@ -0,0 +1,23 @@
+package course;
+
+import java.io.IOException;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Test
+ * @description
+ * @create 2026/8/7
+ */
+public class MyTest {
+    public static void main(String[] args) throws IOException {
+        String src = "D:\\CentOS7\\SpringCloudAlibaba-CentOS7.zip";
+        String desc = "E:\\SpringCloudAlibaba-CentOS7.zip";
+        long start = System.currentTimeMillis();
+        String message = new BufferedCopy().copy(src, desc);
+
+        long end = System.currentTimeMillis();
+
+        System.out.println(message + "耗时" + (end - start) + "毫秒");
+    }
+}

+ 57 - 0
授课代码/c260807/src/exericse/BufferedWriteReadTest.java

@@ -0,0 +1,57 @@
+package exericse;
+
+import java.io.*;
+import java.util.Random;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BufferedWriteReadTest
+ * @description
+ * @create 2026/8/7
+ */
+public class BufferedWriteReadTest {
+    public static void main(String[] args) throws IOException {
+        String path = "D:/letters.txt";
+        Random ran = new Random();
+
+        // ========== 写:100 万随机大写字母,每 500 个换行 + flush ==========
+        // TODO 1: 创建 FileOutputStream + BufferedOutputStream 包装它
+        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
+
+        // TODO 2: for 循环 100 万次,ran.nextInt(26) + 65 生成 A~Z,bos.write(c) 先写入内部缓冲区
+        for (int i = 0; i < 1000000; i++) {
+            int c = ran.nextInt(26) + 65;
+            bos.write(c);
+            // TODO 3: 每 500 个写一次 "\r\n".getBytes(),并调用 bos.flush() 刷新到磁盘
+            if (i % 500 == 0) {
+                bos.write("\r\n".getBytes());
+                bos.flush();
+            }
+        }
+        // TODO 4: 关闭缓冲流和文件流
+        bos.close();
+        // ========== 读:BufferedInputStream 逐字节读回并统计 ==========
+        // TODO 5: 创建 FileInputStream + BufferedInputStream 包装它
+        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
+        // TODO 6: int[] count = new int[26]; 统计每个大写字母出现次数
+        int[] count = new int[26];
+        //         while ((b = bis.read()) != -1) { if (b >= 'A' && b <= 'Z') count[b - 'A']++; }
+        int b;
+        while ((b = bis.read()) != -1) {
+            if (b >= 'A' && b <= 'Z')
+                count[b - 'A']++;
+        }
+        // TODO 7: 遍历 count 输出每个字母出现次数;找出出现最多的字母
+        int max=count[0];
+        for (int i = 0; i < count.length; i++) {
+            System.out.println(count[i]);
+            if (max<count[i]){
+                max=count[i];
+            }
+        }
+        System.out.println("最多的是"+max+"次");
+        // TODO 8: 关闭流
+        bis.close();
+    }
+}

+ 55 - 0
授课代码/c260807/src/exericse/ScoreSort.java

@@ -0,0 +1,55 @@
+package exericse;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ScoreSort
+ * @description
+ * @create 2026/8/7
+ */
+public class ScoreSort {
+    public static void main(String[] args) throws IOException {
+        // ========== 1、将文件中的数据读取到程序中 ==========
+        // TODO 1: new BufferedReader(new FileReader("D:/scores.txt")) 字符缓冲流包装
+        BufferedReader br = new BufferedReader(new FileReader("D:/scores.txt"));
+        ArrayList<Student> list = new ArrayList<>();
+        // TODO 2: while ((line = br.readLine()) != null) { ... } 逐行读取
+        String line;
+        while ((line = br.readLine()) != null) {
+            // ========== 2、对读取到的数据进行处理 ==========
+            // TODO 3: String[] parts = line.split("-");   按 "-" 拆分成 [姓名, 成绩]
+            String[] parts = line.split("-");
+            // TODO 4: int score = Integer.parseInt(parts[1]);   成绩字符串 → 整数
+            String name = parts[0];
+            int score = Integer.parseInt(parts[1]);
+            // ========== 3、将处理后的数据添加到集合中 ==========
+            // TODO 5: new Student(parts[0], score) 封装对象,list.add(stu) 存入 ArrayList
+            Student stu = new Student(name, score);
+            list.add(stu);
+        }
+        // ========== 4、对集合中的数据进行排序 ==========
+        // TODO 6: Collections.sort(list, new Comparator<Student>() { ... });
+        //         按成绩【降序】:o2.getScore() - o1.getScore()
+        Collections.sort(list, ((o1, o2) -> o2.getScore() - o1.getScore()));
+
+
+        // ========== 5、将排序后的集合中的数据写入到文件中 ==========
+        // TODO 7: new BufferedWriter(new FileWriter("D:/sorted.txt"))
+        BufferedWriter bw = new BufferedWriter(new FileWriter("D:/sorted.txt"));
+        // TODO 8: for 遍历排序后的集合,bw.write("第" + (i+1) + "名:" + 学生) + bw.newLine()
+        for (int i = 0; i < list.size(); i++) {
+            Student student = list.get(i);
+            bw.write("第" + (i + 1) + "名:" + student.toString());
+            bw.newLine();
+        }
+        // TODO 9: bw.flush();  bw.close();  br.close();
+        bw.flush();
+        bw.close();
+        br.close();
+        System.out.println("排序完成!结果已写入 D:/sorted.txt");
+    }
+}

+ 25 - 0
授课代码/c260807/src/exericse/Student.java

@@ -0,0 +1,25 @@
+package exericse;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Student
+ * @description
+ * @create 2026/8/7
+ */
+public class Student {
+    private String name;
+    private int score;
+
+    public Student(String name, int score) {
+        this.name = name;
+        this.score = score;
+    }
+    public String getName() { return name; }
+    public int getScore() { return score; }
+
+    @Override
+    public String toString() {
+        return name + "-" + score;
+    }
+}

+ 278 - 0
课后作业/2026年8月7日-作业.md

@@ -0,0 +1,278 @@
+# 2026年8月7日 课后作业 — IO 缓冲流 + 字符流:中文读写、成绩排序写回、接口化文件复制
+
+> **说明**:本次课程学习了 **IO 缓冲流**(`BufferedOutputStream` / `BufferedInputStream`:内部缓冲区、`flush()` 刷新、基础流 vs 扩展流)、**字符流**(`FileReader` / `FileWriter`:字符流 = 字节流 + 字符集编码表、直接 `write(String)` 写中文无需 `getBytes()`)、**字符缓冲流**(`BufferedReader` / `BufferedWriter`:`readLine()` 逐行读取、`newLine()` 跨平台换行)以及 **接口化文件复制**(`CopyFile` 接口 + 面向接口编程 / 多态 + `System.currentTimeMillis()` 耗时对比)。
+>
+> 本作业共 **3 道题**:**基础题(必做)**——字符流中文读写器;**进阶题(必做)**——字符缓冲流成绩排序写回;**挑战题(选做)**——接口化文件复制效率对比器。覆盖今日 80% 以上知识点。
+
+---
+
+## 📌 作业总体要求
+
+### 一、命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **类名** | `Demo_题号_你的姓名`(如 `Demo_01_张三`、`Demo_02_李四`、`Demo_03_王五`) |
+| **文件头注释** | 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释 |
+
+### 二、提交方式与截止时间
+
+| 项目 | 要求 |
+|------|------|
+| **提交方式** | 将源码文件 + 运行结果截图提交至班级指定平台 |
+| **截止时间** | 下次上课前 |
+
+---
+
+## 一、基础题(必做)
+
+### 题目 1:字符流中文读写器 CharFileDemo(⭐⭐ 必做)
+
+**知识点**:字符流概念(字符流 = 字节流 + 字符集编码表)、`FileWriter`(直接 `write(String)` 写中文、六种写方法)、`FileReader`(`read()` / `read(char[])` 两种读法)、`new String(chars, 0, count)` 字符数组转字符串、`flush()` / `close()`
+
+**场景描述**:字节流操作中文很不方便,需要 `getBytes()` 手动转字节数组。字符流内部按**字符集编码表**完成"字符 ↔ 字节"转换,可以直接 `write("中文")`。请用 `FileWriter` 把一首中文古诗写入 `D:/poem.txt`,再用 `FileReader` 读回并显示到控制台。
+
+**题目要求**:
+
+```java
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * @author 你的姓名
+ * @title 题目1:字符流中文读写器
+ * @create 2026/8/7
+ */
+public class Demo_01_姓名 {
+    public static void main(String[] args) throws IOException {
+        // ========== 写:FileWriter 直接写中文(无需 getBytes) ==========
+        // TODO 1: new FileWriter("D:/poem.txt") 创建文件字符输出流
+        // TODO 2: 用 fw.write("...") 写至少 2 句中文(如静夜思),句末用 fw.write("\r\n") 换行
+        // TODO 3: fw.flush();   // 字符流自带缓冲区,强制刷新到文件
+        // TODO 4: fw.close();   // 关闭流
+
+        // ========== 读:FileReader 读回并显示 ==========
+        // TODO 5: new FileReader("D:/poem.txt") 创建文件字符输入流
+        // TODO 6: char[] chars = new char[1024];
+        //         int count;
+        //         while ((count = fr.read(chars)) != -1) {
+        //             System.out.print(new String(chars, 0, count));
+        //         }
+        // TODO 7: fr.close();   // 关闭流
+    }
+}
+```
+
+**预期输出示例**:
+
+```
+床前明月光,疑是地上霜。
+举头望明月,低头思故乡。
+```
+
+**要求说明**:
+1. 必须使用**字符流**(`FileWriter` / `FileReader`),不允许用字节流
+2. 写中文**不需要**调用 `getBytes()`,直接 `write("中文")`
+3. 读取使用 `read(char[])` 批量读取 + `new String(chars, 0, count)`,不允许逐字符打印
+
+**💡 提示**:
+- `FileWriter` / `FileReader` 构造方法**只传路径**即可(使用系统默认字符集)
+- `fr.read(chars)` 返回**本次读到的有效字符个数**,可能小于 `chars.length`,所以转字符串时务必用 `new String(chars, 0, count)` 截断有效部分
+- `flush()` 之后还可以继续写;`close()` 之前会自动刷新一次
+
+**🎯 挑战加分(选做)**:
+1. 用 `new FileWriter("D:/poem.txt", true)` 追加写第二段,验证 `append=true` 追加 vs 默认覆盖
+2. 用 `write(String, off, len)` 只写入诗句的某一段子串
+3. 统计读到的**汉字个数**(提示:读到的 char 值 > 127 即为非 ASCII 中文字符)
+
+---
+
+## 二、进阶题(必做)
+
+### 题目 2:字符缓冲流成绩排序写回 ScoreSort(⭐⭐⭐ 必做)
+
+**知识点**:`BufferedReader` / `BufferedWriter`、`readLine()` 逐行读取(末尾返回 null)、`newLine()` 跨平台换行、`String.split("-")` 拆分、`Integer.parseInt()` 字符串转整数、`ArrayList` + `Collections.sort` + `Comparator`(Lambda)排序、读取→处理→集合→排序→写回五步闭环
+
+**场景描述**:`D:/scores.txt` 中保存了若干行"姓名-成绩"(顺序是乱的),如下:
+
+```
+李四-78
+王五-92
+张三-66
+赵六-85
+孙七-95
+周八-88
+```
+
+请用 `BufferedReader` 逐行读取 → `split("-")` 拆分成姓名和成绩 → 封装 `Student` 对象存入 `ArrayList` → 按成绩**从高到低**排序 → 用 `BufferedWriter` 把带名次的结果写回 `D:/sorted.txt`。
+
+**题目要求**:
+
+```java
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * @author 你的姓名
+ * @title 题目2:字符缓冲流成绩排序写回
+ * @create 2026/8/7
+ */
+public class Demo_02_姓名 {
+    // 学生类:姓名 + 成绩
+    static class Student {
+        private String name;
+        private int score;
+        public Student(String name, int score) { this.name = name; this.score = score; }
+        public String getName() { return name; }
+        public int getScore() { return score; }
+        @Override
+        public String toString() { return name + "-" + score; }
+    }
+
+    public static void main(String[] args) throws IOException {
+        // ========== 1、将文件中的数据读取到程序中 ==========
+        // TODO 1: new BufferedReader(new FileReader("D:/scores.txt")) 字符缓冲流包装
+        // TODO 2: while ((line = br.readLine()) != null) { ... } 逐行读取
+
+        // ========== 2、对读取到的数据进行处理 ==========
+        // TODO 3: String[] parts = line.split("-");   按 "-" 拆分成 [姓名, 成绩]
+        // TODO 4: int score = Integer.parseInt(parts[1]);   成绩字符串 → 整数
+
+        // ========== 3、将处理后的数据添加到集合中 ==========
+        // TODO 5: new Student(parts[0], score) 封装对象,list.add(stu) 存入 ArrayList
+
+        // ========== 4、对集合中的数据进行排序 ==========
+        // TODO 6: Collections.sort(list, (o1, o2) -> o2.getScore() - o1.getScore());  // 按成绩降序
+
+        // ========== 5、将排序后的集合中的数据写入到文件中 ==========
+        // TODO 7: new BufferedWriter(new FileWriter("D:/sorted.txt"))
+        // TODO 8: for 遍历排序后的集合,bw.write("第" + (i+1) + "名:" + list.get(i)) + bw.newLine()
+        // TODO 9: bw.flush();  bw.close();  br.close();
+        System.out.println("排序完成!结果已写入 D:/sorted.txt");
+    }
+}
+```
+
+**预期输出示例**(`D:/sorted.txt` 内容):
+
+```
+第1名:孙七-95
+第2名:王五-92
+第3名:周八-88
+第4名:赵六-85
+第5名:李四-78
+第6名:张三-66
+```
+
+**要求说明**:
+1. 必须使用**字符缓冲流**(`BufferedReader` / `BufferedWriter`),不允许用普通字符流
+2. 读取用 `readLine()` 逐行读取,`while ((line = br.readLine()) != null)` 循环
+3. 排序用 `Collections.sort` + `Comparator`(Lambda 或匿名内部类均可),**按成绩降序**
+4. 写回用 `newLine()` 换行(不要手写 `"\r\n"`)
+
+**💡 提示**:
+- `line.split("-")` 返回 `String[]`,`parts[0]` 是姓名、`parts[1]` 是成绩字符串
+- `Integer.parseInt(parts[1])` 把成绩字符串转成整数后,才能按**数值**降序(否则字符串按字典序,"95" 会排在 "9" 后面导致排序错误)
+- `bw.write("第" + (i + 1) + "名:" + list.get(i))` 直接拼接对象会自动调用重写的 `toString()` 输出"姓名-成绩"
+
+**🎯 挑战加分(选做)**:
+1. **二次排序**:成绩相同时按姓名**字典序升序**(`result == 0 ? o1.getName().compareTo(o2.getName()) : result`)
+2. 让 `Student implements Comparable<Student>`,用 `TreeSet` 自动排序去重
+3. **脏数据过滤**:读取时跳过成绩不在 0~100 范围的行
+
+---
+
+## 三、挑战题(选做)
+
+### 题目 3:接口化文件复制效率对比器 CopyCompare(⭐⭐⭐⭐ 选做)
+
+**知识点**:`CopyFile` 接口(面向接口编程 / 多态)、`FileInputStream` / `FileOutputStream` 普通字节流、`BufferedInputStream` / `BufferedOutputStream` 字节缓冲流、`byte[]` 缓冲区循环复制、try-catch-finally 关闭资源、`System.currentTimeMillis()` 耗时对比、基础流 vs 扩展流
+
+**场景描述**:课堂用 `CopyFile` 接口统一了文件复制规范,并实现了两个版本:`Copy01`(普通字节流 + `byte[8192]` 数组)和 `BufferedCopy`(缓冲流包装底层流)。请定义 `CopyFile` 接口 + 实现这两个版本,复制同一个大文件,用耗时说话——**验证"缓冲流更快"**。
+
+**题目要求**:
+
+```java
+import java.io.*;
+
+/**
+ * @author 你的姓名
+ * @title 题目3:接口化文件复制效率对比器
+ * @create 2026/8/7
+ */
+public class Demo_03_姓名 {
+    // ========== ① 复制器接口:统一复制规范 ==========
+    interface CopyFile {
+        String copy(String src, String desc) throws IOException;
+    }
+
+    // ========== ② 普通字节流实现:FileInputStream + FileOutputStream + byte[8192] + try-catch-finally ==========
+    static class Copy01 implements CopyFile {
+        @Override
+        public String copy(String src, String desc) {
+            // TODO 1: FileOutputStream fos / FileInputStream fis 声明为 null,try 中创建
+            // TODO 2: byte[8192] 缓冲区 + while ((len = fis.read(bytes)) != -1) { fos.write(bytes, 0, len); }
+            // TODO 3: catch (IOException e) 返回 "文件复制失败"
+            // TODO 4: finally 中关闭 fos / fis(close 也可能抛 IOException → 包装成 RuntimeException)
+            return "文件复制成功";
+        }
+    }
+
+    // ========== ③ 缓冲流实现:BufferedOutputStream / BufferedInputStream 包装底层流 ==========
+    static class BufferedCopy implements CopyFile {
+        @Override
+        public String copy(String src, String desc) throws IOException {
+            // TODO 5: 先创建文件流,再用缓冲流包装(new BufferedOutputStream(fos))
+            // TODO 6: byte[8192] + while ((count = bis.read(bytes)) != -1) { bos.write(bytes, 0, count); }
+            // TODO 7: 关闭:先关缓冲流再关底层流
+            return "复制成功";
+        }
+    }
+
+    public static void main(String[] args) throws IOException {
+        String src = "D:/大文件.zip";   // 换成你电脑上任意大于 10MB 的文件
+        // TODO 8: 用 new Copy01().copy(src, "D:/copy1.zip") 复制,System.currentTimeMillis() 统计耗时
+        // TODO 9: 用 new BufferedCopy().copy(src, "D:/copy2.zip") 复制,统计耗时
+        // TODO 10: 输出两次耗时,对比哪个更快
+    }
+}
+```
+
+**预期输出示例**:
+
+```
+普通字节流耗时:486 毫秒
+缓冲流耗时:203 毫秒
+缓冲流更快!
+```
+
+**要求说明**:
+1. `CopyFile` 接口方法 `String copy(String src, String desc) throws IOException`
+2. 两个实现类都必须 `implements CopyFile`,体现**面向接口编程 / 多态**
+3. 复制前后对比源文件与目标文件大小,必须完全一致
+4. 用 `System.currentTimeMillis()` 分别统计两个版本的复制耗时并输出对比
+
+**💡 提示**:
+- `Copy01` 用 try-catch-finally 内部处理异常(返回提示信息);`BufferedCopy` 可以直接 `throws IOException`(接口允许两种异常处理风格并存)
+- 关闭顺序:`Copy01` 先关 fos 再关 fis;`BufferedCopy` 先关缓冲流再关底层流
+- 复制同一个大文件(视频、zip 等)效果更明显;小文件耗时差异很小
+
+**🎯 挑战加分(选做)**:
+1. **缓冲区大小对比**:把 `byte[]` 改成 1024 / 4096 / 8192 / 16384 各测一次,总结"缓冲区大小 → 耗时"规律
+2. **字符集乱码实验**:用 `FileWriter` 的 `Charset.forName("GBK")` 写一个中文文件,再用默认字符集的 `FileReader` 读取,观察乱码;改用 `FileReader(path, Charset.forName("GBK"))` 指定同一字符集读,验证乱码消失
+3. **IO 流体系梳理**:仿照课堂 Demo04 小结,画出今日 IO 流完整体系图(基础流:字节/字符 + 输入/输出;处理流:缓冲字节/缓冲字符),并说明每个流"处理什么类型、怎么配对包装"
+
+---
+
+## 四、提交要求
+
+- **类名**:`Demo_题号_姓名`(如 `Demo_01_张三`、`Demo_02_李四`、`Demo_03_王五`)
+- **文件头注释**:每个 Java 文件头部必须包含 `@author`(你的姓名)、`@title`(题目名称)、`@create 2026/8/7`
+- **提交内容**:源码文件 + 运行结果截图
+- **必做/选做**:基础题(题目 1)、进阶题(题目 2)为**必做**;挑战题(题目 3)为**选做**,鼓励学有余力的同学完成

+ 729 - 0
课堂笔记/01-JavaSE基础-笔记/20260807-笔记.md

@@ -0,0 +1,729 @@
+# 20260807 课堂笔记 — IO 字节缓冲流(BufferedOutputStream 输出 / BufferedInputStream 输入 + 内部缓冲区与 flush 刷新 + 基础流 vs 扩展流)+ 接口化文件复制(CopyFile 接口 + Copy01 普通字节流实现 / BufferedCopy 缓冲流实现 + System.currentTimeMillis 耗时对比评测)+ 字符流入门(字符集编码表 + Reader/Writer 抽象父类 + FileReader/FileWriter 构造与读写方法)+ 字符缓冲流(BufferedReader / BufferedWriter 包装字符流 + newLine() / readLine() 特有方法)+ IO 流体系小结(基础流 vs 处理流-缓冲流完整分类)+ 随堂练习参考解答(BufferedWriteReadTest 缓冲流写读统计 / ScoreSort 字符缓冲流读取排序写回)
+
+- **日期**:2026-08-07
+- **项目**:`c260807`
+- **包路径**:`course`
+- **作者**:WanJL
+
+---
+
+## 目录
+
+1. [接口化设计:CopyFile 接口 —— 统一文件复制的规范](#1-接口化设计copyfile-接口--统一文件复制的规范)
+2. [字节缓冲流概念:BufferedOutputStream / BufferedInputStream(基础流 vs 扩展流)](#2-字节缓冲流概念bufferedoutputstream--bufferedinputstream基础流-vs-扩展流)
+3. [Copy01 —— 普通字节流复制实现(byte[] 数组缓冲区 + try-catch-finally 关闭资源)](#3-copy01--普通字节流复制实现byte-数组缓冲区--try-catch-finally-关闭资源)
+4. [BufferedCopy —— 缓冲流复制实现(缓冲流包装底层字节流)](#4-bufferedcopy--缓冲流复制实现缓冲流包装底层字节流)
+5. [BufferedStreamTest —— 缓冲流写入 100 万字符(flush 刷新)与读取演示](#5-bufferedstreamtest--缓冲流写入-100-万字符flush-刷新与读取演示)
+6. [Demo01 —— Scanner 交互式写入(输入一行写一行)](#6-demo01--scanner-交互式写入输入一行写一行)
+7. [MyTest —— 耗时对比评测(接口多态 + System.currentTimeMillis)](#7-mytest--耗时对比评测接口多态--systemcurrenttimemillis)
+8. [字符流入门:字符集编码表 + Reader/Writer + FileReader/FileWriter(Demo02)](#8-字符流入门字符集编码表--readerwriter--filereaderfilewriterdemo02)
+9. [字符缓冲流:BufferedReader / BufferedWriter(Demo03)](#9-字符缓冲流bufferedreader--bufferedwriterdemo03)
+10. [IO 流体系小结:基础流 vs 处理流-缓冲流(Demo04)](#10-io-流体系小结基础流-vs-处理流-缓冲流demo04)
+11. [随堂练习参考解答:BufferedWriteReadTest / ScoreSort —— 缓冲流写读统计 + 字符缓冲流读取排序写回](#11-随堂练习参考解答bufferedwritereadtest--scoresort--缓冲流写读统计--字符缓冲流读取排序写回)
+12. [知识点全景总结](#12-知识点全景总结)
+13. [随堂练习要点](#13-随堂练习要点)
+14. [拓展阅读](#14-拓展阅读)
+
+---
+
+## 1. 接口化设计:CopyFile 接口 —— 统一文件复制的规范
+
+### 概念 —— 接口作为"文件复制"的契约
+
+在文件复制的场景中,先定义一个 **`CopyFile` 接口**(复制器接口),抽象出"复制文件"这个方法规范:传入**原路径 src** 与**新路径 desc**,返回一个**复制结果提示字符串**,并声明抛 `IOException`(因为实现类创建流、读写文件都可能抛编译期异常)。
+
+```java
+// 来源:course/CopyFile.java
+package course;
+
+public interface CopyFile {
+    /**
+     * 复制文件的方法
+     * @param src 原路径
+     * @param desc 新路径
+     * @return 复制结果提示
+     */
+    String copy(String src, String desc) throws IOException;
+}
+```
+
+> **要点**:
+> - 接口方法默认是 `public abstract`,实现类必须用 `public` 重写
+> - 方法声明 `throws IOException`:接口层面就声明了"此操作可能抛 IOException",实现类可以继续 throws,也可以 try-catch 处理后不再抛出
+> - 本日核心设计思想:**面向接口编程** —— 调用方(MyTest)只依赖 `CopyFile` 接口类型,具体用哪种复制实现可以随时替换(多态)
+
+---
+
+## 2. 字节缓冲流概念:BufferedOutputStream / BufferedInputStream(基础流 vs 扩展流)
+
+### 概念 —— 什么是字节缓冲流
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    字节缓冲流
+        BufferedOutputStream:这个类是实现缓冲输出流的类,通过设置这样的输出流,程序可以向底层输出流写入字节,
+        也就是说它针对的不是真正的文件,而是文件的输出流。
+        BufferedInputStream:这个类是实现缓冲输入流的类。通过设置输入流,可以向底层输入流读取数据。
+        这个类里面维护了一个字节缓冲区,内部缓冲区会根据需要,从包含的输入流中获取数据填充到缓冲区。一次可以读取多个字节。
+
+        public BufferedOutputStream(OutputStream out)   缓冲字节输出流的构造方法,参数是字节输出流
+        public BufferedInputStream(InputStream out)     缓冲字节输入流的构造方法,参数是字节输入流
+*/
+```
+
+### 关键概念 —— 包装流 / 装饰流
+
+- **BufferedOutputStream(缓冲字节输出流)**:针对的不是真正的文件,而是"文件的输出流"——它把 `FileOutputStream` 包装起来,先写入**内部缓冲区**,满了再统一刷到磁盘
+- **BufferedInputStream(缓冲字节输入流)**:内部维护一个**字节缓冲区**,一次从底层输入流读取多个字节填充缓冲区,后续的 `read()` 直接从内存缓冲区取,减少与磁盘的交互次数
+- **构造方法**:参数是**底层字节流**(OutputStream / InputStream),也就是先创建 `FileOutputStream` / `FileInputStream`,再用缓冲流把它们**包装**起来
+- **默认缓冲区大小**:8192 字节(8KB)
+
+> **要点**:缓冲流本质是**"包装流 / 装饰流"**——它不直接操作文件,而是给已存在的字节流"加一层缓冲",牺牲少量内存换取**大幅减少磁盘 I/O 次数**,从而显著提升读写效率。这正对上节课"byte[] 缓冲区循环复制"的思想:**缓冲 = 一次操作一批数据,而非一个字节一个字节**。
+
+### 补充概念 —— 基础流 vs 扩展流(20260807 代码同步更新)
+
+```java
+// 来源:course/Demo01.java(注释部分,本次更新新增)
+/*
+    FileInputStream和FileOutputStream属于基础流,他们是直接接触和操作文件的。
+    BufferedOutputStream和BufferedInputStream属于扩展流,他们是用来操作基础流的。
+*/
+```
+
+> **要点**:
+> - **基础流**(FileInputStream / FileOutputStream 等)**直接接触和操作文件**,是数据的"源头与终点"
+> - **扩展流**(BufferedOutputStream / BufferedInputStream 等)**不直接操作文件**,而是用来"操作基础流"的——在基础流之上叠加缓冲、转换等增强能力
+> - 这个"**基础流 + 扩展流**"的分工,正是后续所有高级流(字符流、转换流、对象流)的共同组织方式:先建基础流,再用扩展流包装它
+
+---
+
+## 3. Copy01 —— 普通字节流复制实现(byte[] 数组缓冲区 + try-catch-finally 关闭资源)
+
+### 实现 —— 实现 CopyFile 接口
+
+`Copy01 implements CopyFile`,在 `copy()` 方法内用 `FileInputStream` 读 + `FileOutputStream` 写 + `byte[8192]` 数组缓冲区循环复制,并用 **try-catch-finally** 保证资源一定关闭:
+
+```java
+// 来源:course/Copy01.java
+public class Copy01 implements CopyFile {
+    @Override
+    public String copy(String src, String desc) {
+        FileOutputStream fos = null;
+        FileInputStream fis = null;
+        try {
+            fos = new FileOutputStream(desc);
+            fis = new FileInputStream(src);
+            byte[] bytes = new byte[8192];
+            int byteCount;
+            while ((byteCount = fis.read(bytes)) != -1) {   // 读一批(-1 表示读完)
+                fos.write(bytes, 0, byteCount);             // 只写本次有效字节
+            }
+        } catch (IOException e) {
+            return "文件复制失败";                          // 复制失败返回提示
+        } finally {
+            try {
+                fos.close();                                // finally 中关闭资源
+                fis.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);              // 关闭失败包装成运行时异常
+            }
+        }
+        return "文件复制成功";
+    }
+}
+```
+
+> **要点**:
+> - **流变量在 try 外面声明为 null**,因为 finally 块要访问它们(作用域问题)
+> - **try-catch-finally**:`finally` 保证无论是否发生异常都会执行 `close()`;`catch (IOException)` 捕获复制失败返回提示信息
+> - **`IOException` 包装成 `RuntimeException`**:finally 里再抛的 IOException 属于编译期异常,用运行时异常包装后可以不强制上层处理
+> - 该实现没有把异常抛给调用方(接口声明了 throws,实现类可以选择 try-catch 消化掉),属于"方法内部自处理"风格
+
+---
+
+## 4. BufferedCopy —— 缓冲流复制实现(缓冲流包装底层字节流)
+
+### 实现 —— 用缓冲流替代普通数组缓冲区
+
+`BufferedCopy` 同样实现 `CopyFile` 接口,但用 **`BufferedInputStream` / `BufferedOutputStream`** 包装底层文件流,再用 `byte[8192]` 数组作为"二次缓冲":
+
+```java
+// 来源:course/BufferedCopy.java
+public class BufferedCopy implements CopyFile {
+    @Override
+    public String copy(String src, String desc) throws IOException {
+        //1、创建文件字节输出流和缓冲流对象
+        FileOutputStream fos = new FileOutputStream(desc);
+        BufferedOutputStream bos = new BufferedOutputStream(fos);   // 缓冲输出流包装文件输出流
+        //2、创建文件字节输入流和缓冲流对象
+        FileInputStream fis = new FileInputStream(src);
+        BufferedInputStream bis = new BufferedInputStream(fis);     // 缓冲输入流包装文件输入流
+
+        byte[] bytes = new byte[8192];
+        int count;
+        while ((count = bis.read(bytes)) != -1) {
+            bos.write(bytes, 0, count);
+        }
+        bis.close();
+        fis.close();
+        bos.close();
+        fos.close();
+        return "复制成功";
+    }
+}
+```
+
+### 两种实现的对比
+
+| 对比维度 | Copy01(普通字节流) | BufferedCopy(缓冲流) |
+|----------|---------------------|------------------------|
+| 核心流 | `FileInputStream` + `FileOutputStream` | 缓冲流包装 `FileInputStream` / `FileOutputStream` |
+| 缓冲区 | 手动 `byte[8192]` 数组 | 缓冲流**内部** 8KB 缓冲区 + 手动数组(双缓冲) |
+| 异常处理 | try-catch-finally 内部处理,返回提示 | 直接 `throws IOException` 交给调用方 |
+| 本质优化 | 减少磁盘交互次数 | 缓冲流再叠一层缓冲,进一步减少磁盘 I/O |
+
+> **要点**:
+> - **包装流程固定**:先建底层文件流 → 再建缓冲流包装它 → 操作缓冲流 → 关闭
+> - 关闭顺序:先关缓冲流(bis / bos)再关底层流(fis / fos);实际上 `close()` 缓冲流会自动关闭它包装的底层流,手写两层 close 更保险
+> - 该实现选择 `throws IOException`(接口声明抛异常),与 Copy01 的"内部消化"形成对照——**接口允许两种实现风格并存**
+
+---
+
+## 5. BufferedStreamTest —— 缓冲流写入 100 万字符(flush 刷新)与读取演示
+
+### 写端 —— 100 万随机大写字母 + 每 500 个换行并 flush
+
+```java
+// 来源:course/BufferedStreamTest.java(写端,已注释保留)
+FileOutputStream fos = new FileOutputStream("D:/abc.txt");
+BufferedOutputStream bos = new BufferedOutputStream(fos);   // 缓冲流包装文件输出流
+Random ran = new Random();
+for (int i = 0; i < 1000000; i++) {
+    int c = ran.nextInt(26) + 65;         // 随机生成大写字母 A~Z(65~90)
+    bos.write(c);                          // 先写入内部缓冲区
+    if (i % 500 == 0) {
+        bos.write("\r\n".getBytes());      // 每 500 个字母写一个换行
+        bos.flush();                       // 手动把缓冲区数据刷到磁盘
+    }
+}
+bos.close();
+fos.close();
+```
+
+### 读端 —— 缓冲流逐字节读取显示
+
+```java
+// 来源:course/BufferedStreamTest.java(读端,活动代码)
+FileInputStream fis = new FileInputStream("D:/abc.txt");
+BufferedInputStream bis = new BufferedInputStream(fis);     // 缓冲流包装文件输入流
+int b;
+while ((b = bis.read()) != -1) {
+    System.out.print((char) b);            // 读到的字节强转成字符输出
+}
+bis.close();
+fis.close();
+```
+
+> **要点**:
+> - **`flush()` 刷新缓冲区**:缓冲流默认**缓冲区满了才自动写入磁盘**,`flush()` 可以**手动强制**把当前缓冲区数据刷到磁盘(这里每 500 个字符刷一次,保证数据及时落盘)
+> - **`read()` 逐字节读也很快**:虽然 `bis.read()` 每次只返回一个字节,但数据**预先成批读入内部缓冲区**,逐字节读取实际上是从内存缓冲区取,性能远高于无缓冲的逐字节磁盘读取
+> - **`(char) b` 强转**:读取到的是 int(字节值),转成 char 才能显示为字母
+> - **Random 随机字母**:`ran.nextInt(26) + 65` 生成 65~90 的 ASCII 码,对应 'A'~'Z'
+
+---
+
+## 6. Demo01 —— Scanner 交互式写入(输入一行写一行)
+
+### 实现 —— 控制台输入即写入文件
+
+```java
+// 来源:course/Demo01.java
+FileOutputStream fos = new FileOutputStream("D:abc.txt");
+Scanner sc = new Scanner(System.in);
+while (true) {
+    System.out.println("请写入内容:");
+    String s = sc.nextLine();           // 读取一行输入
+    fos.write(s.getBytes());            // 字符串转字节数组写入文件
+}
+```
+
+> **要点**:
+> - **Scanner + IO 结合**:键盘输入 → `getBytes()` 转字节数组 → `fos.write()` 写入文件,形成"控制台 → 文件"的数据流动
+> - 无限 `while(true)` 循环持续写入,直到强制终止程序(Ctrl+C)
+> - ⚠️ **注意**:这里没有 `close()` 关闭流——这是教学演示,实际开发中流必须关闭,否则**文件句柄泄漏、数据可能未刷入磁盘**
+
+---
+
+## 7. MyTest —— 耗时对比评测(接口多态 + System.currentTimeMillis)
+
+### 实现 —— 用缓冲流实现复制大文件并统计耗时
+
+```java
+// 来源:course/MyTest.java
+public static void main(String[] args) throws IOException {
+    String src = "D:\\CentOS7\\SpringCloudAlibaba-CentOS7.zip";   // 源文件(大文件)
+    String desc = "E:\\SpringCloudAlibaba-CentOS7.zip";            // 目标文件
+    long start = System.currentTimeMillis();
+    String message = new BufferedCopy().copy(src, desc);           // 接口实现的多态调用
+    long end = System.currentTimeMillis();
+    System.out.println(message + "耗时" + (end - start) + "毫秒");
+}
+```
+
+> **要点**:
+> - **多态体现**:`new BufferedCopy()` 直接调用 `copy()` 方法;因为 Copy01 与 BufferedCopy 都实现 `CopyFile` 接口,只要把 new 的对象换成 `new Copy01()` 即可对比两种实现的复制耗时——**接口让"换实现"零成本**
+> - **耗时统计**:`System.currentTimeMillis()` 复制前后各取一次毫秒数相减,即可得到复制耗时(毫秒)
+> - **对比思路**:分别用 Copy01(普通字节流)与 BufferedCopy(缓冲流)复制同一个大文件(如 SpringCloudAlibaba-CentOS7.zip),对比哪个更快——缓冲流通常显著占优
+
+---
+
+## 8. 字符流入门:字符集编码表 + Reader/Writer + FileReader/FileWriter(Demo02)
+
+### 概念 —— 为什么需要字符流
+
+由于**字节流操作字符(特别是中文)不方便**,所以 Java 提供了**字符流**。字符流的本质公式:**字符流 = 字节流 + 字符集编码表**。
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    由于字节流操作字符(特别是中文)不方便,所以Java提供了字符流
+    字符流 = 字节流 + 字符集编码表
+*/
+```
+
+### 字符集编码表(Charset)
+
+**字符集编码表**是一个系统支持的**所有字符的集合**,包括各个国家的文字、符号、图像符号、数字等等。计算机要识别这些文字和符号,就需要存储一套编码表,然后按照表中的对应关系,存储对应的字符编码。
+
+常见的字符编码:**ASCII、GB-2312、GBK、Unicode(UTF-8)**。
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    什么是字符集编码表?
+    是一个系统支持的所有的字符的集合 ,包括各个国家的文字、符号、图像符号、数字等等
+    计算机要像识别这些文字和符号,就需要存储一套编码表,然后按照表中的对应关系,存储对应的字符编码。
+    常见的字符编码:ASCII、GB-2312、GBK、Unicode(UTF-8)
+*/
+```
+
+### String 类提供的编码 / 解码方法
+
+```java
+// 来源:course/Demo02.java(注释部分)
+// 编码(Encoding):字符串 → 字节数组,String 类提供
+public byte[] getBytes()                        // 使用平台(操作系统)默认字符集转换
+public byte[] getBytes(Charset charset)         // 使用指定的字符集转换
+public byte[] getBytes(String charsetName)      // 使用指定的字符集名称转换
+
+// 解码(Decoding):字节数组 → 字符串,String 类构造方法
+public String(byte[] bytes)                     // 使用平台(操作系统)默认字符集解码
+public String(byte[] bytes, Charset charset)    // 使用指定的字符集解码
+public String(byte[] bytes, String charsetName) // 使用指定的字符集名称解码
+```
+
+> **要点**:编码是**字符串 → 字节**,解码是**字节 → 字符串**;同一段字节用不同编码表解码会得到不同结果(乱码问题的根源)。
+
+### 字符流体系 —— Reader / Writer 两大抽象父类
+
+与字节流(InputStream / OutputStream)对称,字符流也分输入、输出两大体系,子类名同样以父类名作后缀:
+
+```
+字符流:
+    |- 输入字符流:Reader 是输入字符流的抽象类
+        |- 文件输入字符流:FileReader
+    |- 输出字符流:Writer 是输出字符流的抽象类
+        |- 文件输出字符流:FileWriter
+```
+
+### FileReader / FileWriter 的构造方法
+
+```java
+// 来源:course/Demo02.java(注释部分)
+// FileReader 三种构造方法
+public FileReader(String fileName)                  // 根据文件名创建,使用系统默认字符集
+public FileReader(File file)                        // 根据 File 对象创建,使用系统默认字符集
+public FileReader(String fileName, Charset charset) // 根据文件名创建,使用指定字符集
+
+// FileWriter 四种构造方法(含 append 追加)
+public FileWriter(String fileName)                  // 根据文件名创建,使用系统默认字符集
+public FileWriter(File file)                        // 根据 File 对象创建,使用系统默认字符集
+public FileWriter(File file, boolean append)        // 根据 File 对象创建,append=true 可追加
+public FileWriter(String fileName, Charset charset) // 根据文件名创建,使用指定字符集
+```
+
+### 字符流读写方法(与字节流对照)
+
+```java
+// 来源:course/Demo02.java(注释部分)
+// 写方法:写入一个字符 / 字符数组 / 字符串
+write(int c)                       // 写一个字符
+write(char[] c)                    // 写一个字符数组的内容
+write(char[] c, int off, int len)  // 写字符数组的内容,从 off 开始,长度为 len
+write(String str)                  // 写一个字符串
+write(String str, int off, int len)// 写字符串的一部分,从 off 开始,长度为 len
+flush()                            // 刷新流,把缓冲区的内容写入到文件中
+close()                            // 关闭流
+
+// 读方法
+read()                             // 读取一个字符
+read(char[] c, int off, int len)   // 读取一部分字符数组
+```
+
+> **要点**:
+> - **字符流自带缓冲区**,写操作后需要 `flush()` 刷新,强制把缓冲区内容写入文件
+> - 字符流可以直接 `write(String str)` 写字符串,**不需要先 `getBytes()` 转字节**——这是它操作文本(尤其中文)比字节流方便的根本原因
+> - FileReader / FileWriter 构造时可**指定字符集**(Charset),解决不同编码文件的乱码问题
+
+### FileWriterTest —— 写演示(六种写方法实战)
+
+```java
+// 来源:course/FileWriterTest.java
+FileWriter fw = new FileWriter("D:/abc.txt");
+fw.write(97);                              // write(int c) 写一个字符(ASCII 97 = 'a')
+char[] chars = {'a','b','c','d','e'};
+fw.write(chars);                           // write(char[] c) 写一个字符数组
+fw.write(chars, 3, 2);                     // write(char[] c, int off, int len) 从 off 开始写 len 个 → 'd' 'e'
+fw.flush();                                // flush() 刷新流,把缓冲区内容写入文件
+fw.write("Hello world");                   // write(String str) 直接写字符串(无需 getBytes)
+fw.write("qwertyuiopasdfghjklxcvbnm", 3, 6);// write(String str, int off, int len) 写字符串子串 → "rtyuio"
+fw.close();                                // close() 关闭流
+```
+
+### FileReaderTest —— 读演示(两种读法)
+
+```java
+// 来源:course/FileReaderTest.java
+FileReader fr = new FileReader("D:/abc.txt");
+
+// 读法一:read() 逐字符读取(一次一个字符,返回 -1 表示读完)
+int ch;
+while ((ch = fr.read()) != -1) {
+    System.out.print((char) ch);
+}
+
+// 读法二:read(char[] c, int off, int len) 读入字符数组(推荐,批量读取)
+char[] chars = new char[1024];
+int count;
+while ((count = fr.read(chars)) != -1) {
+    System.out.print(new String(chars, 0, count));   // 字符数组 → 字符串
+}
+fr.close();                                        // 释放资源
+```
+
+> **要点**:
+> - **FileWriter 直接 `write(String)`**:字符流写字符串**无需 `getBytes()` 转换**,`write(String, off, len)` 还可写子串
+> - **FileReader 与 FileInputStream 结构一致**:`read()` 返回 -1 表示读到末尾(EOF);`read(char[])` 返回本次有效字符数
+> - **`new String(chars, 0, count)`**:把本次读到的有效字符数组转成字符串,避免把缓冲区残留数据也转进去(与字节流 `new String(bytes, 0, length, UTF-8)` 同理)
+> - **flush() 与 close()**:`flush()` 之后还可以继续 write;`close()` 之前会自动刷新一次缓冲区
+
+---
+
+## 9. 字符缓冲流:BufferedReader / BufferedWriter(Demo03)
+
+### 概念 —— 给字符流再加缓冲
+
+字符缓冲流是**字符流(Reader/Writer)的缓冲版本**:`BufferedWriter` 把文本写入字符输出流并缓冲字符,`BufferedReader` 从字符输入流读取文本并缓冲字符,提供**高效读写**。可以指定缓冲区大小,也可以接受默认大小(默认值足够大,可满足大多数用途)。
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    BufferedWriter  把文本写入字符输出流,缓冲字符。提供高效写入。可以指定缓冲区的大小,或者可以接受默认大小。默认值足够大,可以用在大多数用途中。
+    BufferedReader  从字符输入流中读取文本,缓冲字符。提供高效读取。可以指定缓冲区的大小,或者可以接受默认大小。默认值足够大,可以用在大多数用途中。
+
+    public BufferedReader(Reader in)              创建缓冲字符输入流对象
+    public BufferedReader(Reader in, int sz)      创建缓冲字符输入流对象,指定缓冲区的大小
+    public BufferedWriter(Writer out)             创建缓冲字符输出流对象
+    public BufferedWriter(Writer out, int sz)     创建缓冲字符输出流对象,指定缓冲区的大小
+*/
+```
+
+### 字符缓冲流特有的方法
+
+| 流 | 特有方法 | 说明 |
+|----|----------|------|
+| `BufferedWriter` | `newLine()` | 写一行**行分隔符**,行分隔符字符串根据**系统属性**定义(跨平台) |
+| `BufferedReader` | `readLine()` | 读取一行字符串(**不含终止字符**);如果已到流的末尾,返回 **null** |
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    字符缓冲流特有的方法:
+    BufferedWriter:
+    public void newLine() 写一行行分隔符,行分隔符字符串根据系统属性进行定义
+    BufferedReader:
+    public String readLine() 读取一行字符串,包含行的内容的字符串,不包括任何终止字符,
+    如果已经到了流的末尾,则为null
+*/
+```
+
+### main 演示 —— 包装 FileWriter/FileReader + newLine() 写行 + readLine() 读行
+
+```java
+// 来源:course/Demo03.java
+BufferedWriter bw = new BufferedWriter(new FileWriter("D:/abc.txt"));
+BufferedReader br = new BufferedReader(new FileReader("D:/abc.txt"));
+
+// 写:write 各种写法 + newLine() 换行(跨平台行分隔符)
+bw.write(97);
+bw.write("hello world\r\n");
+bw.write("白日依山尽");
+bw.newLine();
+bw.write("黄河入海流");
+bw.newLine();
+bw.write("哈哈哈哈哈");
+bw.newLine();
+bw.write("自挂东南枝");
+bw.newLine();
+
+bw.flush(); // 写完还没有关闭流,缓冲区还没有满,这时候不会写入到文件中的,所以需要 flush 强制刷新到文件中。
+
+// 读:readLine() 逐行读取,-1 的字符流版本是 null
+String line;    // 行
+while ((line = br.readLine()) != null) {
+    System.out.println(line);
+}
+
+bw.close();
+br.close();
+```
+
+> **要点**:
+> - **`readLine()` 循环读行的经典写法**:`while ((line = br.readLine()) != null)` —— 读取一行,到流末尾返回 **null**(对应字节流的 -1)
+> - **`newLine()` 跨平台换行**:不用手写 `"\r\n"`,`newLine()` 会根据**操作系统属性**自动写对应的行分隔符(Windows `\r\n` / Linux `\n`)
+> - **`flush()` 的作用再次体现**:写完未关流且缓冲区未满时数据不会自动落盘,必须 `flush()` 强制刷新
+> - 与字节缓冲流(BufferedInputStream/BufferedOutputStream)对照:**字符缓冲流 = 字符流 + 缓冲区**,构造参数是 Reader/Writer 类型(同样可链式包装 `new BufferedReader(new FileReader(...))`)
+
+---
+
+## 10. IO 流体系小结:基础流 vs 处理流-缓冲流(Demo04)
+
+### 概念 —— 今日 IO 流知识全景
+
+`Demo04` 是本日 IO 流知识的**小结**,把今天学的所有流按"基础流 / 处理流(扩展流)"和"字节 / 字符"两个维度归类,形成完整体系:
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    基础流两种:
+        字节流:	处理的是可以任意类型的文件
+            输出字节流	OutputStream
+                文件输出字节流:FileOutputStream
+            输入字节流	InputStream
+                文件输入字节流:FileInputStream
+        字符流:	处理的是纯文本文件
+            输入字符流	Reader
+                文件输入字符流:FileReader
+            输出字符流	Writer
+                文件输出字符流:FileWriter
+    处理流(扩展流)-缓冲流两种:
+        缓冲字节流:	处理的是字节输入流和字节输出流。
+            缓冲字节输入流:BufferedInputStream
+            缓冲字节输出流:BufferedOutputStream
+        缓冲字符流:	处理的是字符输入流和字符输出流。
+            缓冲字符输入流:BufferedReader
+            缓冲字符输出流:BufferedWriter
+*/
+```
+
+### IO 流体系总览图
+
+```
+                                    ┌─ 字节流(处理任意类型文件)
+                                    │    ├─ 输出字节流 OutputStream
+                                    │    │    └─ FileOutputStream(文件字节输出流)
+                                    │    └─ 输入字节流 InputStream
+                                    │         └─ FileInputStream(文件字节输入流)
+IO 流 ── 基础流 ────────────────────┤
+                                    │    ├─ 输入字符流 Reader
+                                    │    │    └─ FileReader(文件字符输入流)
+                                    └─ 字符流(处理纯文本文件)
+                                         └─ 输出字符流 Writer
+                                              └─ FileWriter(文件字符输出流)
+
+         ── 处理流(扩展流)─ 缓冲流
+                                    ├─ 缓冲字节流(处理字节输入/输出流)
+                                    │    ├─ BufferedInputStream(缓冲字节输入流)
+                                    │    └─ BufferedOutputStream(缓冲字节输出流)
+                                    └─ 缓冲字符流(处理字符输入/输出流)
+                                         ├─ BufferedReader(缓冲字符输入流)
+                                         └─ BufferedWriter(缓冲字符输出流)
+```
+
+> **要点**:
+> - **两大分类维度**:按数据单位分**字节流 / 字符流**(字节流处理任意类型文件、字符流处理纯文本文件);按是否直接操作底层分**基础流 / 处理流(扩展流)**
+> - **命名规律**:文件流子类名以父类名作后缀(FileOutputStream / FileInputStream / FileReader / FileWriter);缓冲流以 Buffered 前缀 + 父类名(BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter)
+> - **处理流(缓冲流)"处理"的是同类型的流**:缓冲字节流处理字节输入/输出流,缓冲字符流处理字符输入/输出流——构造参数对应(BufferedInputStream(InputStream) 配 FileInputStream,BufferedReader(Reader) 配 FileReader)
+> - **选型口诀**:不确定类型用字节流;纯文本用字符流;要效率再加对应的缓冲流
+
+---
+
+## 11. 随堂练习参考解答:BufferedWriteReadTest / ScoreSort —— 缓冲流写读统计 + 字符缓冲流读取排序写回
+
+### 练习题目回顾
+
+用 `BufferedOutputStream` 把 **100 万随机大写字母**写入 `D:/letters.txt`(每 500 个换行并 flush),再用 `BufferedInputStream` 逐字节读回,**统计每个大写字母出现次数**,并找出出现**最多**的字母。
+
+### 写端 —— 100 万随机大写字母 + 每 500 个换行 + flush
+
+```java
+// 来源:exericse/BufferedWriteReadTest.java(写端)
+String path = "D:/letters.txt";
+Random ran = new Random();
+BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));  // 链式包装
+for (int i = 0; i < 1000000; i++) {
+    int c = ran.nextInt(26) + 65;      // 随机大写字母 A~Z
+    bos.write(c);
+    if (i % 500 == 0) {
+        bos.write("\r\n".getBytes());  // 每 500 个写换行
+        bos.flush();                   // 刷新到磁盘
+    }
+}
+bos.close();
+```
+
+### 读端 —— 字母频次统计 int[26] + 找最多
+
+```java
+// 来源:exericse/BufferedWriteReadTest.java(读端)
+BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
+int[] count = new int[26];             // 26 个计数器,下标 0~25 对应 A~Z
+int b;
+while ((b = bis.read()) != -1) {
+    if (b >= 'A' && b <= 'Z')
+        count[b - 'A']++;              // 核心技巧:用 ASCII 偏移量定位数组下标
+}
+int max = count[0];
+for (int i = 0; i < count.length; i++) {
+    System.out.println(count[i]);
+    if (max < count[i]) max = count[i];
+}
+System.out.println("最多的是" + max + "次");
+bis.close();
+```
+
+> **要点**:
+> - **字母频次统计的核心技巧**:`count[b - 'A']++` —— 把字符的 ASCII 码减去 'A' 得到 0~25 的下标,天然把"字母"映射到"数组下标",一次遍历完成统计(类似哈希思想)
+> - 读回时用 `if (b >= 'A' && b <= 'Z')` **过滤掉换行符**(\r\n),只统计大写字母
+> - 缓冲流构造可以**链式包装**:`new BufferedOutputStream(new FileOutputStream(path))` 一行完成"创建文件流 + 缓冲流包装"
+
+### 练习六参考解答 —— ScoreSort / Student:字符缓冲流读取 + 排序 + 写回
+
+实体类 `Student`(姓名 + 成绩)与主程序 `ScoreSort` 是本日练习六"用字符缓冲流读取文件数据、排序后写回"的参考解答,完整串起**字符缓冲流 + 集合 + 排序 + 写文件**的五步闭环:
+
+```java
+// 来源:exericse/Student.java(实体类)
+public class Student {
+    private String name;
+    private int score;
+    public Student(String name, int score) {
+        this.name = name;
+        this.score = score;
+    }
+    public String getName() { return name; }
+    public int getScore() { return score; }
+    @Override
+    public String toString() {
+        return name + "-" + score;    // "姓名-成绩"
+    }
+}
+```
+
+```java
+// 来源:exericse/ScoreSort.java
+BufferedReader br = new BufferedReader(new FileReader("D:/scores.txt"));  // 1、读取
+ArrayList<Student> list = new ArrayList<>();
+String line;
+while ((line = br.readLine()) != null) {          // 逐行读取
+    String[] parts = line.split("-");             // 2、处理:拆分姓名-成绩
+    String name = parts[0];
+    int score = Integer.parseInt(parts[1]);       //    字符串 → 整数
+    list.add(new Student(name, score));           // 3、封装对象存入集合
+}
+Collections.sort(list, ((o1, o2) -> o2.getScore() - o1.getScore()));  // 4、排序:Lambda 按成绩降序
+
+BufferedWriter bw = new BufferedWriter(new FileWriter("D:/sorted.txt")); // 5、写回
+for (int i = 0; i < list.size(); i++) {
+    bw.write("第" + (i + 1) + "名:" + list.get(i).toString());
+    bw.newLine();                                 // 跨平台换行
+}
+bw.flush();
+bw.close();
+br.close();
+System.out.println("排序完成!结果已写入 D:/sorted.txt");
+```
+
+> **要点**:
+> - **`Collections.sort(list, (o1, o2) -> o2.getScore() - o1.getScore())`**:Comparator 函数式接口用 **Lambda** 写法替代匿名内部类(对应前面学过的 Lambda + 函数式编程),`o2 - o1` 即**降序**(交换参数)
+> - **`line.split("-")` + `Integer.parseInt()`**:把"姓名-成绩"一行拆成两部分,成绩字符串转整数后才能按**数值**排序(否则按字典序,"95" 会排在 "9" 后面)
+> - **`toString()` 重写**:`bw.write("第" + (i + 1) + "名:" + list.get(i))` 直接拼接对象会自动调用重写的 `toString()` 输出"姓名-成绩"
+
+---
+
+## 12. 知识点全景总结
+
+本课知识点围绕"**更快更规范地复制文件**"展开,一张表看清"知识 → 代码位置":
+
+| 知识点 | 具体体现 | 源码位置 |
+|--------|----------|----------|
+| **接口定义(契约)** | `interface CopyFile`:`String copy(String src, String desc) throws IOException`,抽象文件复制规范 | course/CopyFile |
+| **面向接口编程 / 多态** | Copy01 / BufferedCopy 两个类实现同一接口;MyTest 中可自由替换实现对比耗时 | course/Copy01、BufferedCopy、MyTest |
+| **字节缓冲流 BufferedOutputStream** | `new BufferedOutputStream(fos)` 包装文件输出流,先写内部缓冲区再落盘 | course/BufferedCopy、BufferedStreamTest |
+| **字节缓冲流 BufferedInputStream** | `new BufferedInputStream(fis)` 包装文件输入流,一次读多个字节填充缓冲区 | course/BufferedCopy、BufferedStreamTest |
+| **缓冲流构造方法** | `public BufferedOutputStream(OutputStream out)` / `public BufferedInputStream(InputStream out)`,参数是底层字节流 | course/Demo01 注释 |
+| **内部缓冲区** | 缓冲流默认 8KB(8192)缓冲区,减少磁盘 I/O 次数提升效率 | course/BufferedCopy、BufferedStreamTest |
+| **基础流 vs 扩展流** | FileInputStream/FileOutputStream 是基础流**直接操作文件**;BufferedInputStream/BufferedOutputStream 是扩展流**操作基础流** | course/Demo01 注释 |
+| **flush() 刷新** | 缓冲区未满时手动 `flush()` 强制写入磁盘 | course/BufferedStreamTest |
+| **缓冲区循环复制** | `while ((count = bis.read(bytes)) != -1) { bos.write(bytes, 0, count); }` | course/BufferedCopy、Copy01 |
+| **try-catch-finally 关闭资源** | 流变量 null 声明 + finally 中 close + IOException 包装 RuntimeException | course/Copy01 |
+| **throws 声明 vs 内部捕获** | BufferedCopy 向上 throws IOException;Copy01 内部 try-catch 返回结果提示 | course/BufferedCopy、Copy01 |
+| **关闭顺序** | 先关缓冲流再关底层流(close 缓冲流会自动关闭底层流) | course/BufferedCopy、BufferedStreamTest |
+| **Scanner 交互写入** | `sc.nextLine()` 读取输入 → `getBytes()` → `fos.write()` 写入文件 | course/Demo01 |
+| **耗时统计** | `System.currentTimeMillis()` 相减统计复制耗时,对比两种实现效率 | course/MyTest |
+| **字符流概念** | 字符流 = 字节流 + 字符集编码表;字节流操作中文不方便,故提供字符流 | course/Demo02 |
+| **字符集编码表** | ASCII / GB-2312 / GBK / Unicode(UTF-8);String 编码 `getBytes()` / 解码 `new String(byte[])` 三种重载 | course/Demo02 |
+| **字符流体系** | Reader(输入字符流抽象类)→ FileReader;Writer(输出字符流抽象类)→ FileWriter;子类名以父类名作后缀 | course/Demo02 |
+| **字符流构造与读写** | FileReader/FileWriter 构造(可指定字符集 / append 追加);write(int/char[]/String)/read()/flush()/close();字符流自带缓冲区 | course/Demo02 |
+| **FileWriter 写演示** | write(int)/write(char[])/write(char[],off,len)/write(String)/write(String,off,len) 六种写方法 + flush() + close();写字符串无需 getBytes | course/FileWriterTest |
+| **字符缓冲流 BufferedWriter** | `new BufferedWriter(Writer out)` 包装字符输出流 + 缓冲;特有方法 `newLine()` 按系统属性写跨平台行分隔符 | course/Demo03 |
+| **字符缓冲流 BufferedReader** | `new BufferedReader(Reader in)` 包装字符输入流 + 缓冲;特有方法 `readLine()` 读取一行(不含终止符),到末尾返回 null | course/Demo03 |
+| **readLine() 循环读行** | `while ((line = br.readLine()) != null) { ... }` —— 字符流的逐行读取(对应字节流 read 返回 -1) | course/Demo03 |
+| **IO 流体系小结** | 基础流(字节流处理任意文件 / 字符流处理纯文本)vs 处理流-缓冲流(缓冲字节流 / 缓冲字符流)完整分类 | course/Demo04 |
+| **Lambda 排序** | `Collections.sort(list, (o1, o2) -> o2.getScore() - o1.getScore())` Comparator 函数式接口 Lambda 写法按成绩降序 | exericse/ScoreSort |
+| **读取排序写回闭环** | BufferedReader 读取 → split 拆分 → Integer.parseInt 转换 → ArrayList 存对象 → sort 排序 → BufferedWriter newLine 写回 | exericse/ScoreSort、Student |
+| **FileReader 读演示** | read() 逐字符读取(返回 -1 表示 EOF)/ read(char[]) 读入字符数组;new String(chars,0,count) 转字符串;fr.close() 释放资源 | course/FileReaderTest |
+| **字母频次统计** | `int[26]` + `count[b-'A']++` 一次遍历统计;`if (b >= 'A' && b <= 'Z')` 过滤换行符;找出最大值 | exericse/BufferedWriteReadTest |
+
+---
+
+## 13. 随堂练习要点
+
+- 用 `Copy01` 和 `BufferedCopy` 分别复制**同一个大文件**(视频 / zip / exe),用 `System.currentTimeMillis()` 统计耗时,对比普通字节流 vs 缓冲流的复制效率;再尝试改变 `byte[]` 数组大小(1024 / 4096 / 8192 / 16384)观察耗时变化
+- 用 `BufferedOutputStream` 写入 100 万字符并**每 500 个字符 `flush()` 一次**;去掉 flush 对比文件内容差异,理解 `flush()` 的作用(缓冲区未满时数据不会自动落盘)
+- 把 `BufferedStreamTest` 写端注释取消,用 `BufferedInputStream` 读回文件内容,验证写入读取闭环
+- 参考 `Copy01` 的 try-catch-finally 写法,把 `BufferedCopy` 也改造成"内部捕获异常返回结果提示"的版本,对比两种异常处理风格
+- 思考:`Copy01` 只用 `byte[8192]` 数组、`BufferedCopy` 在缓冲流之上再加 `byte[8192]` 数组,两层缓冲是否必要?去掉外层数组(缓冲流逐字节 write/read)耗时会不会有明显变化?
+- 用接口变量接收实现:`CopyFile cf = new BufferedCopy(); cf.copy(src, desc);`,再换成 `new Copy01()`,体会面向接口编程的替换性
+- 完成 `exericse/BufferedWriteReadTest` 练习(参考第 9 节解答):100 万随机大写字母写入 + 逐字节读回 + `int[26]` 字母频次统计 + 找出现最多的字母
+- 字符流练习:用 `FileWriter` 写入一段**中文文本**(体会无需 `getBytes()` 直接 `write(String)`),再用 `FileReader` 读回显示,对比字节流操作中文的差异
+- 字符集练习:用 `FileWriter(..., Charset.forName("GBK"))` 写一个文件,再用默认字符集的 `FileReader` 读取,观察乱码现象;改用 `FileReader(..., Charset.forName("GBK"))` 指定同一字符集读,验证乱码消失
+- 在 `BufferedWriteReadTest` 基础上拓展:把字母频次**按出现次数从高到低排序**输出字母排行(`int[]` + 冒泡/选择排序或 `HashMap` + 比较器)
+- 字符缓冲流练习:把 `FileWriterTest` / `FileReaderTest` 改写为 `BufferedWriter` / `BufferedReader` 版本——用 `newLine()` 写多行中文(体会跨平台换行),用 `readLine()` 逐行读回(体会 `while ((line = br.readLine()) != null)` 写法)
+- 字符缓冲流对比练习:分别用 `FileReader`(read(char[]))与 `BufferedReader`(readLine())读取同一个文件,对比两种写法的代码简洁度;思考 `readLine()` 为什么能"一次读一行"
+- 综合练习(参考第 11 节 ScoreSort 解答):完成"字符缓冲流读取文件 → 排序 → 写回"——`D:/scores.txt` 中"姓名-成绩"乱序,用 `BufferedReader` 逐行读 + `split("-")` 拆分 + `ArrayList` 存 `Student` 对象 + `Collections.sort`(Lambda)按成绩降序 + `BufferedWriter` 写回 `D:/sorted.txt` 带名次结果
+- 体系梳理练习:仿照 `Demo04` 小结,自己画出今日 IO 流的完整体系图(基础流:字节/字符 + 输入/输出;处理流:缓冲字节/缓冲字符),并说出每个流"处理什么类型、怎么配对包装"
+
+---
+
+## 14. 拓展阅读
+
+- **JDK 文档**:[`java.io.BufferedInputStream`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/BufferedInputStream.html)、[`java.io.BufferedOutputStream`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/BufferedOutputStream.html)、[`java.io.Reader`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Reader.html)、[`java.io.Writer`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Writer.html)、[`java.io.FileReader`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/FileReader.html)、[`java.io.FileWriter`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/FileWriter.html)、[`java.io.BufferedReader`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/BufferedReader.html)、[`java.io.BufferedWriter`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/BufferedWriter.html)
+- **缓冲流的本质**:缓冲流是"装饰器模式(Decorator)"在 Java I/O 中的经典应用——通过包装已有的流对象,在不改变流接口的前提下**动态增强功能**(增加缓冲能力)
+- **try-with-resources(JDK7+)**:`try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src))) { ... }` 可在 try 结束时**自动关闭所有流**,无需手写 finally close,是更现代的资源管理方式
+- **关闭流的传递性**:调用 `bis.close()` 会级联关闭内部包装的 `fis`,因此生产代码通常只需关闭最外层流;但为了保证幂等与可读性,逐层显式关闭也常见
+- **字符缓冲流**:字节缓冲流之后,还有配套的**字符缓冲流** `BufferedReader` / `BufferedWriter`,其中 `BufferedReader.readLine()` 一次读一行,非常适合纯文本文件;`BufferedWriter.newLine()` 自动写系统对应的行分隔符
+- **`flush()` 与 `close()` 的关系**:`close()` 之前会自动调用一次 `flush()` 把残留数据写入磁盘,所以只写不 flush 只要 close 也能完整落盘;`flush()` 用于"不关闭流但需要及时看到数据"的场景
+- **字符流 vs 字节流选型**:纯文本(.txt)用字符流(读中文方便、可直接 write(String));图片 / 视频等二进制文件用字节流;字符流底层仍基于字节,由**编码表**负责"字节 ↔ 字符"的转换
+- **乱码与字符集**:文件保存编码(如 GBK)与读取时指定编码(如 UTF-8)不一致就会产生乱码;`FileReader` / `FileWriter` 构造时用 `Charset` 显式指定编码可避免
+- **IO 流学习路线**:File(路径管理)→ 文件字节流(FileInputStream/FileOutputStream)→ 字节缓冲流(BufferedInputStream/BufferedOutputStream)→ 字符流(Reader/Writer + FileReader/FileWriter)→ 字符缓冲流(BufferedReader/BufferedWriter)→ 转换流 / 对象流 / 打印流等,缓冲流是"性能优化"的重要一环

+ 716 - 0
课堂练习题/20260807-IO缓冲流练习.md

@@ -0,0 +1,716 @@
+# 2026-08-07 IO 字节缓冲流 + 字符流练习(课堂练习用)
+
+> **说明**:练习今日新学知识点——**IO 字节缓冲流**(`BufferedOutputStream` / `BufferedInputStream`:内部缓冲区、`flush()` 刷新)与**接口化文件复制**(`CopyFile` 接口 + `Copy01` 普通字节流实现 / `BufferedCopy` 缓冲流实现):try-catch-finally 关闭资源、`System.currentTimeMillis()` 耗时对比;以及**字符流**(`Reader` / `Writer` + `FileReader` / `FileWriter`:直接 `write(String)` 写中文无需 `getBytes()`、字符流 = 字节流 + 字符集编码表、`Charset` 字符集与乱码原理)与**字符缓冲流**(`BufferedReader` / `BufferedWriter`:`readLine()` 逐行读取 / `newLine()` 换行、读 → 处理 → 集合 → 排序 → 写回文件)。
+
+---
+
+## 练习一:文件复制器效率对比(普通字节流 vs 缓冲流)
+
+**难度**:⭐⭐⭐
+**知识点**:`CopyFile` 接口、`FileInputStream` / `FileOutputStream`、`BufferedInputStream` / `BufferedOutputStream`、`byte[]` 缓冲区、try-catch-finally、`System.currentTimeMillis()`
+
+**场景描述**:课堂用 `CopyFile` 接口统一了"文件复制"的规范,并实现了两个版本:`Copy01`(普通字节流 + `byte[8192]` 数组)和 `BufferedCopy`(缓冲流包装底层流)。现在把两个实现整合进一个**对比程序**,复制同一个大文件,用耗时说话——验证"缓冲流更快"。
+
+### 代码框架
+
+```java
+import java.io.*;
+
+// ① 复制器接口:统一复制规范
+public interface CopyFile {
+    String copy(String src, String desc) throws IOException;
+}
+
+// ② 普通字节流实现:FileInputStream + FileOutputStream + byte[8192] 数组 + try-catch-finally
+class Copy01 implements CopyFile {
+    @Override
+    public String copy(String src, String desc) {
+        FileOutputStream fos = null;
+        FileInputStream fis = null;
+        try {
+            // TODO 1: 创建输出流 fos 和输入流 fis
+            // TODO 2: byte[8192] 缓冲区 + while 循环「读一批 → 写一批」
+        } catch (IOException e) {
+            return "文件复制失败";
+        } finally {
+            // TODO 3: finally 中关闭 fos / fis
+            //         提示:close() 也可能抛 IOException → 包装成 RuntimeException
+        }
+        return "文件复制成功";
+    }
+}
+
+// ③ 缓冲流实现:BufferedOutputStream / BufferedInputStream 包装底层流
+class BufferedCopy implements CopyFile {
+    @Override
+    public String copy(String src, String desc) throws IOException {
+        // TODO 4: 先创建文件流,再用缓冲流包装它们
+        // TODO 5: byte[8192] 数组 + while ((count = bis.read(bytes)) != -1) { bos.write(bytes, 0, count); }
+        // TODO 6: 关闭:先关缓冲流再关底层流
+        return "复制成功";
+    }
+}
+
+// ④ 测试类:对比两种实现复制同一个大文件的耗时
+public class CopyCompareTest {
+    public static void main(String[] args) throws IOException {
+        String src = "D:/大文件.zip";   // 换成你电脑上任意大于 10MB 的文件
+        // TODO 7: 用 new Copy01().copy(src, ...) 复制,System.currentTimeMillis() 统计耗时
+        // TODO 8: 用 new BufferedCopy().copy(src, ...) 复制,统计耗时
+        // TODO 9: 输出两次耗时,对比哪个更快
+    }
+}
+```
+
+**选做加分**:
+- 把缓冲区大小改成 1024 / 4096 / 8192 / 16384 各测一次,总结"缓冲区大小 → 耗时"的变化规律
+- 把 `Copy01` 改成"失败时返回提示信息"的 try-catch 风格,`BufferedCopy` 保持 throws 风格,体会两种异常处理方式
+
+**思考题**:
+1. 复制大文件时 `BufferedCopy` 为什么通常比 `Copy01` 更快?(提示:缓冲流内部缓冲区减少与磁盘的交互次数)
+2. `BufferedCopy` 在缓冲流之上又用了 `byte[8192]` 数组,这层"双缓冲"是必须的吗?(提示:可以去掉外层数组试试看耗时变化)
+3. 为什么 finally 中关闭流时还要再写一层 try-catch?(提示:`close()` 也声明抛 `IOException`)
+4. 关闭顺序为什么是"先关缓冲流、再关底层流"?(提示:close 缓冲流会自动关闭它包装的底层流)
+
+---
+
+## 练习二:百万字符写入 + 读取统计(flush 体验 + 字母频次统计)
+
+**难度**:⭐⭐⭐
+**知识点**:`BufferedOutputStream`、`BufferedInputStream`、`flush()`、`Random` 随机数、`getBytes()`、ASCII 码、数组统计
+
+**场景描述**:课堂用 `BufferedOutputStream` 向文件写入了 100 万随机大写字母并演示了 `flush()`。现在把它做成一个**完整闭环**:写入 100 万随机字母 → 关闭流 → 再用 `BufferedInputStream` 读回 → 统计每个大写字母出现的次数 → 输出出现最多的字母。
+
+### 代码框架
+
+```java
+import java.io.*;
+import java.util.Random;
+
+public class BufferedWriteReadTest {
+    public static void main(String[] args) throws IOException {
+        String path = "D:/letters.txt";
+        Random ran = new Random();
+
+        // ========== 写:100 万随机大写字母,每 500 个换行 + flush ==========
+        // TODO 1: 创建 FileOutputStream + BufferedOutputStream 包装它
+        // TODO 2: for 循环 100 万次,ran.nextInt(26) + 65 生成 A~Z,bos.write(c) 先写入内部缓冲区
+        // TODO 3: 每 500 个写一次 "\r\n".getBytes(),并调用 bos.flush() 刷新到磁盘
+        // TODO 4: 关闭缓冲流和文件流
+
+        // ========== 读:BufferedInputStream 逐字节读回并统计 ==========
+        // TODO 5: 创建 FileInputStream + BufferedInputStream 包装它
+        // TODO 6: int[] count = new int[26]; 统计每个大写字母出现次数
+        //         while ((b = bis.read()) != -1) { if (b >= 'A' && b <= 'Z') count[b - 'A']++; }
+        // TODO 7: 遍历 count 输出每个字母出现次数;找出出现最多的字母
+        // TODO 8: 关闭流
+    }
+}
+```
+
+**输出示例**:
+```
+字母出现统计:
+A: 38462
+B: 38610
+...
+Q: 38911   ← 出现最多的字母
+```
+
+**选做加分**:
+- 把写入范围扩展为**大小写字母 + 数字**(共 62 种),统计时用 `Character.isLetterOrDigit()` 判断
+- 统计换行符 `\r\n` 出现的次数,验证是否符合预期(约 1000000 / 500 ≈ 2000 次)
+- 对比:写入时**去掉所有 flush()**,程序结束后立刻查看文件内容大小,观察是否完整(理解 `close()` 会自动 flush)
+
+**思考题**:
+1. 不调用 `flush()` 时,数据什么时候才会真正写入磁盘?(提示:缓冲区满 8KB 或 `close()` 时自动刷)
+2. 为什么 `bis.read()` 逐字节读取也很快?(提示:数据预先成批读入内部缓冲区,读取其实发生在内存)
+3. `read()` 返回 -1 表示什么?`(char) b` 强转后为什么能显示字母?(提示:EOF;读到的字节值是 ASCII 码)
+4. `ran.nextInt(26) + 65` 为什么能生成 A~Z?(提示:65~90 正好对应字符 'A'~'Z')
+
+---
+
+## 练习三:字符流中文复制器(FileReader / FileWriter)
+
+**难度**:⭐⭐⭐
+**知识点**:`Reader` / `Writer` 抽象父类、`FileReader` / `FileWriter`、`char[]` 字符缓冲区、`write(String)` 直接写中文、字符流 vs 字节流
+
+**场景描述**:课堂学了 `FileReader` / `FileWriter`:**字符流 = 字节流 + 字符集编码表**,写中文不用 `getBytes()`。现在用字符流写一个"中文复制器":`FileWriter` 写一段中文 → `FileReader` 读回并显示到控制台,实现"字符流三步走"的完整闭环。
+
+### 代码框架
+
+```java
+import java.io.*;
+
+public class CharStreamTest {
+    public static void main(String[] args) throws IOException {
+        // ========== 写:FileWriter 直接写中文(无需 getBytes) ==========
+        // TODO 1: new FileWriter("D:/中文.txt") 创建文件字符输出流
+        // TODO 2: fw.write("床前明月光,疑是地上霜。");  直接写中文字符串
+        //         fw.write("\r\n");
+        //         fw.write("举头望明月,低头思故乡。");
+        // TODO 3: fw.flush();   刷新缓冲区(字符流自带缓冲区)
+        // TODO 4: fw.close();   关闭流
+
+        // ========== 读:FileReader 用 char[1024] 缓冲区读回 ==========
+        // TODO 5: new FileReader("D:/中文.txt") 创建文件字符输入流
+        // TODO 6: char[] chars = new char[1024];
+        //         while ((count = fr.read(chars)) != -1) {
+        //             System.out.print(new String(chars, 0, count));   // 字符数组 → 字符串
+        //         }
+        // TODO 7: fr.close();   关闭流
+    }
+}
+```
+
+**选做加分**:
+- 实现 `CopyFile` 接口的第三个版本 `CharCopy`:用 `FileReader` + `FileWriter` + `char[1024]` 复制含中文的 txt 文件,对比与字节流版本(Copy01)复制中文文件的差异
+- 用 `BufferedReader` / `BufferedWriter` 包装字符流后复制,体会"字符流 + 字符缓冲流"的组合,`BufferedReader.readLine()` 一次读一行
+- 统计文件中汉字个数(提示:读到的 char 值 > 127 即为非 ASCII 的中文字符)
+
+**思考题**:
+1. 为什么 `fw.write("床前明月光")` 不用像字节流那样先 `getBytes()`?(提示:字符流内部按字符集编码表完成"字符 → 字节"转换)
+2. `fr.read(chars)` 返回的是什么?(提示:本次读到的有效字符个数;-1 表示读完)
+3. `new String(chars, 0, count)` 的作用是什么?(提示:只取本次有效部分,避免把缓冲区残留脏数据也转进去)
+
+---
+
+## 练习四:字符集(Charset)与乱码实验(GBK vs UTF-8)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`Charset` 字符集、`FileWriter(String, Charset)` / `FileReader(String, Charset)` 指定编码、String 编码 `getBytes(Charset)` / 解码 `new String(byte[], Charset)`、乱码原理
+
+**场景描述**:课堂讲了"字符流 = 字节流 + 字符集编码表",乱码的根源是**读写字符集不一致**。现在做个实验:用 **GBK** 字符集写入中文,再用**默认字符集(UTF-8)**读取 → 观察乱码;改用 **GBK** 字符集读取 → 中文正常显示。
+
+### 代码框架
+
+```java
+import java.io.*;
+import java.nio.charset.Charset;
+
+public class CharsetTest {
+    public static void main(String[] args) throws IOException {
+        // ========== 写:用 GBK 字符集写入中文 ==========
+        // TODO 1: new FileWriter("D:/编码.txt", Charset.forName("GBK"))
+        // TODO 2: fw.write("中华人民共和国");   直接写中文
+        // TODO 3: fw.close();
+
+        // ========== 读一:默认字符集(UTF-8)读 → 预期乱码 ==========
+        // TODO 4: new FileReader("D:/编码.txt")   默认字符集读取
+        // TODO 5: char[1024] 读回显示,观察是否出现乱码
+
+        // ========== 读二:指定 GBK 字符集读 → 预期正常 ==========
+        // TODO 6: new FileReader("D:/编码.txt", Charset.forName("GBK"))
+        // TODO 7: 再次读回显示,观察中文是否恢复正常
+    }
+}
+```
+
+**输出示例**:
+```
+默认字符集(UTF-8)读取:�й?????????      ← 乱码
+指定字符集(GBK)读取:中华人民共和国       ← 正常
+```
+
+**选做加分**:
+- 用 String 的编码 / 解码方法演示乱码原理:`"中国".getBytes(Charset.forName("GBK"))` 转成字节数组,再用 `new String(bytes, Charset.forName("UTF-8"))` 解码,观察输出乱码
+- 分别用 GBK / UTF-8 写入同一段中文,打印文件大小,对比**一个汉字占几个字节**(GBK 汉字 2 字节,UTF-8 汉字 3 字节)
+- 改用 `getBytes("GBK")` + 字节流 `FileOutputStream` 写文件,验证"字符流"与"字节流 + 手动编码"是等价的
+
+**思考题**:
+1. 为什么用 UTF-8 读 GBK 写的文件会乱码?(提示:编码表不一致,字节无法正确映射回字符)
+2. 一个汉字在 GBK 和 UTF-8 中各占几个字节?(提示:GBK 2 字节,UTF-8 3 字节)
+3. `FileWriter` 不指定字符集时用的是哪个字符集?(提示:平台操作系统默认字符集)
+
+---
+
+## 练习五:用户信息永久化存储(Scanner 键盘录入 + FileWriter 写入本地文件)
+
+**难度**:⭐⭐⭐
+**知识点**:`Scanner` 键盘录入、`FileWriter` 字符流写入、`String` 拼接、`flush()` 刷新、`close()` 关闭流释放资源
+
+**场景描述**:程序中的数据只存在于内存,程序结束就丢失了。要实现**永久化存储**,就要把数据写入本地文件(磁盘)。本例实现一个简单的**用户注册**:从键盘录入用户名和密码,保存到 `D:/user.txt` 文件中——程序下次运行,文件里的数据依然存在。
+
+### 案例需求
+
+将键盘录入的用户名和密码保存到本地,实现永久化存储。
+
+### 实现步骤
+
+1. 获取用户输入的用户名和密码
+2. 将用户输入的用户名和密码写入到本地文件中
+3. 关流,释放资源
+
+### 代码框架
+
+```java
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Scanner;
+
+public class UserRegister {
+    public static void main(String[] args) throws IOException {
+        // ========== 1、获取用户输入的用户名和密码 ==========
+        // TODO 1: 创建 Scanner 对象读取键盘输入
+        // TODO 2: 提示"请输入用户名:",sc.nextLine() 接收用户名
+        // TODO 3: 提示"请输入密码:",sc.nextLine() 接收密码
+
+        // ========== 2、将用户名和密码写入到本地文件中 ==========
+        // TODO 4: new FileWriter("D:/user.txt") 创建文件字符输出流
+        // TODO 5: fw.write("用户名:" + username);  fw.write("\r\n");
+        //         fw.write("密码:" + password);
+        // TODO 6: fw.flush();   把缓冲区内容刷入文件
+
+        // ========== 3、关流,释放资源 ==========
+        // TODO 7: fw.close();   关闭流
+        System.out.println("注册成功!信息已保存到 D:/user.txt");
+    }
+}
+```
+
+**输出示例**:
+```
+请输入用户名:admin
+请输入密码:123456
+注册成功!信息已保存到 D:/user.txt
+```
+(程序运行后打开 `D:/user.txt` 查看内容)
+```
+用户名:admin
+密码:123456
+```
+
+**选做加分**:
+- 升级为"注册 + 登录"小系统:注册时把用户名密码写入文件,登录时用 `FileReader` 读取文件,校验输入的用户名密码与文件中的是否匹配
+- 用 **追加模式** `new FileWriter("D:/user.txt", true)` 实现多用户注册:一次运行保存一组,再次运行追加一组,不覆盖之前的数据
+- 用 `BufferedWriter` 包装后写入,体会"字符流 + 字符缓冲流"的组合写法
+
+**思考题**:
+1. 为什么写入后要 `flush()`?不写会怎样?(提示:字符流自带缓冲区,数据先到缓冲区,缓冲区满或 `close()` 时才落盘)
+2. 程序关闭后重新运行,文件里的数据还在吗?为什么?(提示:数据已写入磁盘文件,实现了永久化存储)
+3. 如果把 `FileWriter` 换成 `FileOutputStream`,写之前需要做什么?(提示:字符串要先 `getBytes()` 转成字节数组)
+4. 默认覆盖写和 `append=true` 追加写有什么区别?(提示:默认每次运行会覆盖原文件内容)
+
+---
+
+## 练习六:字符缓冲流读取 + 排序 + 写回文件(学生成绩排行榜)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`BufferedReader` / `BufferedWriter`、`readLine()` 逐行读取 / `newLine()` 写入、`String.split()` 拆分、`ArrayList` 集合、`Collections.sort()` + `Comparator` 排序、`Integer.parseInt()` 字符串转整数
+
+**场景描述**:`D:/scores.txt` 中保存了 6 名学生的成绩,格式为"姓名-成绩",**顺序是乱的**。用 `BufferedReader` 逐行读取 → `split("-")` 拆分成姓名和成绩 → 封装成 `Student` 对象存入 `ArrayList` → 按成绩从高到低排序 → 用 `BufferedWriter` 把带名次的排序结果写回 `D:/sorted.txt`——把"读文件 → 处理 → 集合 → 排序 → 写文件"串成一个完整闭环。
+
+### 案例需求
+
+使用字符缓冲流读取文件中的数据,排序后再次写到本地文件。
+
+### 实现步骤
+
+1. 将文件中的数据读取到程序中
+2. 对读取到的数据进行处理
+3. 将处理后的数据添加到集合中
+4. 对集合中的数据进行排序
+5. 将排序后的集合中的数据写入到文件中
+
+### 原始数据 `D:/scores.txt`(顺序是乱的)
+
+```
+李四-78
+王五-92
+张三-66
+赵六-85
+孙七-95
+周八-88
+```
+
+### 代码框架
+
+```java
+import java.io.*;
+import java.util.*;
+
+// 学生类:姓名 + 成绩
+class Student {
+    private String name;
+    private int score;
+
+    public Student(String name, int score) {
+        this.name = name;
+        this.score = score;
+    }
+    public String getName() { return name; }
+    public int getScore() { return score; }
+
+    @Override
+    public String toString() {
+        return name + "-" + score;
+    }
+}
+
+public class ScoreSort {
+    public static void main(String[] args) throws IOException {
+        // ========== 1、将文件中的数据读取到程序中 ==========
+        // TODO 1: new BufferedReader(new FileReader("D:/scores.txt")) 字符缓冲流包装
+        // TODO 2: while ((line = br.readLine()) != null) { ... } 逐行读取
+
+        // ========== 2、对读取到的数据进行处理 ==========
+        // TODO 3: String[] parts = line.split("-");   按 "-" 拆分成 [姓名, 成绩]
+        // TODO 4: int score = Integer.parseInt(parts[1]);   成绩字符串 → 整数
+
+        // ========== 3、将处理后的数据添加到集合中 ==========
+        // TODO 5: new Student(parts[0], score) 封装对象,list.add(stu) 存入 ArrayList
+
+        // ========== 4、对集合中的数据进行排序 ==========
+        // TODO 6: Collections.sort(list, new Comparator<Student>() { ... });
+        //         按成绩【降序】:o2.getScore() - o1.getScore()
+
+        // ========== 5、将排序后的集合中的数据写入到文件中 ==========
+        // TODO 7: new BufferedWriter(new FileWriter("D:/sorted.txt"))
+        // TODO 8: for 遍历排序后的集合,bw.write("第" + (i+1) + "名:" + 学生) + bw.newLine()
+        // TODO 9: bw.flush();  bw.close();  br.close();
+        System.out.println("排序完成!结果已写入 D:/sorted.txt");
+    }
+}
+```
+
+**输出示例**:
+```
+排序完成!结果已写入 D:/sorted.txt
+```
+(程序运行后打开 `D:/sorted.txt`)
+```
+第1名:孙七-95
+第2名:王五-92
+第3名:周八-88
+第4名:赵六-85
+第5名:李四-78
+第6名:张三-66
+```
+
+**选做加分**:
+- **二次排序**:成绩相同时按姓名**字典序升序**(Comparator 先比成绩降序,成绩相等再比姓名 `o1.getName().compareTo(o2.getName())`)
+- 用 **`TreeSet` 自动排序**:让 `Student implements Comparable<Student>`(compareTo 按成绩降序),`new TreeSet<>(list)` 自动排序去重
+- **脏数据过滤**:读取时跳过成绩不在 0~100 范围的行(`if (score < 0 || score > 100) continue;`)
+- 排序结果**同时输出到控制台和文件**,验证写入内容正确
+
+**思考题**:
+1. `readLine()` 返回 null 表示什么?`while ((line = br.readLine()) != null)` 循环靠什么终止?(提示:读到文件末尾返回 null)
+2. `line.split("-")` 拆分后返回什么类型?如果某一行没有 "-" 会怎样?(提示:返回 String[];不含分隔符则返回整行组成的长度 1 数组)
+3. `Integer.parseInt(parts[1])` 的作用是什么?不转换直接用 `parts[1]` 排序,比较的是数字还是字符串?(提示:字符串比较按字典序,"95" 会排在 "9" 之后)
+4. 为什么写回时用 `newLine()` 而不是手写 `"\r\n"`?(提示:跨平台行分隔符,Windows `\r\n` / Linux `\n`)
+5. 不调用 `flush()` 或 `close()`,排序结果能立刻出现在文件中吗?(提示:字符缓冲流有缓冲区,不刷新不落盘)
+
+---
+
+# 参考答案要点
+
+## 练习一:Copy01 / BufferedCopy / CopyCompareTest
+
+```java
+// Copy01.copy 核心(TODO 1~3):普通字节流 + byte[8192] 数组 + try-catch-finally
+FileOutputStream fos = null;
+FileInputStream fis = null;
+try {
+    fos = new FileOutputStream(desc);
+    fis = new FileInputStream(src);
+    byte[] bytes = new byte[8192];
+    int byteCount;
+    while ((byteCount = fis.read(bytes)) != -1) {   // 读一批,-1 表示读完
+        fos.write(bytes, 0, byteCount);             // 只写本次有效字节
+    }
+} catch (IOException e) {
+    return "文件复制失败";
+} finally {
+    try {
+        fos.close();
+        fis.close();
+    } catch (IOException e) {
+        throw new RuntimeException(e);              // close 异常包装成运行时异常
+    }
+}
+return "文件复制成功";
+```
+
+```java
+// BufferedCopy.copy 核心(TODO 4~6):缓冲流包装底层流 + 双缓冲
+FileOutputStream fos = new FileOutputStream(desc);
+BufferedOutputStream bos = new BufferedOutputStream(fos);   // 缓冲输出流包装文件输出流
+FileInputStream fis = new FileInputStream(src);
+BufferedInputStream bis = new BufferedInputStream(fis);     // 缓冲输入流包装文件输入流
+byte[] bytes = new byte[8192];
+int count;
+while ((count = bis.read(bytes)) != -1) {
+    bos.write(bytes, 0, count);
+}
+bis.close();    // 先关缓冲流
+fis.close();
+bos.close();
+fos.close();
+return "复制成功";
+```
+
+```java
+// CopyCompareTest.main 核心(TODO 7~9):两次复制 + 两次计时对比
+String src = "D:/大文件.zip";
+long start = System.currentTimeMillis();
+new Copy01().copy(src, "D:/copy1.zip");
+long mid = System.currentTimeMillis();
+new BufferedCopy().copy(src, "D:/copy2.zip");
+long end = System.currentTimeMillis();
+System.out.println("普通字节流耗时:" + (mid - start) + " 毫秒");
+System.out.println("缓冲流耗时:" + (end - mid) + " 毫秒");
+```
+
+## 练习二:BufferedWriteReadTest
+
+```java
+// 写(TODO 1~4):100 万随机大写字母 + 每 500 个换行 + flush
+FileOutputStream fos = new FileOutputStream("D:/letters.txt");
+BufferedOutputStream bos = new BufferedOutputStream(fos);
+Random ran = new Random();
+for (int i = 0; i < 1000000; i++) {
+    int c = ran.nextInt(26) + 65;        // 65~90 → 'A'~'Z'
+    bos.write(c);                        // 先写入内部缓冲区
+    if (i % 500 == 0) {
+        bos.write("\r\n".getBytes());    // 每 500 个字母换一行
+        bos.flush();                     // 手动把缓冲区数据刷到磁盘
+    }
+}
+bos.close();
+fos.close();
+
+// 读 + 统计(TODO 5~8):逐字节读回,统计每个大写字母出现次数
+FileInputStream fis = new FileInputStream("D:/letters.txt");
+BufferedInputStream bis = new BufferedInputStream(fis);
+int[] count = new int[26];               // 下标 0~25 对应 'A'~'Z'
+int b;
+while ((b = bis.read()) != -1) {
+    if (b >= 'A' && b <= 'Z') {          // 过滤换行符等非字母字符
+        count[b - 'A']++;
+    }
+}
+bis.close();
+fis.close();
+
+int max = 0;
+char maxChar = 'A';
+for (int i = 0; i < count.length; i++) {
+    System.out.println((char) ('A' + i) + ": " + count[i]);
+    if (count[i] > max) {
+        max = count[i];
+        maxChar = (char) ('A' + i);
+    }
+}
+System.out.println("出现最多的字母:" + maxChar + ",共 " + max + " 次");
+```
+
+## 练习三:CharStreamTest
+
+```java
+// 写(TODO 1~4):FileWriter 直接写中文,无需 getBytes
+FileWriter fw = new FileWriter("D:/中文.txt");
+fw.write("床前明月光,疑是地上霜。");
+fw.write("\r\n");
+fw.write("举头望明月,低头思故乡。");
+fw.flush();          // 字符流自带缓冲区,flush 刷新到文件
+fw.close();
+
+// 读(TODO 5~7):FileReader + char[1024] 批量读取,字符数组 → 字符串
+FileReader fr = new FileReader("D:/中文.txt");
+char[] chars = new char[1024];
+int count;
+while ((count = fr.read(chars)) != -1) {
+    System.out.print(new String(chars, 0, count));   // 只取本次有效部分
+}
+fr.close();
+```
+
+```java
+// 选做:CopyFile 接口的字符流实现(char[1024] 复制中文 txt)
+class CharCopy implements CopyFile {
+    @Override
+    public String copy(String src, String desc) throws IOException {
+        FileReader fr = new FileReader(src);
+        FileWriter fw = new FileWriter(desc);
+        char[] chars = new char[1024];
+        int count;
+        while ((count = fr.read(chars)) != -1) {
+            fw.write(chars, 0, count);
+        }
+        fw.close();
+        fr.close();
+        return "复制成功";
+    }
+}
+```
+
+## 练习四:CharsetTest
+
+```java
+// 写(TODO 1~3):指定 GBK 字符集写入中文
+FileWriter fw = new FileWriter("D:/编码.txt", Charset.forName("GBK"));
+fw.write("中华人民共和国");
+fw.close();
+
+// 读一(TODO 4~5):默认字符集(UTF-8)读 → 乱码
+FileReader fr1 = new FileReader("D:/编码.txt");
+char[] chars = new char[1024];
+int count;
+while ((count = fr1.read(chars)) != -1) {
+    System.out.println("默认字符集(UTF-8)读取:" + new String(chars, 0, count)); // 乱码
+}
+fr1.close();
+
+// 读二(TODO 6~7):指定 GBK 字符集读 → 正常
+FileReader fr2 = new FileReader("D:/编码.txt", Charset.forName("GBK"));
+while ((count = fr2.read(chars)) != -1) {
+    System.out.println("指定字符集(GBK)读取:" + new String(chars, 0, count));   // 正常
+}
+fr2.close();
+```
+
+```java
+// 选做:String 编码/解码乱码演示(getBytes 编码 + new String 解码)
+byte[] gbkBytes = "中国".getBytes(Charset.forName("GBK"));
+String wrong = new String(gbkBytes, Charset.forName("UTF-8"));
+System.out.println(wrong);          // 输出乱码(编码表不一致)
+String right = new String(gbkBytes, Charset.forName("GBK"));
+System.out.println(right);          // 输出:中国
+```
+
+## 练习五:UserRegister
+
+```java
+// 1、获取用户输入的用户名和密码(TODO 1~3)
+Scanner sc = new Scanner(System.in);
+System.out.println("请输入用户名:");
+String username = sc.nextLine();
+System.out.println("请输入密码:");
+String password = sc.nextLine();
+
+// 2、将用户名和密码写入到本地文件中(TODO 4~6)
+FileWriter fw = new FileWriter("D:/user.txt");     // 创建文件字符输出流
+fw.write("用户名:" + username);                    // 直接写字符串,无需 getBytes
+fw.write("\r\n");                                  // 换行(Windows 是 \r\n)
+fw.write("密码:" + password);
+fw.flush();                                        // 刷新缓冲区,确保写入文件
+
+// 3、关流,释放资源(TODO 7)
+fw.close();
+System.out.println("注册成功!信息已保存到 D:/user.txt");
+```
+
+```java
+// 选做一:追加模式多用户注册(不覆盖之前数据)
+FileWriter fw = new FileWriter("D:/user.txt", true);   // append=true 追加写
+fw.write("用户名:" + username + "\r\n");
+fw.write("密码:" + password + "\r\n");
+fw.close();
+
+// 选做二:FileReader 读取文件做登录校验
+FileReader fr = new FileReader("D:/user.txt");
+char[] chars = new char[1024];
+int count;
+String data = "";
+while ((count = fr.read(chars)) != -1) {
+    data += new String(chars, 0, count);   // 读出文件全部内容
+}
+fr.close();
+// 判断 data 是否包含 "用户名:" + username + 换行 + "密码:" + password 片段
+boolean ok = data.contains("用户名:" + username + "\r\n" + "密码:" + password);
+System.out.println(ok ? "登录成功" : "用户名或密码错误");
+```
+
+## 练习六:ScoreSort
+
+```java
+// 1、将文件中的数据读取到程序中(TODO 1~2):BufferedReader 逐行读取
+BufferedReader br = new BufferedReader(new FileReader("D:/scores.txt"));
+ArrayList<Student> list = new ArrayList<>();
+String line;
+while ((line = br.readLine()) != null) {
+    // 2、对读取到的数据进行处理(TODO 3~4):split 拆分 + 转整数
+    String[] parts = line.split("-");
+    String name = parts[0];
+    int score = Integer.parseInt(parts[1]);
+    // 3、将处理后的数据添加到集合中(TODO 5):封装 Student 对象
+    list.add(new Student(name, score));
+}
+br.close();
+
+// 4、对集合中的数据进行排序(TODO 6):Comparator 按成绩降序
+Collections.sort(list, new Comparator<Student>() {
+    @Override
+    public int compare(Student o1, Student o2) {
+        return o2.getScore() - o1.getScore();   // 降序:交换参数
+    }
+});
+
+// 5、将排序后的集合中的数据写入到文件中(TODO 7~9)
+BufferedWriter bw = new BufferedWriter(new FileWriter("D:/sorted.txt"));
+for (int i = 0; i < list.size(); i++) {
+    bw.write("第" + (i + 1) + "名:" + list.get(i));
+    bw.newLine();                               // 跨平台换行
+}
+bw.flush();
+bw.close();
+System.out.println("排序完成!结果已写入 D:/sorted.txt");
+```
+
+```java
+// 选做一:成绩相同时按姓名升序(Comparator 二次比较)
+Collections.sort(list, new Comparator<Student>() {
+    @Override
+    public int compare(Student o1, Student o2) {
+        int result = o2.getScore() - o1.getScore();                    // 先比成绩【降序】
+        return result == 0 ? o1.getName().compareTo(o2.getName()) : result;  // 成绩相同再比姓名【升序】
+    }
+});
+
+// 选做二:Student 实现 Comparable,TreeSet 自动排序
+class Student implements Comparable<Student> {
+    // ...属性、构造、getter、toString 省略...
+    @Override
+    public int compareTo(Student o) {
+        int result = o.getScore() - this.getScore();                   // 按成绩降序
+        return result == 0 ? this.getName().compareTo(o.getName()) : result;
+    }
+}
+TreeSet<Student> set = new TreeSet<>(list);   // 自动排序 + 去重
+```
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `CopyFile` 接口 | 统一文件复制规范,体现面向接口编程 / 多态 |
+| `FileInputStream` / `FileOutputStream` | 文件字节输入 / 输出流 |
+| `BufferedInputStream` / `BufferedOutputStream` | 缓冲流包装底层字节流,内部 8KB 缓冲区 |
+| 缓冲流构造方法 | `BufferedOutputStream(OutputStream out)` / `BufferedInputStream(InputStream in)`,参数是底层流 |
+| `byte[]` 缓冲区循环复制 | `while ((count = bis.read(bytes)) != -1) { bos.write(bytes, 0, count); }` |
+| `flush()` 刷新 | 缓冲区未满时手动强制写入磁盘 |
+| try-catch-finally | 流变量 null 声明 + finally 关闭 + `IOException` 包装 `RuntimeException` |
+| 关闭顺序 | 先关缓冲流再关底层流(close 缓冲流会自动关闭底层流) |
+| `System.currentTimeMillis()` | 复制前后毫秒数相减,做耗时对比 |
+| `Random` 随机字母 | `ran.nextInt(26) + 65` 生成 'A'~'Z'(65~90 是 ASCII 码) |
+| `read()` 返回 -1 | EOF 结束标记;`(char) b` 强转把字节值显示为字母 |
+| 数组统计 | `count[b - 'A']++` 用下标映射字母,统计频次并找出最大值 |
+| `FileReader` / `FileWriter` | 文件字符输入 / 输出流;字符流 = 字节流 + 字符集编码表 |
+| `write(String)` 直接写中文 | 字符流可直接写字符串,无需 `getBytes()` 转换 |
+| `char[]` 字符缓冲区 | `fr.read(chars)` 批量读字符;`new String(chars, 0, count)` 转字符串 |
+| `Charset` 字符集 | `FileWriter(String, Charset)` / `FileReader(String, Charset)` 构造指定编码 |
+| String 编码 / 解码 | `getBytes(Charset)` 字符串 → 字节(编码);`new String(byte[], Charset)` 字节 → 字符串(解码) |
+| 乱码原理 | 读写字符集不一致 → 乱码;GBK 汉字 2 字节,UTF-8 汉字 3 字节 |
+| Scanner 键盘录入 | `sc.nextLine()` 获取用户名、密码输入(配合 `FileWriter` 完成数据持久化) |
+| 数据永久化存储 | 内存数据程序结束即丢失;写入本地文件(磁盘)即可永久保存 |
+| `FileWriter` 追加模式 | `new FileWriter(String, true)` append=true 追加写,不覆盖原文件内容 |
+| 登录校验(选做) | `FileReader` 读文件 + `String.contains()` 判断用户名密码是否匹配 |
+| `BufferedReader` / `BufferedWriter` | 字符缓冲流包装 `FileReader` / `FileWriter`,提供高效读写 |
+| `readLine()` 逐行读取 | 读取一行(不含终止符),到文件末尾返回 null;`while ((line = br.readLine()) != null)` |
+| `newLine()` 写入换行 | 按系统属性写行分隔符(跨平台,替代手写 `\r\n`) |
+| `String.split()` 拆分 | `line.split("-")` 按分隔符把一行拆成 String[],用于解析"姓名-成绩" |
+| `Integer.parseInt()` | 字符串 → 整数转换,排序时才能按数值比较 |
+| 集合 + 排序 | `ArrayList` 存储 `Student` 对象 + `Collections.sort` + `Comparator` 匿名内部类按成绩降序 |
+| 读 → 处理 → 集合 → 排序 → 写回 | 文件数据处理五步闭环:字符缓冲流读 → 处理 → 集合 → 排序 → 字符缓冲流写回 |