|
@@ -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)→ 转换流 / 对象流 / 打印流等,缓冲流是"性能优化"的重要一环
|