IO 流 - 图1
IO 流也称为输入、输出流,就是用来读写数据的。

  • I 表示 intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
  • O 表示 output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。

流的四大类:

  • 字节输入流 InputStream:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流
  • 字节输出流 OutputStream :以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流
  • 字符输入流 Reader:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流
  • 字符输出流 Writer:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流

IO 流 - 图2

字节流的使用

文件字节输入流:每次读取一个字节

  1. @Test
  2. public void t1() throws IOException {
  3. // 1、创建一个文件字节输入流管道与源文件接通。
  4. InputStream is = new FileInputStream(new File("src\\main\\java\\io\\data.txt"));
  5. // 2、读取一个字节返回 (每次读取一滴水)
  6. int b1 = is.read();
  7. System.out.println((char) b1);
  8. int b2 = is.read();
  9. System.out.println((char) b2);
  10. int b3 = is.read();
  11. System.out.println((char) b3);
  12. int b4 = is.read(); // 读取完毕返回 -1
  13. System.out.println(b4);
  14. }
  1. @Test
  2. public void t2() throws IOException {
  3. // 简化写法
  4. InputStream is = new FileInputStream("src\\main\\java\\io\\data.txt");
  5. // 使用循环改进, 定义一个变量记录每次读取的字节
  6. int b;
  7. while ((b = is.read()) != -1) {
  8. System.out.print((char) b);
  9. }
  10. }

存在问题:

  • 性能较慢
  • 读取中文字符输出无法避免乱码问题

    文件字节输入流:每次读取一个字节数组

    @Test
    public void t3() throws Exception {
      // 1、创建一个文件字节输入流管道与源文件接通
      InputStream is = new FileInputStream("src\\main\\java\\io\\data.txt");
    
      // 2、定义一个字节数组,用于读取字节数组
      byte[] buffer = new byte[3]; // 3B
      int len = is.read(buffer);
      System.out.println("读取了几个字节:" + len);
      String rs = new String(buffer);
      System.out.println(rs);
    
      int len1 = is.read(buffer);
      System.out.println("读取了几个字节:" + len1);
      String rs1 = new String(buffer);
      System.out.println(rs1);
      // buffer = [a b c]
    
      // 桶内还存在没覆盖的 c
      // buffer = [a b c]  ==>  [c d c]
      int len2 = is.read(buffer);
      System.out.println("读取了几个字节:" + len2);
      // 读取多少倒出多少
      String rs2 = new String(buffer,0 ,len2);
      System.out.println(rs2);
    
      int len3 = is.read(buffer);
      System.out.println(len3); // 读取完毕返回 -1
    }
    
    @Test
    public void t4() throws Exception {
      InputStream is = new FileInputStream("src\\main\\java\\io\\data.txt");
    
      // 改进使用循环,每次读取一个字节数组
      byte[] buffer = new byte[3];
      int len; // 记录每次读取的字节数。
      while ((len = is.read(buffer)) != -1) {
          // 读取多少倒出多少
          System.out.print(new String(buffer, 0 , len));
      }
    }
    

    注:字符串的编码和解码 JSRUN 在线运行

    文件字节输入流:一次读完全部字节

    定义一个与文件一样大的字节数组,一次性读取完文件的全部字节,可以读取中文内容输出不乱码,但如果文件过大,字节数组可能引起内存溢出。

    @Test
    public void t5() throws Exception {
      // 1、创建一个文件字节输入流管道与源文件接通
      File f = new File("src\\main\\java\\io\\data.txt");
      InputStream is = new FileInputStream(f);
    
      // 2、定义一个字节数组与文件的大小刚刚一样大。
      byte[] buffer = new byte[(int) f.length()];
      int len = is.read(buffer);
      System.out.println("读取了多少个字节:" + len);
      System.out.println("文件大小:" + f.length());
      System.out.println(new String(buffer));
    }
    

    Java 9 开始之后官方为字节输入流 InputStream 提供了如下 API 可以直接把文件的全部数据读取到一个字节数组中

  • public byte[] readAllBytes() throws IOException:直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回

    @Test
    public void t6() throws Exception {
      InputStream is = new FileInputStream("src\\main\\java\\io\\data.txt");
      byte[] buffer = is.readAllBytes();
      System.out.println(new String(buffer));
    }
    

    文件字节输出流:写字节数据到文件

    作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。

  • public FileOutputStream(File file):创建字节输出流管道与源文件对象接通

  • public FileOutputStream(File file,boolean append):创建字节输出流管道与源文件对象接通,可追加数据
  • public FileOutputStream(String filepath):创建字节输出流管道与源文件路径接通
  • public FileOutputStream(String filepath,boolean append):创建字节输出流管道与源文件路径接通,可追加数据

流的关闭与刷新:

  • flush():刷新流,还可以继续写数据
  • close():关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 ```java // 1、创建一个文件字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream(“src\main\java\io\out.txt”, true); // 追加数据管道 // OutputStream os = new FileOutputStream(“src\main\java\io\out.txt”); // 先清空之前的数据,写新数据进入

// 2、写数据出去 os.write(‘a’); os.write(98); os.write(“\r\n”.getBytes()); // 换行

// 3、释放资源,包含了刷新,关闭后流不可以使用了 os.close();

```java
// 1、创建一个文件字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("src\\main\\java\\io\\out2.txt", true); // 追加数据管道

// public void write(byte[] buffer):写一个字节数组出去。
byte[] buffer = {'a', 97, 98, 99};
os.write(buffer);
os.write("\r\n".getBytes()); // 换行

byte[] buffer2 = "你好世界".getBytes();
os.write(buffer2);
os.write("\r\n".getBytes()); // 换行

// public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
byte[] buffer3 = {'a', 97, 98, 99};
os.write(buffer3, 0, 3);
os.write("\r\n".getBytes()); // 换行

os.close();

文件拷贝:

  • 根据数据源创建字节输入流对象
  • 根据目的地创建字节输出流对象
  • 读写数据,复制视频
  • 释放资源

    try {
      // 1、创建一个字节输入流管道与原视频接通
      InputStream is = new FileInputStream("src\\main\\java\\io\\out.txt");
    
      // 2、创建一个字节输出流管道与目标文件接通
      OutputStream os = new FileOutputStream("src\\main\\java\\io\\copy_out.txt");
    
      // 3、定义一个字节数组转移数据
      byte[] buffer = new byte[1024];
      int len; // 记录每次读取的字节数
      while ((len = is.read(buffer)) != -1) {
          os.write(buffer, 0, len);
      }
      System.out.println("复制完成了!");
    
      // 4、关闭流。
      os.close();
      is.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    

    资源释放的方式

    try-catch-finally

    finally:在异常处理时提供 finally 块来执行所有清除操作,比如说 IO 流中的释放资源
    特点:被 finally 控制的语句最终一定会执行,除非 JVM 退出 ```java InputStream is = null; OutputStream os = null; try { // System.out.println(10 / 0);

    // 1、创建一个字节输入流管道与原视频接通 is = new FileInputStream(“src\main\java\io\out.txt”);

    // 2、创建一个字节输出流管道与目标文件接通 os = new FileOutputStream(“src\main\java\io\copy_out.txt”);

// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1) {
    os.write(buffer, 0, len);
}
System.out.println("复制完成了!");

// System.out.println(10 / 0);

} catch (Exception e) { e.printStackTrace(); } finally { // 无论代码是正常结束,还是出现异常都要最后执行这里 System.out.println(“======== finally =========”); try { // 4、关闭流。 if (os != null) { os.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } }

```java
try {
    int c = a / b;
    return c;
}catch (Exception e){
    e.printStackTrace();
    return -1; // 计算出现 bug.
}finally {
    // 哪怕上面有 return 语句执行,也必须先执行完这里才可以!
    // 开发中不建议在这里加 return ,如果加了,返回的永远是这里的数据了,这样会出问题!
    return 100;
}

try-with-resource

finally 虽然可以用于释放资源,但是释放资源的代码过于繁琐

class MyConnection implements AutoCloseable{
    @Override
    public void close() throws IOException {
        System.out.println("连接资源被成功释放了!");
    }
}
try (
    // 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)

    InputStream is = new FileInputStream("src\\main\\java\\io\\out.txt");
    OutputStream os = new FileOutputStream("src\\main\\java\\io\\copy_out.txt");

    // int age = 23; // 这里只能放资源
    MyConnection connection = new MyConnection(); // 最终会自动调用资源的close方法
) {
    // 3、定义一个字节数组转移数据
    byte[] buffer = new byte[1024];
    int len; // 记录每次读取的字节数。
    while ((len = is.read(buffer)) != -1) {
        os.write(buffer, 0, len);
    }
    System.out.println("复制完成了!");
} catch (Exception e) {
    e.printStackTrace();
}

资源都是实现了 CloseableAutoCloseable 接口的类对象

Java 9 如果你有一个资源是 final 或等效于 final 变量, 那么你可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。

private static String readDataJava7(String message) throws IOException {
    Reader reader = new StringReader(message);
    BufferedReader bufferedReader = new BufferedReader(reader);
    try (BufferedReader bufferedReader2 = bufferedReader) {
        return bufferedReader2.readLine();
    }
}
private static String readDataJava9(String message) throws IOException {
    Reader reader = new StringReader(message);
    BufferedReader bufferedReader = new BufferedReader(reader);
    try (bufferedReader) {
        return bufferedReader.readLine();
    }
}

字符流的使用

文件字符输入流:一次读取一个字符

作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。
构造器:

  • public FileReader(File file):创建字符输入流管道与源文件对象接通
  • public FileReader(String pathname):创建字符输入流管道与源文件路径接通

方法:

  • public int read():每次读取一个字符返回,如果字符已经没有可读的返回 -1
  • public int read(char[] buffer):每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回 -1 ```java // 目标:每次读取一个字符。 // 1、创建一个字符输入流管道与源文件接通 Reader fr = new FileReader(“src\main\java\io\data.txt”);

// 2、读取一个字符返回,没有可读的字符了返回-1 int code = fr.read(); System.out.print((char)code);

int code1 = fr.read(); System.out.print((char)code1);

```java
Reader fr = new FileReader("src\\main\\java\\io\\data.txt");
int code;
while ((code = fr.read()) != -1) {
    System.out.print((char) code);
}

读取中文字符不会出现乱码(如果代码文件编码一致)但性能较慢

文件字符输入流:一次读取一个字符数组

// 1、创建一个文件字符输入流与源文件接通
Reader fr = new FileReader("src\\main\\java\\io\\data.txt");

// 2、用循环,每次读取一个字符数组的数据。  
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
    String rs = new String(buffer, 0, len);
    System.out.print(rs);
}

读取的性能得到了提升,读取中文字符输出不会乱码。

文件字符输出流

构造器:

  • public FileWriter(File file):创建字符输出流管道与源文件对象接通
  • public FileWriter(File file,boolean append):创建字符输出流管道与源文件对象接通,可追加数据
  • public FileWriter(String filepath):创建字符输出流管道与源文件路径接通
  • public FileWriter(String filepath,boolean append):创建字符输出流管道与源文件路径接通,可追加数据

有关方法:

  • void write(int c):写一个字符
  • void write(char[] cbuf):写入一个字符数组
  • void write(char[] cbuf, int off, int len):写入字符数组的一部分
  • void write(String str):写一个字符串
  • void write(String str, int off, int len):写一个字符串的一部分
  • void write(int c):写一个字符

流的关闭与刷新

  • flush():刷新流,还可以继续写数据
  • close():关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 ```java // 1、创建一个字符输出流管道与目标文件接通 // Writer fw = new FileWriter(“src\\main\\java\\io\\data.txt”); // 覆盖管道,每次启动都会清空文件之前的数据 Writer fw = new FileWriter(“src\main\java\io\data.txt”, true); // 覆盖管道,每次启动都会清空文件之前的数据

// a. public void write(int c): 写一个字符出去 fw.write(98); fw.write(‘a’); fw.write(‘徐’); // 不会出问题了 fw.write(“\r\n”); // 换行

// b. public void write(String c): 写一个字符串出去 fw.write(“你好世界 Hello World”); fw.write(“\r\n”); // 换行

// c. public void write(char[] buffer): 写一个字符数组出去 char[] chars = “123 你好世界 Hello World”.toCharArray(); fw.write(chars); fw.write(“\r\n”); // 换行

// d. public void write(String c ,int pos ,int len): 写字符串的一部分出去 fw.write(“你好世界 Hello World”, 0, 2); fw.write(“\r\n”); // 换行

// e. public void write(char[] buffer ,int pos ,int len): 写字符数组的一部分出去 fw.write(chars, 4, 4);

// fw.flush(); // 刷新后流可以继续使用 fw.close(); // 关闭包含刷线,关闭后流不能使用

字节流、字符流如何选择使用?

- 字节流适合做一切文件数据的拷贝(音视频,文本)
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)
<a name="BhvFm"></a>
## 缓冲流
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。<br />作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能<br />![](https://cdn.jsdelivr.net/gh/halo-blog/cdn-blog-img-e@master/io.5e3bf4wqexo0.svg#crop=0&crop=0&crop=1&crop=1&id=ThbeG&margin=%5Bobject%20Object%5D&originHeight=720&originWidth=1280&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="Zaq4u"></a>
### 字节缓冲流
字节缓冲流性能优化原理:

- 字节缓冲输入流自带了8KB 缓冲池,以后我们直接从缓冲池读取数据,所以性能较好。
- 字节缓冲输出流自带了8KB 缓冲池,数据就直接写入到缓冲池中去,写数据性能极高了。

字节缓冲输入流:BufferedInputStream,提高字节输入流读取数据的性能,读写功能上并无变化。<br />字节缓冲输出流:BufferedOutputStream,提高字节输出流读取数据的性能,读写功能上并无变化。
```java
try (
    // 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的 close 方法关闭资源(即使出现异常也会做关闭操作)
    // 1、创建一个字节输入流管道与原视频接通
    InputStream is = new FileInputStream("D:\\resources\\test.jpeg");
    // a.把原始的字节输入流包装成高级的缓冲字节输入流
    InputStream bis = new BufferedInputStream(is);
    // 2、创建一个字节输出流管道与目标文件接通
    OutputStream os = new FileOutputStream("D:\\resources\\test.jpeg");
    // b.把字节输出流管道包装成高级的缓冲字节输出流管道
    OutputStream bos = new BufferedOutputStream(os);

) {

    // 3、定义一个字节数组转移数据
    byte[] buffer = new byte[1024];
    int len; // 记录每次读取的字节数。
    while ((len = bis.read(buffer)) != -1){
        bos.write(buffer, 0 , len);
    }
    System.out.println("复制完成了!");

} catch (Exception e){
    e.printStackTrace();
}

字节缓冲流的性能分析

public class ByteBufferTimeDemo {
    private static final String SRC_FILE = "D:\\Root\\Videos\\test.mp4";
    private static final String DEST_FILE = "D:\\Root\\Videos\\";

    public static void main(String[] args) {
        copy01(); // 使用低级的字节流按照一个一个字节的形式复制文件:慢的让人简直无法忍受。直接被淘汰。
        copy02(); // 使用低级的字节流按照一个一个字节数组的形式复制文件: 比较慢,但是还是可以忍受的!
        copy03(); // 缓冲流一个一个字节复制:很慢,不建议使用。
        copy04(); // 缓冲流一个一个字节数组复制:飞快,简直太完美了(推荐使用)
    }

    //...
}
/**
 * 使用低级的字节流按照一个一个字节的形式复制文件
 */
private static void copy01() {
    long startTime = System.currentTimeMillis();
    try (
        // 1、创建低级的字节输入流与源文件接通
        InputStream is = new FileInputStream(SRC_FILE);
        // 2、创建低级的字节输出流与目标文件接通
        OutputStream os = new FileOutputStream(DEST_FILE + "video1.mp4")
    ) {

        // 3、定义一个变量记录每次读取的字节(一个一个字节的复制)
        int b;
        while ((b = is.read()) != -1) {
            os.write(b);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println("使用低级的字节流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}
private static void copy02() {
    long startTime = System.currentTimeMillis();
    try (
        // 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
        // 1、创建一个字节输入流管道与原视频接通
        InputStream is = new FileInputStream(SRC_FILE);
        // 2、创建一个字节输出流管道与目标文件接通
        OutputStream os = new FileOutputStream(DEST_FILE + "video2.mp4")
    ) {

        // 3、定义一个字节数组转移数据
        byte[] buffer = new byte[1024];
        int len; // 记录每次读取的字节数。
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println("使用低级的字节流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}
private static void copy03() {
    long startTime = System.currentTimeMillis();
    try (
        // 1、创建低级的字节输入流与源文件接通
        InputStream is = new FileInputStream(SRC_FILE);
        // a.把原始的字节输入流包装成高级的缓冲字节输入流
        InputStream bis = new BufferedInputStream(is);
        // 2、创建低级的字节输出流与目标文件接通
        OutputStream os = new FileOutputStream(DEST_FILE + "video3.mp4");
        // b.把字节输出流管道包装成高级的缓冲字节输出流管道
        OutputStream bos = new BufferedOutputStream(os);
    ) {

        // 3、定义一个变量记录每次读取的字节(一个一个字节的复制)
        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println("使用缓冲的字节流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}
private static void copy04() {
    long startTime = System.currentTimeMillis();
    try (
        // 1、创建低级的字节输入流与源文件接通
        InputStream is = new FileInputStream(SRC_FILE);
        // a.把原始的字节输入流包装成高级的缓冲字节输入流
        InputStream bis = new BufferedInputStream(is);
        // 2、创建低级的字节输出流与目标文件接通
        OutputStream os = new FileOutputStream(DEST_FILE + "video4.mp4");
        // b.把字节输出流管道包装成高级的缓冲字节输出流管道
        OutputStream bos = new BufferedOutputStream(os);
    ) {

        // 3、定义一个字节数组转移数据
        byte[] buffer = new byte[1024];
        int len; // 记录每次读取的字节数。
        while ((len = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println("使用缓冲的字节流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}

建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。

字符缓冲流

字符缓冲输入流:BufferedReader。
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。
字符缓冲输入流新增功能

  • public String readLine():读取一行数据返回,如果读取没有完毕,无行可读返回 null
    try (
      // 1、创建一个文件字符输入流与源文件接通。
      Reader fr = new FileReader("src/main/java/io/data.txt");
      // a、把低级的字符输入流包装成高级的缓冲字符输入流。
      BufferedReader br = new BufferedReader(fr);
    ) {
      String line;
      while ((line = br.readLine()) != null) {
          System.out.println(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    

字符缓冲输出流:BufferedWriter
作用:提高字符输出流写取数据的性能,除此之外多了换行功能
字符缓冲输出流新增功能:

  • public void newLine():换行操作 ```java // 1、创建一个字符输出流管道与目标文件接通 Writer fw = new FileWriter(“src/main/java/io/out.txt”); // 覆盖管道,每次启动都会清空文件之前的数据 //Writer fw = new FileWriter(“io-app2/src/out02.txt”, true); // 追加数据 BufferedWriter bw = new BufferedWriter(fw);

bw.write(“Hello World”); bw.newLine(); // bw.write(“\r\n”); // 换行

// fw.flush();// 刷新后流可以继续使用 bw.close(); // 关闭包含刷线,关闭后流不能使用

<a name="W4G7z"></a>
### 案例—拷贝《出师表》到另一个文件并恢复顺序
```java
十一.出师未捷身先死,长使英雄泪满襟。
三.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
八.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
四.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
二.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
一.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
九.今当远离,临表涕零,不知所言。
十.今当远离,临表涕零,不知所言。
陆.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
柒.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
五.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

思路:

  1. 定义一个缓存字符输入流管道与源文件接通。
  2. 定义一个 List 集合存储读取的每行数据。
  3. 定义一个循环按照行读取数据,存入到 List 集合中去。
  4. 对 List 集合中的每行数据按照首字符编号升序排序。
  5. 定义一个缓存字符输出管道与目标文件接通。
  6. 遍历 List 集合中的每个元素,用缓冲输出管道写出并换行。 ```java try ( // 1、创建缓冲字符输入流管道与源文件接通 BufferedReader br = new BufferedReader(new FileReader(“src/main/java/io/csb.txt”));

    // 5、定义缓冲字符输出管道与目标文件接通 BufferedWriter bw = new BufferedWriter(new FileWriter(“src/main/java/io/csb_new.txt”)); ) {

    // 2、定义一个List集合存储每行内容 List data = new ArrayList<>(); // 3、定义循环,按照行读取文章 String line; while ((line = br.readLine()) != null) {

     data.add(line);
    

    } System.out.println(data);

    // 4、排序 // 自定义排序规则 List sizes = new ArrayList<>(); Collections.addAll(sizes, “一”, “二”, “三”, “四”, “五”, “陆”, “柒”, “八”, “九”, “十”, “十一”);

    Collections.sort(data, new Comparator() {

     @Override
     public int compare(String o1, String o2) {
         return sizes.indexOf(o1.substring(0, o1.indexOf(".")))
             - sizes.indexOf(o2.substring(0, o2.indexOf(".")));
     }
    

    });

    // 简化 // data.sort(Comparator.comparingInt(o -> sizes.indexOf(o.substring(0, o.indexOf(“.”)))));

    System.out.println(data);

    // 6、遍历集合中的每行文章写出去,且要换行 for (String datum : data) {

     bw.write(datum);
     bw.newLine(); // 换行
    

    }

} catch (Exception e) { e.printStackTrace(); }

<a name="p3wLT"></a>
## 转换流
字符流直接读取文本内容:

- 必须文件和代码编码一致才不会乱码
- 如果文件和代码编码不一致,读取将出现乱码
<a name="rv9gQ"></a>
### 字符输入转换流
字符输入转换流:InputStreamReader,可以把原始的字节流按照指定编码转换成字符输入流。<br />解决思路:

- 使用字符输入转换流
- 可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
- 然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码了

构造器:

- `public InputStreamReader(InputStream is)`:可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的 FileReader 一样。
- `public InputStreamReader(InputStream is ,String charset)`:可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点)
```java
// 代码UTF-8   文件 GBK  "D:\\resources\\data.txt"
// 1、提取GBK文件的原始字节流。   abc 我
//                              ooo oo
InputStream is = new FileInputStream("D:\\resources\\data.txt");
// 2、把原始字节流转换成字符输入流
// 默认以 UTF-8 的方式转换成字符流。 还是会乱码的 跟直接使用 FileReader 是一样的
// Reader isr = new InputStreamReader(is); 
// 以指定的 GBK 编码转换成字符输入流  完美的解决了乱码问题
Reader isr = new InputStreamReader(is , "GBK"); 

BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null){
    System.out.println(line);
}

字符输入转换流

字符输入转换流:OutputStreamWriter,可以把字节输出流按照指定编码转换成字符输出流。
构造器:

  • OutputStreamWriter(OutputStream out):可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。
  • OutputStreamWriter(OutputStream out, String charsetName):可以把原始的字节输出流按照指定编码转换成字符输出流(重点) ```java // 1、定义一个字节输出流 OutputStream os = new FileOutputStream(“src/main/java/io/out.txt”);

// 2、把原始的字节输出流转换成字符输出流 // 以默认的 UTF-8 写字符出去 跟直接写 FileWriter 一样 // Writer osw = new OutputStreamWriter(os); // 指定 GBK 的方式写字符出去 Writer osw = new OutputStreamWriter(os, “GBK”);

// 3、把低级的字符输出流包装成高级的缓冲字符输出流。 BufferedWriter bw = new BufferedWriter(osw);

bw.write(“Hello World”); bw.write(“你好 世界”); bw.write(“你好 世界 Hello World”);

bw.close();

<a name="Raoqn"></a>
## 序列化对象
<a name="ntwmk"></a>
### 对象序列化
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。<br />使用到的流是对象字节输出流:ObjectOutputStream<br />构造器:

- `ObjectOutputStream(OutputStream out)`:把低级字节输出流包装成高级的对象字节输出流

方法:

- `final void writeObject(Object obj)`:把对象写出去到对象序列化流的文件中去
```java
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
    // 申明序列化的版本号码
    // 序列化的版本号与反序列化的版本号必须一致才不会出错!
    private static final long serialVersionUID = 1L;
    private String name;
    private String loginName;
    // transient 修饰的成员变量不参与序列化了
    private transient String passWord;
    private int age;
}

被序列化的对象需要实现 Serializable 接口

// 1、创建学生对象
Student s = new Student("张三", "zhangsan", "zhangsan", 21);

// 2、对象序列化:使用对象字节输出流包装字节输出流管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/main/java/serializable/obj.txt"));

// 3、直接调用序列化方法
oos.writeObject(s);

// 4、释放资源
oos.close();
System.out.println("序列化完成了~~");

对象反序列化

使用到的流是对象字节输入流:ObjectInputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
构造器:

  • public ObjectInputStream(InputStream out):把低级字节输如流包装成高级的对象字节输入流

方法:

  • public Object readObject():把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 ```java // 1、创建对象字节输入流管道包装低级的字节输入流管道 ObjectInputStream is = new ObjectInputStream(new FileInputStream(“src/main/java/serializable/obj.txt”));

// 2、调用对象字节输入流的反序列化方法 Student s = (Student) is.readObject();

System.out.println(s);

<a name="YXtrq"></a>
## 打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter 两个类。<br />可以实现打印什么数据就是什么数据,例如打印整数 97 写出去就是 97,打印 boolean 的 true,写出去就是 true。
<a name="dCV5p"></a>
### PrintStream
构造器:

- `PrintStream(OutputStream os)`:打印流直接通向字节输出流管道
- `PrintStream(File f)`:打印流直接通向文件对象
- `PrintStream(String filepath)`:打印流直接通向文件路径

方法:

- `void print(Xxx xx)`:打印任意类型的数据出去
```java
// 1、创建一个打印流对象
PrintStream ps = new PrintStream(new FileOutputStream("src/main/java/io/data.txt"));
// 追加数据,在低级管道后面加 true
// PrintStream ps = new PrintStream(new FileOutputStream("src/main/java/io/data.txt" , true)); 
// PrintStream ps = new PrintStream("src/main/java/io/data.txt" );

ps.println(97);
ps.println('a');
ps.println(23.3);
ps.println(true);
ps.println("我是打印流输出的,我是啥就打印啥");

ps.close();

PrintWriter

// 打印功能上与 PrintStream 的使用没有区别
PrintWriter ps = new PrintWriter("src/main/java/io/data.txt"); 

ps.println(97);
ps.println('a');
ps.println(23.3);
ps.println(true);
ps.println("我是打印流输出的,我是啥就打印啥");

ps.close();

PrintStream 和 PrintWriter 的区别:

  • 打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
  • PrintStream 继承自字节输出流 OutputStream,支持写字节数据的方法。
  • PrintWriter 继承自字符输出流 Writer,支持写字符数据出去。

    输出语句重定向

    属于打印流的一种应用,可以把输出语句的打印位置改到文件。 ```java System.out.println(“Halo”); System.out.println(“Halo”); System.out.println(“Halo”);

PrintStream printStream = new PrintStream(“src/main/java/io/log.txt”); System.setOut(printStream);

System.out.println(“Test”); System.out.println(“Test”); System.out.println(“Test”);

<a name="niAV9"></a>
## Properties 与 IO 流
Properties 其实就是一个 Map 集合,但是我们一般不会当集合使用,因为 HashMap 更好用。<br />Properties 核心作用:

- Properties 代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
- 属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。

Properties 和 IO 流结合的方法:

- `void load(InputStream inStream)`:从输入字节流读取属性列表(键和元素对)
- `void load(Reader reader)`:从输入字符流读取属性列表(键和元素对)
- `void store(OutputStream out, String comments)`:将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 `load(InputStream)` 方法的格式写入输出字节流
- `void store(Writer writer, String comments)`:将此属性列表(键和元素对)写入此 Properties表中,以适合使用 `load(Reader)` 方法的格式写入输出字符流
- `public Object setProperty(String key, String value)`:保存键值对(put)
- `public String getProperty(String key)`:使用此属性列表中指定的键搜索属性值 (get)
- `public Set<String> stringPropertyNames()`:所有键的名称的集合  (keySet())
```java
// 需求:使用 Properties 把键值对信息存入到属性文件中去。
Properties properties = new Properties();
properties.setProperty("admin", "123456");
properties.setProperty("halo", "003197");
properties.setProperty("test", "test");
System.out.println(properties);

/**
 * 参数一:保存管道 字符输出流管道
 * 参数二:保存心得
 */
properties.store(new FileWriter("src/main/java/properties/users.properties"),
                 "comments");
// 需求:Properties 读取属性文件中的键值对信息。(读取)
Properties properties = new Properties();
System.out.println(properties);

// 加载属性文件中的键值对数据到属性对象properties中去
properties.load(new FileReader("src/main/java/properties/users.properties"));

System.out.println(properties);

String rs = properties.getProperty("halo");
System.out.println(rs);

String rs1 = properties.getProperty("admin");
System.out.println(rs1);

IO 框架—Commons-io

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.11.0</version>
</dependency>
// 1.完成文件复制!
IOUtils.copy(new FileInputStream("src/main/java/io/data.txt"),
             new FileOutputStream("src/main/java/io/data_copy.txt"));

// 2.完成文件复制到某个文件夹下
FileUtils.copyFileToDirectory(new File("src/main/java/io/data.txt"), new File("D:/resources"));

// 3.完成文件夹复制到某个文件夹下
FileUtils.copyDirectoryToDirectory(new File("D:/resources"), new File("D:/resources_copy"));

// 删除文件夹
FileUtils.deleteDirectory(new File("D:/resources"));

// JDK 1.7 自己也做了一些一行代码完成复制的操作: New IO 的技术
Files.copy(Path.of("src/main/java/io/data.txt"), Path.of("src/main/java/io/data_new.txt"));

FileUtils.deleteDirectory(new File("D:/resources_copy"));