文件操作和IO流

📃 认识文件

官方定义:计算机文件属于文件的一种,与普通文件载体不同,计算机文件是以计算机硬盘为载体存储在计算机上的信息集合。文件可以是文本文档、图片、程序等等。文件通常具有点+三个字母的文件扩展名,用于指示文件类型(例如,图片文件常常以 JPEG 格式保存并且文件扩展名为 .jpg)。
但是,我们一般会认为,我们平常写的word文档,文件夹📁,把这些当作问价,但是除了这些,我们往往会疏漏一个重要的文件那就是目录,也是一个重要的文件,
在操作系统中,还会使用一些文件来描述一些其他的硬件设备和软件资源比如网卡,显示器,键盘,操作系统把这些硬件也给抽象成了文件,我们当前讨论的文件还是主要对普通文件来讨论

📃文件的分类

站在程序猿的角度上,主要会把文件分为两类

  1. 文本文件:这里面存的都是字符,但是文本文件本质上存的也是字节,但是在文本文件中相邻的字节会组成字符
  2. 二进制文件 这里存储的都是字节了,他们相邻之间就没有关系的,就构不成字符了

我们之所以要知道,这两种文件的本质,是因为这种文件在我们写代码的时候存在着很大的差异

在我们所熟悉的这些文件中
.txt,.c,.java都是文本文件
.doc, ppt, zip, class,都是二进制文件,word,Excel都是二进制文件,像word这样的文本文件里面不只是单纯的文本,而是一个富文本,文本中带着各种各样格式化的信息

判读文件是什么类型的最简单的方法就是把文件拖到你的记事本里
如果是乱码就是二进制文件,如果不是乱码就是文本,(window系统下是可以验证的,我的是mac的有些不同),我们来看一下,在window下是什么样的效果

这是文本文件

请添加图片描述

二进制文件

请添加图片描述

📃关于目录结构

在计算机中,对与于文件的相关操作是通过操作系统中“文件系统”这个模块来负责的,文件系统中,一般是通过树形结构,来组织磁盘上的目录和文件的,这也就对应了我们数据结构的,但是这里的树并不是二叉树,因为文件脉络复杂,有多个方向,所以我们将这种数据结构叫做N叉树.如下请添加图片描述

那么,如果我们想要知道,文件在哪里,应该怎么办呢,总不能一个个树去找吧,所以我们有了路径的概念

📃文件的地址

在操作系统中,就通过路径这样的概念来描述一个文件的具体位置

📃 绝对路径和相对路径

  1. 绝对路径

已盘符开头的地址,在windo系统中我们会把各个文件放在不同的盘里C盘,D盘,在Mac系统中都在一个系统默认的Macintosh HD

请添加图片描述/Users/apple/Desktop/JAVA

  1. 相对路径

以 . 开头的或者 … 出头的(这里是两个点,博主的电脑打出不来😭 ),一个点表示当前路径,两个点表示当前路径的父目录(上级路径)
相对路径必须有一个基准路径,就是你当前所在的位置,不同的基准路径找到的找相同的文件,但是相对路径是不相同的

举个🌰
假设我现在在游乐场的门口,想去玩过山车🎢 ,但是我找不到,我就需要问工作人员,工作人员就会告诉我,从大门口出发,然后左转,一直走,再右转,就是过山车了,这是绝对路径,无论我在什么地方,他都会这么告诉我,哪怕过山车离我只有一步之遥, 他也会这么说,但是相对路径不一样,他会根据你当前的位置来判断,假如这个过山车就在你前方不到五十米,他就会告诉你直走五十米,这与绝对路径截然不同.

我们需要理解这两个概念,这是非常重要的知识,在后续的Java文件操作中中我们还会接触到这两个概念

📁Java中文件的操作

这主要包含两种操作第一种是对文件系统的操作,第二种就是对文件内容的操作也就是IO流,接下来我们一一介绍

📁 文件系统的操作——File类

对于文件系统的操作可以有以下功能

请添加图片描述

这是File类中的方法实现的,我们将File的一些常见方法列出

请添加图片描述
请添加图片描述
我们实现几个简单的列子

  1. 普通文件的创造与删除
import java.io.File;
import java.io.IOException;
public class Main {
    public static void main(String[] args) throws IOException {
        File file = new File("hello-world.txt"); // 要求该文件不存在,才能看到相同
的现象
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file.createNewFile());
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file.createNewFile());
   }
}
  1. 一级目录和多级目录的实现
    1)一级目录
import java.io.File;
import java.io.IOException;
public class Main {
    public static void main(String[] args) throws IOException {
        File dir = new File("some-dir"); // 要求该目录不存在,才能看到相同的现象
        System.out.println(dir.isDirectory());
        System.out.println(dir.isFile());
        System.out.println(dir.mkdir());
        System.out.println(dir.isDirectory());
        System.out.println(dir.isFile());
   }
}
 2)多级目录
import java.io.File;
import java.io.IOException;
public class Main {
    public static void main(String[] args) throws IOException {
        File dir = new File("some-parent\\some-dir"); // some-parent 和 somedir 都不存在
        System.out.println(dir.isDirectory());
        System.out.println(dir.isFile());
        System.out.println(dir.mkdirs());
        System.out.println(dir.isDirectory());
        System.out.println(dir.isFile());
   }
}

如果是多级目录mkdir需要➕ s

  1. 文件的重命名
public class Demo3 {
    public static void main(String[] args) {
        File f = new File("./wn");
        File F = new File("./Wn");
        System.out.println(f.isDirectory());
        System.out.println(f.isFile());
        f.renameTo(F);
        System.out.println(f.isDirectory());
        System.out.println(f.isFile());
    }
}

以上这些是📃文件系统的相关操作

📁文件内容的操作——IO流

在之前我们也提到过,文件的内容分为两种,一种是文本文件,里面存的全是字符,另一种是二进制文件,里面存的全是字节,所以对于文件内容的读取
Java标准库中提供了一组类,按照文件的类型,分为了两个系列,字节流对象,和字符流对象

什么叫做流对象?
流(Stream),这是一种形象的比喻,此处我们把文件内容比作成水流,打开开关就会有源源不断的感觉
举个🌰 ,假如我们想接100ml的水,可以每次接10ml,接十次,也可以每次接20ml,接五次,或者每次接100ml,一次接完
我们读取文件的时候也可以如此,源源不断的读取,直到读到最后一个字符/字节

  • 字节流对象
    读取文件—InputStream
    下面我们一步步来分析如何按字节流的方式读取文件内容
  1. 创造InputStream实例,当没有读取文件时,inputStream置为空
InputStream inputStream = null;
  1. 创建对象,同时也是在打开文件(文件应该存在)
inputStream = new FileInputStream("/Users/apple/Desktop/JAVA/test.txt");
  1. 将代码放到循环里,使用read()方法读取文件内容,将返回的值赋予int a,如果a为负一,就代表文件读取结束结束循环,否则就把文件一个一个打印出来,使用read方法会抛出异常
try {
            inputStream = new FileInputStream("/Users/apple/Desktop/JAVA/test.txt");
            while(true){
                int a = inputStream.read();
                if(a==-1){
                    break;
                }
                System.out.println(a);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

看到这里可能会用同学会问返回的类型不应该是byte吗,为什么用int来解接受呢?这是因为byte的取值范围是-128~127,
我们查阅源码会发现
请添加图片描述
在源码中,规定如果返回-1就代表文件读取内容结束,如果使用byte来接受,我们无法判断是文件读取结束,还是文件本身就有一个值是-1,与其这样猜测,不如直接将byte类型提升到int,就避免了这样的问题

  1. 读取完之后,释放资源

此处的释放资源是在文件读取完之后关闭的,但是如果在读文件时,抛出了异常,就无法将资源释放了,就会浪费资源,这样的话我们可以将close方法放到finally块中,这样无论抛没抛异常都可以将资源释放了.

finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

这样按照字符节读取文件就完成了,但是我们发现因为需要释放资源,这样的代码就显得很臃肿,于是我们对代码进行改进,使用try with resources即try(),使用此方法,就会自动释放资源,就不需要我们手动释放了

代码如下

 try(InputStream inputStream = new FileInputStream("/Users/apple/Desktop/JAVA/test.txt") ){
            while(true){
                int b = inputStream.read();
                if(b==-1){
                    break;
                }
                System.out.println(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

上面的代码是按照一个一个字节读取并打印的,我们也可以利用for循环一次把字符全都打印出来

        try(InputStream inputStream = new FileInputStream("/Users/apple/Desktop/JAVA/test.txt")){
            while(true){
                byte[] bytes = new byte[1024];
                int b = inputStream.read(bytes);
                if(b==-1){
                    break;
                }
                for (int i = 0; i < b; i++) {
                    System.out.println(bytes[i]);
                }
            }

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

在上面的代码我们引进了一个byte[]数组,然后将它当作参数传入到了read()方法中,但是我们需要强调,此处的参数并不是我们平时的输入型参数,而是输出型参数,就是把读取的字节流文件放到了byte数组中,当文件读取结束后,在将数组一次性读出

写入文件—OutputStream
写文件相对来说简单一些,只需要创造一个实例对象,把你需要写的文件路径传入,并使用write方法即可

//按照字节写文件
  public static void main1(String[] args)  {
        try(OutputStream outputStream = new FileOutputStream("/Users/apple/Desktop/JAVA/test.txt");){
            //一个一个输入
            outputStream.write(97);
            outputStream.write(97);
            outputStream.write(99);
            //一次性输入
            byte[] bytes =new byte[] {97,98,99};
            outputStream.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  • 字符流对象—Writer和Reader
  1. Reader
    基于IO流的介绍,字符流我们就不做过多的介绍,相比之下字符流更简单一下
//按照字符读文件
    public static void main1(String[] args) {
        try(Reader reader = new FileReader("/Users/apple/Desktop/JAVA/test.txt")){
            while(true){
                char[] chars = new char[1024];
                int len = reader.read(chars);
                if(len==-1){
                    break;
                }
                String str = new String(chars,0,len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

与IO流不同的只有输出方式不同,IO流是基于字符数组输出的,这个是基于字符串输出的.

  1. Writer
//按照字符串写文件
    public static void main(String[] args) {
        try(Writer writer = new FileWriter("/Users/apple/Desktop/JAVA/test.txt")) {
            writer.write("xyz");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

介绍完这么多,我们接下来使用几个案列来帮助大家理解、

📖 文件操作的案列

  • 案例一:扫描指定目录,并删除目标文件
import java.io.File;
import java.io.IOException;
import java.util.Scanner;

/**
 * 实现查找案列并删除文件
 */
public class TestDemo1 {
    public static void main(String[] args) throws IOException {
        //1.请输入扫描的目录,以及要删除的文件名
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描目录的路径 : ");
        String rootDirPath = scanner.next();
        System.out.println("请输入要扫描的文件名: ");
        String toDeleteName = scanner.next();
        File rootDir = new File(rootDirPath);
        if(!rootDir.isDirectory()){
            System.out.println("输入的路径不正确");
            return;
        }
        //2.遍历目录,寻找要删除的文件,把指定目录的文件所有的目录和文件都扫描一遍,从而要找到删除的文件
        //此方法要递归遍历,并删除文件
        scanDir(rootDir,toDeleteName);
    }

    private static void scanDir(File rootDir, String toDeleteName) throws IOException {
        //1.先列出rootDir有哪些文件
        File[] files = rootDir.listFiles();
        if(files.length==0){
            return;
        }
        for (File file:files) {
             if(file.isFile()){
                if (file.getName().contains(toDeleteName)){
                    deleteFile(file);
                }
            }else if(file.isDirectory()){
                scanDir(file,toDeleteName);
            }
        }
    }

    private static void deleteFile(File file) {
        try {
            System.out.println(file.getCanonicalPath()+"确认要删除吗?(Y/y)");
            Scanner scanner = new Scanner(System.in);
            String choice = scanner.next();
            if(choice.equals("Y")||choice.equals("y")){
                file.delete();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此段代码重点的是这个scan扫描函数,需要使用递归的方法扫描目录和文件,如果是目录问价就继续递归,如果是普通文件就判断这个文件是否是我们要删除的文件,前面我们说到目录文件是一个N叉树,所以我们需要逐个遍历,即递归.

案例二:普通文件的复制

/**
 * 普通文件的复制
 */
public class TestDemo2 {
    public static void main(String[] args)  {
        //1.输入两个路径
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入要复制的文件路径");
        String str1 = scanner.nextLine();
        System.out.println("输入复制到的目标路径");
        String str2 = scanner.nextLine();
        File file = new File(str1);
        if(!file.isFile()){
            System.out.println("输入的路径不正确");
            return;
        }
        //此处不需要检查目标文件是否存在,OutputStream会自动创建不存在的文件
        //2.读取源文件,拷贝到目标文件
        try(InputStream inputStream = new FileInputStream(str1)){
           try( OutputStream outputStream = new FileOutputStream(str2)){
               byte[] buffer = new byte[1024];
               while(true){
                   int len = inputStream.read(buffer);
                   if(len==-1){
                       break;
                   }
                   outputStream.write(buffer,0,len);
               }
           }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段代码的重点就是我们的IO流的使用,使用InputStream读入要复制的文件,使用OutputStream把读取的文件写到目标文件中

案例三—扫瞄指定目录,判断这个文件名称或者文件内容中是否包含我们要寻找的指定字符

import java.io.*;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: zy
 * Date: 2022-04-08
 * Time; 18:19
 */
public class TestDemo3 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径");
        String rootDirPath = scanner.next();
        System.out.println("请输入要查询的关键字");
        String word = scanner.next();
        File rootDir = new File(rootDirPath);
        if(!rootDir.isDirectory()){
            System.out.println("路径非法");
            return ;
        }
        scanDir(rootDir,word);
    }

    private static void scanDir(File rootDir, String word) throws IOException {
        File[] files =rootDir.listFiles();
        if(files==null){
            return;
        }
        for (File file:files) {
            if(file.isDirectory()){
                scanDir(file,word);
            }else if(file.isFile()){
                if(containWord(file,word)){
                    System.out.println(file.getCanonicalPath());
                }
            }
        }
    }

    private static boolean containWord(File file, String word) {
        StringBuffer stringBuffer = new StringBuffer();
        try(Reader reader = new FileReader(file)) {
            char[] buffer = new char[1024];
            while(true){
                int len = reader.read(buffer);
                if(len==-1){
                    break;
                }
                stringBuffer.append(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //indexOf返回的是子串的下标,如果word在stringBuffer不存在,返回-1;
        return stringBuffer.indexOf(word)!=-1;
    }
}

这种扫描文件的方式比较低效,我们可以使用多线程的方式来提高效率,由线程池来扫描和遍历文件

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值