一、File 类

1.1 File概述

  • FIle 类是** java.io **包下代表和平台无关的文件和目录,换言之,如果希望在程序中操作文件和目录都可以使用 File 类来完成,File 类能新建、删除、重命名文件和目录。
  • 在 API 中 File 的解释是 **文件和目录路径名的抽象表示形式** ,即 File 类是文件或目录的路径,而不是文件本身,因此 File 类不能直接访问文件内容本身,如果需要访问文件内容本身,就需要使用 IO 流了。
  • 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名(路径字符串而已)。它可以是存在的,也可以是不存在的;将来是要通过具体的操作把这个路径的内容转换为具体存在的
    • File 类代表磁盘或网络中某个文件或目录的路径名称,例如:E:\\Develop\\java.txt `` E:\\lhl
    • 不能直接通过 File 对象读取和写入数据,如果要操作数据,需要 IO 流。

1.2 File 类的构造方法

方法名 说明
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
  1. package com.lhl.file;
  2. import java.io.File;
  3. import java.net.URI;
  4. /**
  5. * @author lhl
  6. * @ClassName FileDemo01
  7. * @description TODO File类的构造方法
  8. * @data 2022/03/19 20:14
  9. * @Version V1.0
  10. **/
  11. public class FileDemo01 {
  12. public static void main(String[] args) {
  13. //File(String pathname): 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
  14. File f1 = new File("E:/Develop/java.txt");
  15. System.out.println("file = " + f1);
  16. //File(String parent, String child): 从父路径名字符串和子路径名字符串创建新的 File实例
  17. File f2 = new File("E:/lhl","靓仔.jpg");//两个路径的拼接,String类型
  18. System.out.println("file = " + f2);
  19. //File(File parent, String child): 从父抽象路径名和子路径名字符串创建新的 File实例
  20. File f3 = new File("E:/lhl");
  21. File f4 = new File(f3,"IO.md");//两个路径的拼接,file类型跟String类型
  22. System.out.println("file = " + f4);
  23. //File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
  24. URI uri = URI.create("file:///E:/Develop/Java/jdk1.8.0_221/bin");//uri语法自行学习嗷~,
  25. File f5 = new File(uri);
  26. System.out.println("file = " + f5);
  27. }
  28. }

输出:

  1. file = E:\Develop\java.txt
  2. file = E:\lhl\靓仔.jpg
  3. file = E:\lhl\IO.md
  4. file = E:\Develop\Java\jdk1.8.0_221\bin

1.3 绝对路径和相对路径

  • 绝对路径
    是一个完整的路径,从盘符开始
  • 相对路径
    是一个简化的路径,相对当前项目下的路径
  • 示例代码

    1. public class FileDemo02 {
    2. public static void main(String[] args) {
    3. // 是一个完整的路径,从盘符开始
    4. File file1 = new File("E:\\lhl\\a.txt");
    5. // 是一个简化的路径,从当前项目根目录开始
    6. File file2 = new File("a.txt");
    7. File file3 = new File("模块名\\a.txt");
    8. }
    9. }

    1. 4 File 的常用方法

1.4.1 File 的创建功能

方法名 说明
public boolean createNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件
public boolean mkdir() 创建由此抽象路径名命名的目录
public boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
  1. package com.lhl.file;
  2. import java.io.File;
  3. import java.io.IOException;
  4. /**
  5. * @author lhl
  6. * @ClassName FileDemo02
  7. * @description TODO File类的创建功能
  8. * @data 2022/03/19 21:28
  9. * @Version V1.0
  10. **/
  11. public class FileDemo02 {
  12. public static void main(String[] args) throws IOException {
  13. //需求1:我要在E:\\lhl目录下创建一个文件java.txt
  14. File f1 = new File("E:\\lhl\\java.txt");
  15. System.out.println(f1.createNewFile());
  16. System.out.println("--------");
  17. //需求2:我要在E:\\lhl目录下创建一个目录JavaSE
  18. File f2 = new File("E:\\lhl\\JavaSE");
  19. System.out.println(f2.mkdir());
  20. System.out.println("--------");
  21. //需求3:我要在E:\\lhl目录下创建一个多级目录JavaWEB\\HTML
  22. File f3 = new File("E:\\lhl\\JavaWEB\\HTML");
  23. //System.out.println(f3.mkdir());
  24. System.out.println(f3.mkdirs());
  25. System.out.println("--------");
  26. //需求4:我要在E:\\lhl目录下创建一个文件javaee.txt
  27. File f4 = new File("E:\\lhl\\javaee.txt");
  28. //System.out.println(f4.mkdir());
  29. System.out.println(f4.createNewFile());
  30. }
  31. }

1.4.2 File类的删除功能

方法名 说明
public boolean delete() 删除由此抽象路径名表示的文件或目录

示例:

  1. package com.lhl.file;
  2. import java.io.File;
  3. import java.io.IOException;
  4. /**
  5. * @author lhl
  6. * @ClassName FileDemo03
  7. * @description TODO File类的删除功能
  8. * @data 2022/03/19 21:44
  9. * @Version V1.0
  10. **/
  11. public class FileDemo03 {
  12. public static void main(String[] args) throws IOException {
  13. //需求1:在当前模块目录下创建java.txt文件
  14. File f1 = new File("JavaSE\\java.txt");
  15. //System.out.println(f1.createNewFile());
  16. //需求2:删除当前模块目录下的java.txt文件
  17. System.out.println(f1.delete());
  18. System.out.println("--------");
  19. //需求3:在当前模块目录下创建 美女目录
  20. File f2 = new File("JavaSE\\美女");
  21. System.out.println(f2.mkdir());
  22. //需求4:删除当前模块目录下的 美女目录
  23. System.out.println(f2.delete());
  24. System.out.println("--------");
  25. //需求5:在当前模块下创建一个目录aaa,然后在该目录下创建一个文件java.txt
  26. File f3 = new File("JavaSE\\aaa");
  27. System.out.println(f3.mkdir());
  28. File f4 = new File(f3,"java.txt");
  29. System.out.println(f4.createNewFile());
  30. //需求6:删除当前模块下的目录aaa
  31. System.out.println(f4.delete()); //得先删除目录里的文件才能删除目录
  32. System.out.println(f3.delete());
  33. }
  34. }

1.4.3 File类判断和获取功能

  • 判断功能 | 方法名 | 说明 | | —- | —- | | public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 | | public boolean isFile() | 测试此抽象路径名表示的File是否为文件 | | public boolean exists() | 测试此抽象路径名表示的File是否存在 |

  • 获取功能 | 方法名 | 说明 | | —- | —- | | public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 | | public String getPath() | 将此抽象路径名转换为路径名字符串 | | public String getName() | 返回由此抽象路径名表示的文件或目录的名称 | | public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |

示例代码:

  1. package com.lhl.file;
  2. import java.io.File;
  3. /**
  4. * @author lhl
  5. * @ClassName FileDemo04
  6. * @description TODO File类的判断和获取功能
  7. * @data 2022/03/19 21:57
  8. * @Version V1.0
  9. **/
  10. public class FileDemo04 {
  11. public static void main(String[] args) {
  12. //创建一个File对象
  13. File f = new File("JavaSE\\java.txt");
  14. //public boolean isDirectory():测试此抽象路径名表示的File是否为目录
  15. //public boolean isFile():测试此抽象路径名表示的File是否为文件
  16. //public boolean exists():测试此抽象路径名表示的File是否存在
  17. System.out.println(f.isDirectory());
  18. System.out.println(f.isFile());
  19. System.out.println(f.exists());
  20. //public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串
  21. //public String getPath():将此抽象路径名转换为路径名字符串
  22. //public String getName():返回由此抽象路径名表示的文件或目录的名称
  23. System.out.println(f.getAbsolutePath());
  24. System.out.println(f.getPath());
  25. System.out.println(f.getName());
  26. System.out.println("--------");
  27. //public File[] listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组
  28. File f2 = new File("E:\\lhl");
  29. File[] fileArray = f2.listFiles();
  30. for(File file : fileArray) {
  31. //System.out.println(file);
  32. //System.out.println(file.getName());
  33. if (file.isFile()) {
  34. System.out.println(file.getName());
  35. }
  36. }
  37. }
  38. }

1.4.4 练习

练习1:

  • 案例需求
    在当前模块下的aaa文件夹中创建一个a.txt文件
  • 实现步骤
    • 创建File对象,指向aaa文件夹
    • 判断aaa文件夹是否存在,如果不存在则创建
    • 创建File对象,指向aaa文件夹下的a.txt文件
    • 创建这个文件 ```java package com.lhl.file.test;

import java.io.File; import java.io.IOException;

/**

  • @author lhl
  • @ClassName Test1
  • @description TODO
  • @data 2022/03/20 01:08
  • @Version V1.0 **/ public class Test1 { public static void main(String[] args) throws IOException {

    1. //1.创建File对象,指向aaa文件夹
    2. File file = new File("filemodule/aaa");
    3. //2.判断aaa文件夹是否存在,如果文件夹不存在就创建
    4. //注意:文件所在的目录必须存在
    5. if (!file.exists()){
    6. file.mkdir();
    7. }
    8. //3.创建File对象,指向aaa文件夹下面的a.txt文件
    9. File file1 = new File(file, "a.txt");
    10. //4.创建这个文件
    11. file1.createNewFile();

    } } ```

练习2:

  • 案例需求
    删除一个多级文件夹
  • 实现步骤
    • 定义一个方法,接收一个File对象
    • 遍历这个File对象,获取它下边的每个文件和文件夹对象
    • 判断当前遍历到的File对象是文件还是文件夹
    • 如果是文件,直接删除
    • 如果是文件夹,递归调用自己,将当前遍历到的File对象当做参数传递
    • 参数传递过来的文件夹File对象已经处理完成,最后直接删除这个空文件夹 ```java package day13.File; import java.io.File; import java.util.Scanner; public class Demo02 { static Scanner sc=new Scanner(System.in); public static void main(String[] args) { /已创建的/ //D:\ccc\bbb\aaa\f\g—-空文件夹 //D:\ccc\bbb\aaa\f\h\d.txt //D:\ccc\bbb\aaa\f\h\e.txt System.out.println(“请输入一个多级目录:”); String str=sc.nextLine(); File file1=new File(str); System.out.println(method(file1)); } public static int method(File file1){ //打印当前文件名 System.out.println(file1.getName()); File[] files = file1.listFiles(); if(file1.exists()){
         for(File s:files){
             System.out.println(s);
         }
      
      } //判断是否为文件 if(file1.isFile()){//文件
         file1.delete();
         return 1;
      
      } else{//递归调用 //文件夹
         int leng= files.length;
         for (int i = 0; i <leng ; i++) {
             //System.out.println(files[i]);
             files[i].delete();
         }
         return -1;
      
      } } }
```java
package com.lhl.file.test;

import java.io.File;
import java.util.Arrays;

/**
 * @author lhl
 * @ClassName Test2
 * @description TODO
 * @data 2022/03/20 13:22
 * @Version V1.0
 **/
public class Test2 {
    public static void main(String[] args) {

        File file = new File("C:\\Users\\Tul\\Desktop\\JavaSE");
        deleteDir(file);
    }

    //定义一个方法,接收一个File对象
    public static void deleteDir(File file) {
        //1.先删除掉这个文件夹里的所有东西
        //递归,在方法中自己调用自己


        //2.遍历这个File对象,获取他下面的每个文件和文件夹对象
        File[] files = file.listFiles();
        System.out.println(Arrays.toString(files));

        //3.判断当前遍历到的File
        for (File f : files) {
            //4.如果是文件,直接删除
            if(f.isFile()){
                f.delete();
            }else{
                //5.如果是文件夹,递归调用自己,将当前遍历到的File对象当做参数传递
                deleteDir(f);//参数一定要是src文件夹里面的文件夹File对象
            }
        }

        //6.参数传递过来的文件夹File对象已经处理完成,最后直接删除这个空文件夹
        file.delete();

    }
}

练习3:

  • 案例需求
    统计一个文件夹中每种文件的个数并打印
    打印格式如下:

txt:3个

      doc:4个

      jpg:6个<br /> 
  • 实现步骤
    • 定义一个方法,参数是HashMap集合用来统计次数和File对象要统计的文件夹
    • 遍历File对象,获取它下边的每一个文件和文件夹对象
    • 判断当前File对象是文件还是文件夹
    • 如果是文件,判断这种类型文件后缀名在HashMap集合中是否出现过
      • 没出现过,将这种类型文件的后缀名存入集合中,次数存1
      • 出现过,获取这种类型文件的后缀名出现的次数,对其+1,在存回集合中
    • 如果是文件夹,递归调用自己,HashMap集合就是参数集合,File对象是当前文件夹对象 ```java package com.lhl.file.test;

import java.io.File; import java.util.HashMap;

/**

  • @author lhl
  • @ClassName Test3
  • @description TODO
  • @data 2022/03/20 14:30
  • @Version V1.0 **/ public class Test3 { public static void main(String[] args) {

     //统计一个文件夹中,每种文件出现的次数.
     //统计 --- 定义一个变量用来统计. ---- 弊端:同时只能统计一种文件
     //利用map集合进行数据统计,键 --- 文件后缀名  值 ----  次数
    
     File file = new File("JavaSE");
     HashMap<String, Integer> hm = new HashMap<>();
     getCount(hm, file);
     System.out.println(hm);
    

    }

    //1.定义一个方法,参数是HashMap集合用来统计次数和File对象要统计的文件夹 private static void getCount(HashMap hm, File file) {

     //2.遍历File对象,获取它下边的每一个文件和文件夹对象
     File[] files = file.listFiles();
     for (File f : files) {
         //3.判断当前File对象是文件还是文件夹
         if(f.isFile()){
             //如果是文件,判断这种类型文件后缀名在HashMap集合中是否出现过
             String fileName = f.getName();
             String[] fileNameArr = fileName.split("\\.");
             //有些文件是没有后缀名的,譬如aaa,或者存在,a.a.txt,我们先不做考虑,只统计长度为2的
             if(fileNameArr.length == 2){
                 //获取到文件的后缀名
                 String fileEndName = fileNameArr[1];
                 if(hm.containsKey(fileEndName)){
                     //出现过,获取这种类型文件的后缀名出现的次数,对其+1,在存回集合中
                     Integer count = hm.get(fileEndName);
                     //这种文件又出现了一次.
                     count++;
                     //把已经出现的次数给覆盖掉.
                     hm.put(fileEndName,count);
                 }else{
                     // 没出现过,将这种类型文件的后缀名存入集合中,次数存1
                     hm.put(fileEndName,1);
                 }
             }
         }else{
             //如果是文件夹,递归调用自己,HashMap集合就是参数集合,File对象是当前文件夹对象代码实现
             getCount(hm,f);
         }
     }
    

    }

}

<a name="hHCkF"></a>
# 二、IO流
<a name="LxohY"></a>
## 2.1 IO流概述

- I/O 是 Input 和 Output 的缩写,IO 技术是非常实用的技术,用于 `处理设备之间的数据传输` ,如:读写文件、文件复制,文件上传下载,网络通讯等。
- Java 程序中,对于数据的输入/输出操作以 `流stream` 的方式进行。
- java.io 包下提供了各种 `流` 类和接口,用于获取不同种类的数据,并通过 `标准的方法` 输入或输出数据。
- **流**(`Stream`):是一种抽象概念, 是指一连串的数据(字符或字节),以先进先出的方式发送信息的通道;也就是说数据在设备间的传输称为流,**流的本质也就是是数据传输**;
> 流的特性:
> - 先进先出:最先写入输出流的数据最先被输入流读取到。 
> -  顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外) 
> -  只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。 

<a name="Alvoe"></a>
## 2.2 IO的原理

-  IO流的方向:以内存为参照,进行读写 
   - 输入( Input ):读取外部数据(磁盘、U 盘等存储设备的数据)到程序(内存)中。
   - 输出( Output ):将程序(内存)数据输出到磁盘、U 盘等存储设备中。

![image-20220320032041865.png](https://cdn.nlark.com/yuque/0/2022/png/26775128/1647718003363-b2d6b972-a54d-4994-9609-730426f5e5e8.png#clientId=u17716176-2796-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=401&id=u149f83ec&margin=%5Bobject%20Object%5D&name=image-20220320032041865.png&originHeight=501&originWidth=1097&originalType=binary&ratio=1&rotation=0&showTitle=false&size=293821&status=done&style=stroke&taskId=u90d79d59-af73-4756-9b13-2579f5095cf&title=&width=877.6)
<a name="PVIbW"></a>
## 2.3 IO流的分类

-  根据流的流向分类: 
   - 输入流:将数据从 `其他设备` 上读取到 `内存` 中的流。以 InputStream 、Reader 结尾。
   - 输出流:将数据从 `内存` 中写出到 `其他设备` 上的流。以 OutputStream 和 Writer 结尾。
- 根据数据的类型分类: 
   - 字节流:以字节为单位,读写数据的流。以 InputStream 和 OutputStream 结尾。
   - 字符流:以字符为单位,读写数据的流。以 Reader 和 Writer 结尾。
-  根据 IO 流的角色不同分类: 
   - 节点流:可以从或向一个特定的地方(节点)读取数据。如 FileReader 。
   - 处理流:对一个已经存在的流进行连接和封装,通过锁封装的流的功能实现数据读写。如:BufferReader ,处理流的构造方法总是要带一个其它的流对象做参数。一个流对象经过多次其它流的多次包装,称为流的链接。
-  常用的节点流: 
   - 文件:FileInputStream 、FileOutputStream 、FileReader 、FileWriter 文件进行处理的节点流。
   - 字符串:StringReader 、StringWriter 对字符串进行处理的节点流。
   - 数组: ByteArrayInputStream 、ByteArrayOutputStream、CharArrayReader 、CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
   - 管道:PipedInputStream 、PipedOutputStream 、PipedReader 、PipedWriter 对管道进行处理的节点流。
-  常用处理流: 
   - 缓冲流:BufferedInputStream 、BufferedOutputStream 、BufferedReader 、BufferedWriter ---增加缓冲功能,避免频繁读写硬盘。
   - 转换流:InputStreamReader 、OutputStreamReader --- 实现字节流和字符流之间的转换。
   - 数据流:DataInputStream 、DataOutputStream - 提供读写 Java 基础数据类型功能。
   - 对象流:ObjectInputStream 、ObjectOutputStream -- 提供直接读写 Java 对象功能。
<a name="c59ddff7"></a>
## 2.4 四大顶级抽象流父类
|  | **输入流** | **输出流** |
| --- | --- | --- |
| **字节流** | 字节输入流 **InputStream** | 字节输出流 **OutputStream** |
| **字符流** | 字符输入流 **Reader** | 字符输出流 **Writer** |

![image.png](https://cdn.nlark.com/yuque/0/2022/png/26775128/1647718508666-13fb905e-42da-4eb5-8985-6b8d790edc9a.png#clientId=u17716176-2796-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=442&id=uc5a02b11&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=1152&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58170&status=done&style=stroke&taskId=ub8f09038-e014-4547-9a00-43f47ad5e27&title=&width=921.6)
> **4个顶级类都是抽象类,并且是所有流类型的父类**

- IO流的使用场景 
   - 如果操作的是图片、视频、音频等二进制文件,优先使用字节流
   - 如果操作的是纯文本文件,优先使用字符流
   - 如果不确定文件类型,优先使用字节流.字节流是万能的流
- 字节流与字符流区别 
   - 字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
<a name="48bb1d58"></a>
## 2.5 字节流

<a name="504dfcb7"></a>
### 2.5.1 概述

- 一切文件数据(文本、图片、视频等)在存储的时候,都是以二进制的形式保存的,都是一个个的字节;同样,在传输的时候依然如此。所以,字节流可以传输任意文件数据。
- 在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输始终是二进制数据。

<a name="tIf0L"></a>
### 2.5.2 继承体系

- 字节流抽象基类
   - InputStream:这个抽象类是表示**字节输入流**的所有类的超类
   - OutputStream:这个抽象类是表示**字节输出流**的所有类的超类
   - `**子类名特点:子类名称都是以其父类名作为子类名的后缀**`

![](https://cdn.nlark.com/yuque/0/2022/png/26775128/1647719307860-e618b62d-de15-479f-b61b-422de65677ff.png#crop=0&crop=0&crop=1&crop=1&id=Vb0y3&originHeight=413&originWidth=1100&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/png/26775128/1647719359253-9bd0ee84-7a9f-452c-a521-ffbfe8f6e627.png#crop=0&crop=0&crop=1&crop=1&id=fPlYx&originHeight=414&originWidth=1100&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="sLzYt"></a>
### 2.5.3 字节流写数据
**FileOutputStream**

-  FileOutputStream 是文件输出流,用于将数据写出到文件中。
-  构造函数:  
| 方法名 | 说明 |
| --- | --- |
| FileOutputStream(String name) | 创建文件输出流以指定的名称写入文件 |
| FileOutputStream(File file) | 创建文件输出流以写入由指定的 File对象表示的文件 |
| FileOutputStream(String name,boolean append) | 创建文件输出流以写入由指定的 File对象表示的文件。 |
| FileOutputStream((File file,boolean append) | 创建文件输出流以指定的名称写入文件。 |

> 注意:`FileOutputStream((File file,boolean append)`,如果第二个参数为true ,则字节将写入文件的末尾而不是开头

 使用字节输出流写数据的步骤

- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)

      入门示例:
```java
public class FileOutputStreamDemo01 {
    public static void main(String[] args) throws IOException {
        //创建字节输出流对象
          /*
              注意点:
              1.如果文件不存在,会帮我们创建
              2.如果文件存在,会把文件清空
          */
          //FileOutputStream(String name):创建文件输出流以指定的名称写入文件
        FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");

        //void write(int b):将指定的字节写入此文件输出流
        fos.write(97);    
        //fos.write(57);
        //fos.write(55);

        //最后都要释放资源
        //void close():关闭此文件输出流并释放与此流相关联的任何系统资源。
        fos.close();
    }
}

2.5.3.1 字节流写数据的三种方式

方法名 说明
void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据
void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据
void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据
void flush() 刷新此输出流并强制写出所有缓冲的输出字节
void close() 关闭此输出流并释放与此流相关的所有系统资源
package com.lhl.output;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lhl
 * @ClassName FileOutputStreamDemo02
 * @description TODO
 * @data 2022/03/20 14:38
 * @Version V1.0
 **/
public class FileOutputStreamDemo02 {
        public static void main(String[] args) throws IOException {
            //FileOutputStream(String name):创建文件输出流以指定的名称写入文件
            FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
            //FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件
            //FileOutputStream fos = new FileOutputStream(new File("myByteStream\\fos.txt"));

            //void write(int b):将指定的字节写入此文件输出流
            //fos.write(97);
            //fos.write(98);
            //fos.write(99);
            //fos.write(100);
            //fos.write(101);

            //void write(byte[] b):将 b.length字节从指定的字节数组写入此文件输出流
            //byte[] bys = {97, 98, 99, 100, 101};
            //byte[] getBytes():返回字符串对应的字节数组
            byte[] bys = "abcde".getBytes();
            //fos.write(bys);

            //void write(byte[] b, int off, int len):将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流
            //fos.write(bys,0,bys.length);
            fos.write(bys,1,3);

            //释放资源
            fos.close();
        }
    }

2.5.3.2 字节流写数据的两个小问题

  • 字节流写数据如何实现换行
    • windows:\r\n
    • linux:\n
    • mac:\r
  • 字节流写数据如何实现追加写入
    • public FileOutputStream(String name,boolean append)
    • 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
package com.lhl.output;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lhl
 * @ClassName FileOutputStreamDemo03
 * @description TODO
 * @data 2022/03/20 14:52
 * @Version V1.0
 **/
public class FileOutputStreamDemo03 {
    public static void main(String[] args) throws IOException {
        //创建字节输出流对象
        //FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
        FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt",true);

        //写数据
        for (int i = 0; i < 10; i++) {
            fos.write("hello".getBytes());
            fos.write("\r\n".getBytes());
        }

        //释放资源
        fos.close();
    }
}

2.5.3.3 字节流写数据的异常处理

异常处理格式

  • try-catch-finally

    try{
     可能出现异常的代码;
    }catch(异常类名 变量名){
     异常的处理代码;
    }finally{
     执行所有清除操作;
    }
    
  • finally特点

    • 被finally控制的语句一定会执行,除非JVM退出
      public class FileOutputStreamDemo04 {
      public static void main(String[] args) {
         //加入finally来实现释放资源
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream("myByteStream\\fos.txt");
             fos.write("hello".getBytes());
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
             if(fos != null) {
                 try {
                     fos.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
      }
      }
      

      2.5.4 字节流读数据

      FileInputStream
  • java.io.FileInputStream 类是文件输入流,从文件中读取字节。

  • 构造方法: | 方法名 | 说明 | | —- | —- | | FileInputStream(File file) | 通过打开与实际文件的连接来创建一个 FileInputStream
    ,该文件由文件系统中的 File对象 file命名。 | | FileInputStream(String name) | 通过打开与实际文件的连接来创建一个 FileInputStream
    ,该文件由文件系统中的路径名 name命名。 |

2.5.4.1 字节流写数据的三种方式

方法名 说明
public int read() 从该输入流读取一个字节的数据。数据的下一个字节,如果达到文件的末尾, -1
public int read(byte[] b) 从输入流读取最多b.length个字节的数据
public int read(byte[] b, int off, int len) 读取从此输入流中的数据len个字节到字节数组,开始在目标数组b的偏移。

一次读一个字节:

package com.lhl.input;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @author lhl
 * @ClassName FileInputStreamDemo01
 * @description TODO
 * @data 2022/03/21 13:51
 * @Version V1.0
 **/
public class FileInputStreamDemo01 {
    public static void main(String[] args){
        FileInputStream fis = null;
        try {
            //创建字节输入流对象
            //FileInputStream(String name)
            fis = new FileInputStream("myByteStream\\aaa\\fos.txt");

            int by;
        /*

            fis.read():读数据
            by=fis.read():把读取到的数据赋值给by
            by != -1:判断读取到的数据是否是-1
         */
            while ((by=fis.read())!=-1) {
                System.out.print((char)by);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //释放资源
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

思考:为什么read()方法返回值为什么是int?而不是byte 因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式存储的。如果每次读取都返回byte;有可能在读到中间的时候遇到11111111,而byte类型的8个1的十进制正好是-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收。如果是11111111就会在其前面补上24个0凑足4个字节,那么byte类型的-1就会变成int类型的255了,这样就可以保证整个数据读完,而结束标记的-1就是int类型。

一次读一个数组:

package com.lhl.input;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lhl
 * @ClassName FileInputStreamDemo02
 * @description TODO
 * @data 2022/03/21 19:34
 * @Version V1.0
 **/
public class FileInputStreamDemo02 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            //创建字节输入流对象
            fis = new FileInputStream("myByteStream\\aaa\\fos.txt");

            byte[] bys = new byte[1024]; //1024及其整数倍
            int len;//本次读到的有效字节个数 -- 这次读了几个字节
            //循环读取
            while ((len=fis.read(bys))!=-1) {
                System.out.print(new String(bys,0,len));//读几个就打印出几个
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis!= null){
                    //释放资源
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.5.5 字节流复制文件(练习)

文件复制的本质是字节的搬家。
image.png

  • 案例需求
    把“E:\lhl\窗里窗外.txt”复制到模块目录下的“窗里窗外.txt” (文件可以是任意文件)
  • 实现步骤
    • 复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
    • 数据源:
      E:\lhl\窗里窗外.txt —- 读数据 —- InputStream —- FileInputStream
    • 目的地:
      myByteStream\窗里窗外.txt —- 写数据 —- OutputStream —- FileOutputStream
  • 代码实现 ```java package com.lhl.copydemo;

import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;

/**

  • @author lhl
  • @ClassName CopyDemo
  • @description TODO
  • @data 2022/03/21 19:58
  • @Version V1.0 **/ public class CopyDemo { public static void main(String[] args) throws IOException {

     //根据数据源创建字节输入流对象
     FileInputStream fis = new FileInputStream("E:\\lhl\\嘿嘿嘿.avi");
     //根据目的地创建字节输出流对象
     FileOutputStream fos = new FileOutputStream("myByteStream\\嘿嘿嘿.avi");
    
     //读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
     byte[] bys = new byte[1024];
     int len;
     while ((len=fis.read(bys))!=-1) {
         fos.write(bys,0,len);
     }
     //释放资源
     fos.close();
     fis.close();
    

    } } ```

    2.6 字节缓冲流

2.6.1 缓冲流概述

  • 缓冲流,也叫高效流,按照数据类型分类:
    • 字节缓冲流:BufferedOutputStream 、BufferedInputStream 。
    • 字符缓冲流:BufferedReader 、BufferedWriter 。
  • 缓冲流会在内部创建一个缓冲区数组,缺省使用 8192 个字节(8kb)的缓冲区。
  • 缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流、字符流对象进行操作

    public class BufferedInputStream extends FilterInputStream {
     private static int DEFAULT_BUFFER_SIZE = 8192;
     ... 
    }
    
  • 缓冲流的基本原理:

当读取数据的时候,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。

  • 当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性的从文件中读取 8192 个,存储在缓冲区中,直到缓冲区中装满了,才重新从文件中读取下一个 8192 个字节数组。
  • 向流中写入字节的时候,不会直接写到文件中,先写到缓冲区中直到缓冲区中写满,BufferedOutputStream 才会将缓冲区中的数据一次性的写到文件里。使用 flush() 方法可以强制将缓冲区的内容全部写入输出流。

image.png

  • 关闭流的顺序和打开流的顺序相反,只需要关闭最外层流即可,因为关闭最外层流的同时也会关闭对应的内层流。
  • flush() 方法的使用:手动将 buffer 中内容写入文件。
  • 如果是带缓冲区的流对象的 close() 方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

    2.6.2 字节缓冲流

字节缓冲流介绍

  • lBufferOutputStream:该类实现缓冲输出流.通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
  • lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组.当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节

构造方法:

方法名 说明
BufferedOutputStream(OutputStream out) 创建字节缓冲输出流对象
BufferedInputStream(InputStream in) 创建字节缓冲输入流对象
package com.lhl.BufferStream;

import java.io.*;

/**
 * @author lhl
 * @ClassName BufferStreamDemo
 * @description TODO
 * @data 2022/03/21 20:52
 * @Version V1.0
 **/
public class BufferStreamDemo {
    public static void main(String[] args) throws IOException {
        //字节缓冲输出流:BufferedOutputStream(OutputStream out)
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\bos.txt"));

        //写数据
        bos.write("hello\r\n".getBytes());
        bos.write("world\r\n".getBytes());
        //释放资源
        bos.close();

        //字节缓冲输入流:BufferedInputStream(InputStream in)
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myByteStream\\bos.txt"));
        //一次读取一个字节数据
        //int by;
        //while ((by=bis.read())!=-1) {
        //    System.out.print((char)by);
        //}

        //一次读取一个字节数组数据
        byte[] bys = new byte[1024];
        int len;
        while ((len=bis.read(bys))!=-1) {
            System.out.print(new String(bys,0,len));
        }

        //释放资源
        bis.close();
    }
}

2.6.3 字节缓冲流复制视频

  • 案例需求
    把“E:\lhl\字节流复制图片.avi”复制到模块目录下的“字节流复制图片.avi”
  • 实现步骤
    • 根据数据源创建字节输入流对象
    • 根据目的地创建字节输出流对象
    • 读写数据,复制视频
    • 释放资源 ```java package com.lhl.BufferStream;

import java.io.*;

/**

  • @author lhl
  • @ClassName CopyAviDemo
  • @description TODO
  • @data 2022/03/21 21:09
  • @Version V1.0 **/ public class CopyAviDemo { public static void main(String[] args) throws IOException {

     //复制视频
     //method1();
     method2();
    

    }

    //字节缓冲流一次读写一个字节数组 public static void method2() throws IOException {

     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\lhl\\字节流复制图片.avi"));
     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
    
     byte[] bys = new byte[1024];
     int len;
     while ((len=bis.read(bys))!=-1) {
         bos.write(bys,0,len);
     }
    
     bos.close();
     bis.close();
    

    }

    //字节缓冲流一次读写一个字节 public static void method1() throws IOException {

     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\lhl\\字节流复制图片.avi"));
     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
    
     int by;
     while ((by=bis.read())!=-1) {
         bos.write(by);
     }
    
     bos.close();
     bis.close();
    

    }

} ```

2.7 字符流

思考:既然字节流可以操作所有文件,那么为什么还要学习字符流?

  • 如果利用字节流,把文本文件的中文,读取到内存中,有可能还会出现乱码
  • 如果利用字节流,把中文写到文本文件中,也有可能出现乱码

了解原因之前,前来学习下编码表这个概念

2.7.1 编码表

2.7.1.1 基础知识

  • 计算机中存储的信息都用二机制表示的
  • 按照某种规则,将字符变成二进制,再存储到计算机中,称为编码
  • 按照同样的规则,将存储在计算机中的二进制数据解析出来,称为解码
  • 编码和解码的方式必须一致,否则会导致乱码

简单理解:存储一个字符a,首先需要在码表中查到对应数字是97,然后转换成二进制进行存储
读取的时候,先把二进制解析出来,再转换成97,通过97查找到对应字符是a;

  • 字符编码( CharacterEncoding ):就是一套自然语言的字符和二进制数之间的对应规则。

    2.7.1.2 码表

  • 又叫做字符集,是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

  • 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

image.png

  • 可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

2.7.1.3 常见的码表

  • ASCII字符集(American Standard Code for Information Interchange,美国信息交换标准代码)
    • 是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
    • 基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
    • ASCII码表中是没有中文的。
  • ISO-8859
    • ISO-8859实在ASCII码上的扩展,其中包含128个ASCII码字符,并增加了128个用于西欧国家的字符。
  • GBXXX字符集:
    • GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字。
    • GBK:windows默认的码表,兼容ASCII码表,最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
  • Unicode字符集:
    • Unicode是一个字符集,这个字符集就厉害了,他想包含所有的字符,由国际组织ISO指定,是统一的万国码,基本上包括了世界上所有语言的常见字符。
    • 但是因为表示的字符太多了,所以Unicode码表中的数组不会直接以二进制的的形式存储到计算机中,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及UTF-32进行编码,再存储到计算机,其中最常见的就是UTF-8
    • UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
    • 编码规则:
      • 128个US-ASCII字符,只需一个字节编码
      • 拉丁文等字符,需要二个字节编码
      • 大部分常用字(含中文),使用三个字节编码
      • 其他极少使用的Unicode辅助字符,使用四字节编码

注意:UTF-8 不是一张码表,他只是一种编码格式

2.7.2 字符流读取中文乱码原因

2.7.2.1 汉字存储和展示过程解析

image.png

2.7.2.2 字节流读取纯文本文件到内存,乱码原因

因为字节流一次读一个字节,而不管GBK还是UTF-8一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题。

2.7.2.3 字节流复制纯文本文件未出现乱码原因

原因是最终底层操作会自动进行字节拼接成中文,那么计算机是如何识别中文的呢?
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

2.7.3 字符流概述

字符流的介绍
由于字节流操作中文不是特别的方便,所以Java就提供字符流
字符流 = 字节流 + 编码表

三、路径中的”/“ 和 “\”

正斜杠,又称左斜杠,符号是 “/“; 反斜杠,也称右斜杠,符号是”\”。

在Linux系统中

Linux系统中只能使用正斜杠:
在Linux系统中使用反斜杠,路径是不可识别的:

windows系统

1、windows系统中默认使用反斜杠
image-20220319205148840.png
2、同时,在windows系统中,也可以使用正斜杠,访问文件目录
image-20220319205121869.png
3、在DOS命令中
\ 是用于路径的, 如 C:\users\user ,
/ 用于命令的参数,如dos命令 dir /a 表示显示当前目录下的文件,包括隐藏文件

网络路径

网络路径必须使用正斜杠
image-20220319205940559.png