目录

JavaEE文件IO操作

【JavaEE】文件IO操作

什么是文件

  • 狭义上的文件指的就是存储在硬盘空间里面的文件.
  • 广义上的文件指的就是操作系统管理资源的一种机制, 很多软硬件资源把他抽象成文件来表示.
  • 文件夹在计算机专业术语上指的是目录
  • 为什么要用文件? 我们程序输入的数据一般随着进程结束, 那么这个数据是临时保存在内存里面的就会被清空. 如果我们下次运行程序还要执行这个数据, 那么就需要我们再次手动输入. 而如果我们不想手动输入, 就可以用第三方介质硬盘完成可持久化存储, 那么我们下次要用这部分的数据, 就直接可以从硬盘里面读取. 可是如果说我们要存储, 那么怎么管理这些数据. 就是用文件来管理组织这些数据.

文件的组织形式

  • 随着文件越来越多, 我们就需要一个数据结构来管理这些文件. 而我们就采用了树形结构来管理这些文件. 这里就引入了目录(文件夹)的概念, 一个目录里面可以有多个文件或目录, 目录里面的文件或目录就可以看成子节点, 而他们的父节点就是包含他们的目录.
  • 这里目录包含的文件或目录数量是不一定的, 即他的子节点数量不一定. 那么整个树形结构是一颗N叉树
  • 文件除了有数据内容之外,还有文件本身的一个信息,例如:文件名、文件类型、文件大小,文件路径、文件源地址,我们把这一块这信息称之文件的 “元信息”。
    https://i-blog.csdnimg.cn/direct/cce2b6c610d74285ae398f974205d912.png

文件路径

  • 现在硬盘上的文件越来越多. 那么如果说我们要查找一个文件的位置应该如何查找呢? 他的位置又该如何表示呢? 这就引入了文件路径这个概念
  • 从树根开始(电脑上通常从那个盘(C盘, D盘))开始查找对应的文件, 这个过程中所涉及到的包含查找文件的目录. 把这些目录记录下来, 就形成了文件路径. 一般使用"/“来分割路径中的多级目录
    https://i-blog.csdnimg.cn/direct/e7c44d5d03394c908b37584b8c765882.png

相对路径和绝对路径

  • 盘符(C盘或D盘这些)为根节点开始,逐渐向子节点延申,直到找到目标文件,那么找到该文件的所经过的节点,就称之为文件路径,从根节点开始扫描这种描述方式也被称之为文件的 “绝对路径”。
  • 举个例子:D:\JAVA\JDK\bin\jave.exe 这是一个绝对路径
    https://i-blog.csdnimg.cn/direct/bae7f629cecc45c9a399e0705f95ceaa.png
  • 而相对路径呢则不一定是从盘符开始, 他是看谁是他的基准路径.也就是上一级路径是谁?比如C:\user\root, 基准路径是C:\user. 如果说我们要查找root这个文件, 那么我们是基于C:\user这个路径下查找. 这就是基准路径
  • " . " 符号在相对路径中是一个特殊符号,表示当前目录
  • " … " 符号也是特殊符号,表示当前目录中的上级目录
  • 在相对路径的情况下要描述当前目录的上上级目录,或者级别更高的目录时,可以使用 “…\ …\ …\ ” 的形式(windowns操作系统允许使用\当分割符, 也可以使用/当分隔符),每一个 " …/ " 表示上一个目录。

文件的种类

  • 从开发的角度来分类, 文件分为二进制文件和文本文件.
  • 二进制文件是冯诺依曼祖师爷规定的, 所有的文件的是二进制的. 所以叫做二进制文件. 比如我们的 图片, 音视频文件都是二进制的.
  • 文本文件其实实质上也是二进制文件, 但是由于编码表的原因, 文本文件的二进制刚好能够对应上编码表上的文本(如ascill, utf8), 这些映射出来的文本能够组成有意义的文本. 就叫做文本文件

Java中的文件操作

File类的属性

  • 这里的分隔符指的就是我们文件路径中的分割符/或(反斜杠), 其中”" Windows系统支持, 其他大部分系统只支持"/"

https://i-blog.csdnimg.cn/direct/841e707970a649a1b3f439b1ce211c74.png

File类的构造方法

https://i-blog.csdnimg.cn/direct/61b2fcdf0f9a40448a5ffa48196a8ded.png

File类的常用方法

https://i-blog.csdnimg.cn/direct/0da06369e57045d588066eb6d8954f3b.png

JavaIO操作

文件缓冲区

  • 文件缓冲区的作用就是解决因为硬盘IO速度太慢导致内存等待时间太久, 这个时候为了解决速度匹配问题, 我们不让内存直接和硬盘进行交互. 而是让内存和文件缓冲区交互, 而文件缓冲区担任缓存硬盘数据的任务, 那么当缓冲区存满了数据. 一次性交给内存处理. 减少硬盘和内存IO次数
  • 这就好比我们嗑瓜子一样, 我们如果离垃圾桶很远, 那么我们每嗑一个瓜子如果都要跑到垃圾桶哪里去扔, 那么太耗费时间了. 不让用一张纸缓存瓜子壳, 一次性把大量的瓜子交给垃圾桶.
    https://i-blog.csdnimg.cn/direct/a6f20a008df54b7696ef22828b38a1da.png

文件流

  • 为什么叫文件流? 流我们就会想到一个词叫流水, 那么接(读取)这个水, 可以有很多种方法, 每次度100ml, 每次读50ml都可以. 那么对于数据也是一样. 这里的数据就是我们的流水. 每次可以读100字节, 每次也可以读50字节. 对于灌(写入)也是一样, 我们可以每次灌100ml, 也可以灌50ml. 那么我们写入数据也是一样, 每次可以写入100字节, 每次也可以写入50字节

https://i-blog.csdnimg.cn/direct/591352efaed342829da0c61b295a9c6f.png

字节流

  • 字节流就是我们读写数据是以字节为单位进行读写.

读(InputStream)

  • InputStream类的常用方法
    https://i-blog.csdnimg.cn/direct/f6f4073be57b43a8bb5603006c338f8b.png
  • 注意的是InputStream类是一个抽象类. 我们并不能直接使用他来创建对象从而使用对象方法. 而是要用他的子类FileInputStream 类。
  • FileInputStream类的构造方法
    https://i-blog.csdnimg.cn/direct/36470882407745dbbcaf968709dd2d5d.png
public class demo1 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("./test.txt")){
            //读文件操作
            while(true){
                byte[] data = new byte[1024];
                //这里进行读文件操作
                //read方法会尽量把data数组填满, 填不满的话, 能填几个是几个
                //此处的n就表示读取了几个字节
                int n = inputStream.read(data);
                if(n == -1){
                    break; //文件读取完
                }
                for(int i = 0; i < n; i++){
                    System.out.printf("0x%x\n", data[i]);
                }
            }
        }
    }
}
  • 这里我们读取test文件里面是你好的内容, 以字节方式读取, 存储在data数组中. 然后按16进制打印字节. 可以对照utf8编码表, 这样的字节码对应你好
    https://i-blog.csdnimg.cn/direct/aec9dbbf41324661ae9ba2acde6a8350.png
  • 这里解释一下 try(InputStream inputStream = new FileInputStream(“./test.txt”)), 这是try with resource写法, 这么写是为了我们打开文件后自动关闭文件也就是调用close方法. 如果文件资源不关闭那么就会导致文件资源泄露的问题(占着厕所不拉shit)
  • List item

Java中提供了"try-with-resources"语句,用于自动关闭实现了"AutoCloseable"接口(没实现这个接口就不能自动关闭, 比如ReentrantLock就是没有实现)的资源,无需显式调用close()方法来关闭资源。这个语句可以在代码块结束后自动关闭资源,无论代码块是正常执行完毕还是发生了异常。

写(OutputStream)

  • 他的常用方法

https://i-blog.csdnimg.cn/direct/c0aefd00389d4db7acefe6ca0b9dac0b.png

  • 对于OutputStream来说, 默认会尝试创建不存在的问题. 前面的InputStream则是要求文件必须存在.
  • OutputStream也是一个抽象类, 我们想要使用他的方法, 只能使用他的子类FileOutputStream来创建对象
public class demo2 {
    public static void main(String[] args) throws IOException {
        try(OutputStream outputStream = new FileOutputStream("./output.txt")){	//打开文件写, 写完后自动关闭文件
            byte[] bytes = {97, 98, 99};
            outputStream.write(bytes);  //把字节数组写入文件
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }
}

https://i-blog.csdnimg.cn/direct/b7ff9cc57a7541d99caa6a70a4b51323.png

  • 注意的是, OutputStream写的时候往文件里面写入数据的时候会默认把文件清空. 然后写入.这是操作系统的行为.
  • 如果说我们不想让操作系统每次写入数据的时候都把文件内容清空, 那么我们造创建Filie对象的时候为第二个参数指定true, 意思是以追加方式写入文件, 这样操作系统就不会清空文件了.
    https://i-blog.csdnimg.cn/direct/9bceb6c3a9644ef8a426e98517117508.png
  • 写入中文字符串
public class demo2 {
    public static void main(String[] args) throws IOException {
        try(OutputStream outputStream = new FileOutputStream("./output.txt", true)){
            String str = "你好中国";
            byte[] bytes = str.getBytes("utf-8");
            outputStream.write(bytes);  //把字节数组写入文件
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }
}
  • getBytes方法按照指定的字符编码格式转换为一个字节数组(byte[])。

字符流

  • 字节流就是我们读写数据是以字符为单位进行读写.

读(Reader)

  • Reader是抽象类, 如果要使用他的方法, 只能用FileReader即他的子类创建对象从而调用方法.
public class demo3 {
    public static void main(String[] args) throws IOException {
        try(Reader reader = new FileReader("./test.txt")){
            while(true){
                char[] buffer = new char[1024];
                int n = reader.read(buffer);    //把从文件中读取的数据放在buffer数组中, 并且返回读取了多少个数据
                if(n == -1){    //文件内容已经被读取完成
                    break;
                }
                for(int i = 0; i < n; i++){
                    System.out.println(buffer[i]);
                }
            }
        }
    }
}

https://i-blog.csdnimg.cn/direct/b8eeba629ef84fdbbf48a76e33ac6b51.png

  • 字符流在读取的时候会根据文件内容的编码格式进行解码. 这里test文件内容是按utf8编码格式存储的, 那么字符流读取的时候就会按utf8编码要求一样, utf8要求一个字符3个字节. 那么这里就会3个字节为1组读取. 读取完成后会按unicode编码自动解码, 转换成unicode字符. 这个时候buffer就得到了两个unicode编码对应的编码值字符

写(write)

public class demo4 {
    public static void main(String[] args) throws IOException {
        try(Writer writer = new FileWriter("./write.txt", true)){
            writer.write("hello world"); //写入字符串
        }
    }
}

  

https://i-blog.csdnimg.cn/direct/356e8f334f014d9c99941c5a9ab8f83d.png

小练习

case1

  • 扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
public class demo5 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要搜索的目录:");
        String rootDir = scanner.next();
        File rootFile = new File(rootDir);
        if(!rootFile.isDirectory()){
            System.out.println("输入的不是目录:");
            return;
        }
        System.out.println("请输入关键字:");
        String keyword = scanner.next();
        scanDir(rootFile, keyword);
    }
    private static void scanDir(File rootFile, String keyword){
        //列出当前目录的内容
        File[] files = rootFile.listFiles();
        if(files == null){
            return; //当前目录为空, 不可能有包含关键字的文件
        }
        //编码目录中的文件
        for(File file : files){
            System.out.println("遍历目录&文件: " + file.getAbsolutePath());
            // 3. 判断当前文件是目录还是普通文件.
            if(file.isFile()){
                // 4. 如果是普通文件, 则判断文件名是否包含关键字
                dealFile(file, keyword);
            }else{
                //如果是目录, 递归目录调用本方法
                scanDir(file, keyword);
            }
        }
    }
    private static void dealFile(File file, String keyword){
        if(file.getName().contains(keyword)){
            System.out.println("发现文件: " + file.getAbsolutePath() + ", 包含关键字! 是否删除? (y/n)");
            Scanner scanner = new Scanner(System.in);
            String input = scanner.next();
            if(input.equalsIgnoreCase("y")){ //忽略大小写
                file.delete();
                System.out.println("文件已删除!");
            }
        }
    }
}

case2

  • 进⾏普通⽂件的复制
  • 这里输入必须是绝对路径
    https://i-blog.csdnimg.cn/direct/f7066434d7c243b3bd9ba9ded72f648b.png

https://i-blog.csdnimg.cn/direct/f9669b4f768844a686a8426ed935c25d.png

public class demo6 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要复制文件路径:");
        String srcPath = scanner.next();
        System.out.println("请输入要复制到的文件路径:");
        String destPath = scanner.next();
        File srcFile = new File(srcPath);
        if(!srcFile.isFile()){
            System.out.println("源文件不存在或不是文件");
            return;
        }
        File destFile = new File(destPath); //要求destFile不一定存在, 但是destFile所在的目录必须存在
        // 例如, destFile 为 d:/test/test.txt, 则要求 d:/test 目录存在.
        if(!destFile.getParentFile().isDirectory()) {
            System.out.println("目标文件所在目录不存在");
            return;
        }
        // 真正进行复制文件的操作, 读取源文件, 写入复制文件
        // 此处不应该使用追加写. 需要确保目标文件和源文件一模一样.
        try(InputStream inputStream = new FileInputStream(srcFile);   //读取源文件
            OutputStream outputStream = new FileOutputStream(destFile)) //写入复制文件
        {
            while(true){
                byte[] buffer = new byte[1024];
                int n = inputStream.read(buffer);
                if(n == -1){
                    break;
                }
                // 此处的 write 不应该写整个 buf 数组的.
                // buf 数组不一定被填满. 要按照实际的 n 这个长度来写入
                // 读多少, 写多少
                outputStream.write(buffer, 0, n);
            }
        }
    }
}

case3

  • 扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
public class demo7 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要搜索的路径");
        String rootPath = scanner.next();
        File rootFile = new File(rootPath);
        if(!rootFile.isDirectory()){
            System.out.println("输入的路径不是目录!");
            return;
        }
        System.out.println("请输入要搜索的关键字");
        String keyword = scanner.next();
        scanDir(rootFile, keyword);
    }
    private static void scanDir(File rootFile, String keyword) throws IOException {
        File[] files = rootFile.listFiles(); //列出目录
        if(files == null){
            //当前目录为空, 不可能有包含关键字的普通文件
            return;
        }
        //遍历当前目录下的所有文件
        for(File file : files){
            if(file.isFile()){
                //是普通文件, 查看文件名是否包含关键字或文件内容是否包含关键字
                dealFile(file, keyword);
            }else{
                scanDir(file, keyword);
            }
        }
    }
    private static void dealFile(File file, String keyword) throws IOException {
        if(file.getName().contains(keyword)){
            System.out.println("文件名包含关键字:" + file.getAbsolutePath());
            return;
        }
        //文件名不包含关键字, 查看文件内容是否包含
       // 由于 keyword 是字符串. 就按照字符流的方式来处理. 读取出来自动转换unicode字符,
        StringBuilder stringBuilder = new StringBuilder();
        try(Reader reader = new FileReader(file)){
            while(true){
                char[] chars = new char[1024];
                int n = reader.read(chars);
                if(n == -1){ //文件数据已经全部读取完
                    break;
                }
                stringBuilder.append(chars, 0, n);
            }
            if(stringBuilder.indexOf(keyword) >= 0){ //indexOf查找字符串出现在stringBuilder的位置, 如果大于等于0, 说明包含keyword
                System.out.println("文件内容包含关键字: " + file.getAbsolutePath());
            }
            return;
        }
    }
}