File,IO流

发布时间 2023-11-17 15:01:33作者: 奕帆卷卷

变量,数组,对象,集合都是内容中的数据容器,他们在内存中,会因为断电,或者程序终止时会丢失

文件是非常重要的存储方式,他存储在计算机硬盘中,即便断电了,或者程序终止了,存储在硬盘文件中的数据也不会丢失

File类

File类,它就用来表示当前系统下的文件,或者文件夹,通过File类提供的方法可以获取文件大小,判断文件是否存在,创建文件,创建文件夹等

判断功能

 //1.创建File对象
        File f1 = new File("E/resource/meinv.jpg");
        System.out.println(f1.length());//返回的是字节数

        //2.file对象可以代表文件,也可以代表文件夹
        File f2 = new File("E:/resource");
        System.out.println(f2.length());//那文件夹的大小确实是不准确的

        //3.File对象代表的文件路径可以是不存在的
        File f3 = new File("E:/resource/aaabccc");
        System.out.println(f3.exists());

        //4.File对象的路径可以支持绝对路径,相对路径(相对)
        //什么是绝对路径?从磁盘开始一路寻找的路径
        File f4 = new File("E:/resource/menice.jpg");


获取功能

File f1 = new File("D:/resource/ab.txt");

// 5.public String getName():获取文件的名称(包含后缀)
System.out.println(f1.getName());

// 6.public long length():获取文件的大小,返回字节个数
System.out.println(f1.length());

// 7.public long lastModified():获取文件的最后修改时间。
long time = f1.lastModified();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(sdf.format(time));

// 8.public String getPath():获取创建文件对象时,使用的路径
File f2 = new File("D:\\resource\\ab.txt");
File f3 = new File("file-io-app\\src\\itheima.txt");
System.out.println(f2.getPath());
System.out.println(f3.getPath());

// 9.public String getAbsolutePath():获取绝对路径
System.out.println(f2.getAbsolutePath());
System.out.println(f3.getAbsolutePath());

创建和删除文件的方法

 // 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
        File f1 = new File("D:/resource/itheima2.txt");
        System.out.println(f1.createNewFile());

        // 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
        File f2 = new File("D:/resource/aaa");
        System.out.println(f2.mkdir());

        // 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
        File f3 = new File("D:/resource/bbb/ccc/ddd/eee/fff/ggg");
        System.out.println(f3.mkdirs());

        // 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。
        System.out.println(f1.delete());
        System.out.println(f2.delete());
        File f4 = new File("D:/resource");
        System.out.println(f4.delete());
    }

值得注意的是

1.mkdir():只能创建单级文件夹
2.mkdirs():才能创建多级文件夹
3.delete():文件可以直接删除,但是文件夹只能删除空的文件夹

遍历文件夹

用来获取文件夹中的内容

public String[] list() 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回

public File[] listFiles() 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)

  // 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。(了解)
        File f1 = new File("D:\\course\\待研发内容");
        String[] names = f1.list();
        for (String name : names) {
            System.out.println(name);
        }

        // 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
        File[] files = f1.listFiles();
//用listFiles()将文件的所有一级对象放入File[]数组中,可以用遍历方式进行对象操作(******)
        for (File file : files) {
            System.out.println(file.getAbsolutePath());//地址
        }

        File f = new File("D:/resource/aaa");
        File[] files1 = f.listFiles();
        System.out.println(Arrays.toString(files1));
    }

注意

1.当主调是文件时,或者路径不存在时,返回null
2.当主调是空文件时,返回一个长度为0的数组
3.当主调是一个有内容的文件夹时,将里面所有的一级文件和文件夹路径放在File数组中,并把数组返回
4.当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹路径放在File数组中,包含隐藏文件
5.当主调是一个文件夹,但是没有权限访问时,返回null

递归

通过以上学习,只能获取到一级文件夹,那么如何获取文件夹中子文件夹中的内容呢,那么我们就需要使用递归这个知识点

什么是递归?

递归是一种算法,从形式上来说,方法调用自己的形式被称为递归

递归的形式:有直接递归,间接递归

/**
 * 目标:认识一下递归的形式。
 */
public class RecursionTest1 {
    public static void main(String[] args) {
        test1();
    }

    // 直接方法递归
    public static void test1(){
        System.out.println("----test1---");
        test1(); // 直接方法递归
    }

    // 间接方法递归
    public static void test2(){
        System.out.println("---test2---");
        test3();
    }

    public static void test3(){
        test2(); // 间接递归
    }
}


如果执行上面代码,会进入死循环,最终导致栈内存溢出

递归算法执行过程

递归的执行

  • 列如:5的阶乘 =5*4*3*2*1
    
    假设f(n)表示n的阶乘,那么我们可以推导出下面的式子
    
    f(5)=5*f(5-1)
    f(4)=4*f(4-1)
    f(3)=3*f(3-1)
    ...
    
    /**
    
     * 目标:掌握递归的应用,执行流程和算法思想。
       */
       public class RecursionTest2 {
       public static void main(String[] args) {
       System.out.println("5的阶乘是:" + f(5));
    }
     
    //求n个数的阶乘
    public static int f(int n){
        // 终结点
        if(n == 1){
            return 1;
        }else {
            return f(n - 1) * n;
        }
    }
    }
    



该递归调用的特点:一层一层调用,再一层一层回返



### 递归文件搜索(**)

接下来我们用递归算法来遍历文件夹

案例需求:在`D:\\`判断下搜索QQ.exe这个文件,然后直接输出。

​```java
1.先调用文件夹的listFiles方法,获取文件夹的一级内容,得到一个数组
2.然后再遍历数组,获取数组中的File对象
3.因为File对象可能是文件也可能是文件夹,所以接下来就需要判断
   判断File对象如果是文件,就获取文件名,如果文件名是`QQ.exe`则打印,否则不打印
   判断File对象如果是文件夹,就递归执行1,2,3步骤
所以:把1,2,3步骤写成方法,递归调用即可。
 /**
     * @param dir      目录
     * @param fileName 要搜索的文件名称
     * @throws Exception
     */
    public static void searchFile(File dir, String fileName) throws Exception {
//1.把非法的情况都拦截住
//        exists()表示要检查的目录
//        isFile()表示是否是普通文件
        if (dir == null || !dir.exists() || dir.isFile()) {
            return;
        }
        //2.dir不是null,存在,一定是目录对象
        //  获取当前目录下的全部一级文件对象
        File[] files = dir.listFiles();

        //3.判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象
        if (files != null && files.length > 0) {
            //4.遍历全部一级对象
            for (File f : files) {
//5.判断文件是否是文件,还是文件夹
                if (f.isFile()) {
                    //是文件,判断这个文件名是否是我们要找的
                    if (f.getName().contains(fileName)) {
                        System.out.println("找到了" + f.getAbsoluteFile());
                        Runtime runtime = Runtime.getRuntime();
                        runtime.exec(f.getAbsolutePath());

                    }
                } else {
                    //是文件夹,继续重复这个过程
                    searchFile(f, fileName);
                }
            }
        }
    }
}

字符集

字符集的来历

由于计算机能够处理的数据只能是0和1组成的二进制数据,为了让计算机能够处理字符,于是美国人就把他们会用到的每一个字符进行编码(所谓编码,就是为了一个字符编一个二进制数据

美国人常用的字符有英文字母、标点符号、数字以及一些特殊字符,这些字符一共也不到128个,所以他们用1个字节来存储1字符就够了。 美国人把他们用到的字符和字符对应的编码总结成了一张码表,这张码表叫做ASCII码表(也叫ASCII字符集)。

其实计算机只在美国用是没有问题的,但是计算机慢慢的普及到全世界,当普及到中国的时候,在计算机中想要存储中文,那ASCII字符集就不够用了,因为中文太多了,随便数一数也有几万个字符。

于是中国人为了在计算机中存储中文,也编了一个中国人用的字符集叫做GBK字符集,这里面包含2万多个汉字字符,GBK中一个汉字采用两个字节来存储,为了能够显示英文字母,GBK字符集也兼容了ASCII字符集,在GBK字符集中一个字母还是采用一个字节来存储

需要我们注意汉字和字母的编码特点:

    1. 如果是存储字母,采用1个字节来存储,一共8位,其中第1位是0
    2. 如果是存储汉字,采用2个字节来存储,一共16位,其中第1位是1

当读取文件中的字符时,通过识别读取到的第1位是0还是1来判断是字母还是汉字

  • 如果读取到第1位是0,就认为是一个字母,此时往后读1个字节。
  • 如果读取到第1位是1,就认为是一个汉字,此时往后读2个字节。

Unicode字符集

们国家可以用GBK字符集来表示中国人使用的文字,那世界上还有很多其他的国家,他们也有自己的文字,他们也想要自己国家的文字在计算机中处理,于是其他国家也在搞自己的字符集,就这样全世界搞了上百个字符集,而且各个国家的字符集互不兼容。 这样其实很不利于国际化的交流,可能一个文件在我们国家的电脑上打开好好的,但是在其他国家打开就是乱码了。

为了解决各个国家字符集互不兼容的问题,由国际化标准组织牵头,设计了一套全世界通用的字符集,叫做Unicode字符集。在Unicode字符集中包含了世界上所有国家的文字,一个字符采用4个自己才存储。

在Unicode字符集中,采用一个字符4个字节的编码方案,又造成另一个问题:如果是说英语的国家,他们只需要用到26大小写字母,加上一些标点符号就够了,本身一个字节就可以表示完,用4个字节就有点浪费。

于是又对Unicode字符集中的字符进行了重新编码,一共设计了三种编码方案。分别是UTF-32、UTF-16、UTF-8; 其中比较常用的编码方案是UTF-8

1.UTF-8是一种可变长的编码方案,工分为4个长度区
2.英文字母、数字占1个字节兼容(ASCII编码)
3.汉字字符占3个字节
4.极少数字符占4个字节

字符集小结

ASCII字符集:《美国信息交换标准代码》,包含英文字母、数字、标点符号、控制字符
	特点:1个字符占1个字节

GBK字符集:中国人自己的字符集,兼容ASCII字符集,还包含2万多个汉字
	特点:1个字母占用1个字节;1个汉字占用2个字节

Unicode字符集:包含世界上所有国家的文字,有三种编码方案,最常用的是UTF-8
    UTF-8编码方案:英文字母、数字占1个字节兼容(ASCII编码)、汉字字符占3个字节

编码与解码

在String类类中就提供了相应的方法,可以完成编码和解码的操作

  • 编码:把字符串按照指定的字符集转换为字节数组
  • 解码:把字节数组按照指定的字符集转换为字符串
/**
 * 目标:掌握如何使用Java代码完成对字符的编码和解码。
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 1、编码
        String data = "a我b";
        byte[] bytes = data.getBytes(); // 默认是按照平台字符集(UTF-8)进行编码的。
        System.out.println(Arrays.toString(bytes));

        // 按照指定字符集进行编码。
        byte[] bytes1 = data.getBytes("GBK");
        System.out.println(Arrays.toString(bytes1));

        // 2、解码
        String s1 = new String(bytes); // 按照平台默认编码(UTF-8)解码
        System.out.println(s1);

        String s2 = new String(bytes1, "GBK");
        System.out.println(s2);
    }
}

IO流(字节流)

由于File只能操作文件,但是不能操作文件中的内容。所以我们要使用IO流的知识

IO流的作用:就是可以对文件或者网络中的数据进行读写的操作。

  • 把数据从磁盘,网络中读取到程序中来,用的是输入流
  • 把程序中的数据写入磁盘,网络中,用到的是输出流
  • 口令:输入流(读数据),输出流(写数据)

IO流分为两大派系

  1. 字节流:字节流又分为字节输入流,字节输出流
  2. 字符类:字符流由分为字符输入流,字符输出流

image-20231117103411962

FilelnputStream读取一个字节

字节流中的字节输入流,用InputStream来表示。但是InputStream是抽象类,我们用的是它的子类,叫FilelnputStream。

使用FilelnputStream读取文件中的字节数据

1.创建FilelnputStream文件字节输入流管道,与源文件接通
2.调用read()方法开始读取文件的字节数据
3.调用close()方法释放资源

代码如下

/**
 * 目标:掌握文件字节输入流,每次读取一个字节。
 */
public class FileInputStreamTest1 {
    public static void main(String[] args) throws Exception {
        // 1、创建文件字节输入流管道,与源文件接通。
        InputStream is = new FileInputStream(("file-io-app\\src\\itheima01.txt"));

        // 2、开始读取文件的字节数据。
        // public int read():每次读取一个字节返回,如果没有数据了,返回-1.
        int b; // 用于记住读取的字节。
        while ((b = is.read()) != -1){
            System.out.print((char) b);
        }
        
        //3、流使用完毕之后,必须关闭!释放系统资源!
        is.close();
    }
}

问题

由于一个中文在UTF-8编码方案中是占3个字节,采用一次读取一个字节的方式,读一个字节就相当于读了1/3个汉字,此时将这个字节转换为字符,是会有乱码的。

FilelnputStream读取多个字节

单个字节读取单个太慢,我们可以使用read(byte[] bytes)的重载方法,可以一次性读取多个字节

使用FilelnputStream一次读取多个字节的步骤如下

1.创建FilelnputStream文件字节输入流管道,与源文件接通
2.调用read(byte[] bytes)方法开始读取文件的字节数据
3.调用close()方法释放资源

代码如下:

/**
 * 目标:掌握使用FileInputStream每次读取多个字节。
 */
public class FileInputStreamTest2 {
    public static void main(String[] args) throws Exception {
        // 1、创建一个字节输入流对象代表字节输入流管道与源文件接通。
        InputStream is = new FileInputStream("file-io-app\\src\\itheima02.txt");

        // 2、开始读取文件中的字节数据:每次读取多个字节。
        //  public int read(byte b[]) throws IOException
        //  每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1.

        // 3、使用循环改造。
        byte[] buffer = new byte[3];
        int len; // 记住每次读取了多少个字节。  abc 66
        while ((len = is.read(buffer)) != -1){
            // 注意:读取多少,倒出多少。
            String rs = new String(buffer, 0 , len);
            System.out.print(rs);
        }
        // 性能得到了明显的提升!!
        // 这种方案也不能避免读取汉字输出乱码的问题!!

        is.close(); // 关闭流
    }
}
  • 需要我们注意的是:read(byte[] bytes)它的返回值,表示当前这一次读取的字节个数

假设有个文件

abcde

每次读取过程如下

也就是说,并不是每次读取的时候都把数组装满,比如数组是byte[] bytes = new byte[3];
第一次调用read(bytes)读取了3个字节(分别是97,98,99),并且往数组中存,此时返回值就是3
第二次调用read(bytes)读取了2个字节(分别是99,100),并且往数组中存,此时返回值是2
第三次调用read(bytes)文件中后面已经没有数据了,此时返回值为-1

注意:采用读取多个字节的方式,也有可能有乱码的,因为也有可能读取到半个汉字

FileInputStream读取全部字节

读取全部字节只需要一次性读取,然后把全部字节转换为一个字符串,就不会有乱码

代码如下

// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");

// 2、准备一个字节数组,大小与文件的大小正好一样大。
File f = new File("file-io-app\\src\\itheima03.txt");
long size = f.length();
byte[] buffer = new byte[(int) size];

int len = is.read(buffer);
System.out.println(new String(buffer));

//3、关闭流
is.close(); 

最后,还是要注意一个问题:一次读取所有字节虽然可以解决乱码问题,但是文件不能过大,如果文件过大,可能导致内存溢出。

FileOutputStream写字节

我们学习了使用FileInputStream读取文件中的字节数据,然后就该写入数据

往文件中写数据需要用到OutputStream下面的一个子类FileOutputStream。

步骤如下

  1. 创建FileOutputStream文件字节输出流管道,与目标文件接通
  2. 调用Wirte()方法往文件中写数据
  3. 调用close()方法释放资源
/**
 * 目标:掌握文件字节输出流FileOutputStream的使用。
 */
public class FileOutputStreamTest4 {
    public static void main(String[] args) throws Exception {
        // 1、创建一个字节输出流管道与目标文件接通。
        // 覆盖管道:覆盖之前的数据
//        OutputStream os =
//                new FileOutputStream("file-io-app/src/itheima04out.txt");

        // 追加数据的管道
        OutputStream os =
                new FileOutputStream("file-io-app/src/itheima04out.txt", true);

        // 2、开始写字节数据出去了
        os.write(97); // 97就是一个字节,代表a
        os.write('b'); // 'b'也是一个字节
        // os.write('磊'); // [ooo] 默认只能写出去一个字节

        byte[] bytes = "我爱你中国abc".getBytes();
        os.write(bytes);

        os.write(bytes, 0, 15);

        // 换行符
        os.write("\r\n".getBytes());

        os.close(); // 关闭流
    }
}

字节流复制文件

以上是字节输入流和字节输出流,现在我们可以将这两种流配合起来使用,将一个文件复制

比如:我们要复制一张图片,从磁盘D:/resource/meinv.png的一个位置,复制到C:/data/meinv.png位置。

思路如下

  1. 需要创建一个FileInputStream流与源文件接通,创建FileInputStream与目标文件接通
  2. 然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存入数组中
  3. 然后使用再使用FileOutputStream把字节数组中的有效元素,写入目标数组中
 //1.创建一个文件字节输出流与源文件接通
        try {
            InputStream is = new FileInputStream("....");
//2.创建一个文件字节输出流与目标文件接通
            OutputStream os = new FileOutputStream(".....");
//3.开始定义字节数组转移数据
            byte[] buffer = new byte[1024];  //1kb
            //1024+1024+3
            //4.开始转移字节到目标文件
            int len;//记录每次读取的字节数
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            os.close();
            is.close();

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

我们使用了os.close()可以释放资源,使用try{}catch(Exception e ){}

可以防止由于异常导致释放资源语句无法执行,导致会占用资源