文件操作 -- IO

文件操作 – IO

文件 :


文件相比大家都不陌生把 , 打开我们的电脑,随便进入到一个盘内,都能看到一些文件, 比如 : txt 文本, jpg 图片, mp4视频 ,还有文件夹(专业一点称为目录) 这些都是文件 .


但是 文件(File) 这个概念 在计算机里 , 也是 “一词多用” 。

正因如此 我们将上面这些文件都认为是狭义的文件 ,特点就是存储在硬盘上。

而广义的文件 : 泛指计算机中很多的 软硬件资源 , 操作系统中,把很多的硬件设备和软件资源抽象成了 文件 ,按照文件的方式来统一管理.


后面学习的网络编程中 就会讲到一个很重要的设备 网卡 , 网卡 是一个硬件设备,但是在操作系统中就把网卡这样的硬件给抽象成了一个文件 。


为啥这样做呢 ?


因为这样的操作 ,可以让我们进行网络编程带来很大的便利, 比如 我们想要通过网卡接收数据,直接按照读文件代码去写就可以了 , 想要进行网卡来发送数据,直接按照写文件的代码 去写即可,这里就可以通过对文件的操作来完成对网卡的操作,简化了开发。


本文只讨论狭义的文件 , 也就是硬盘上的数据.

文件路径 :


之前我们写过的代码,存储的数据,主要是靠变量, 而变量是存储在内存中,关闭程序后这些变量就会释放,啥都没有留下,而现在我们学习的文件是保存在硬盘上的,既然保存在硬盘上,那么文件都会有属于自己的路径

在这里插入图片描述


知道了 路径,下面来 了解一下路径的两种表示风格


1.绝对路径 : 以盘符开头的路径


2.相对路径 : 以当前所在的目录为基准 , 以 . 或者 .. 开头 (.有时候可以省略) , 找到指定的路径


相对路径图解 :

在这里插入图片描述

相对路径看完, 下面来了解一下 文件的类型。

文件的类型


我们的文件有 word , exe , 图片 ,视频 , 音频 , 源代码 , 动态库 等 ,这些不同的文件, 整体 可以归纳到 两类中 。


1.文本文件 (存的是文本, 字符串)

这里字符串 , 是由字符构成的, 每个字符,都是通过一个数字来表示的, 这里文本文件,里存的数据,一定是合法的字符, 都是再你指定字符编码表之内的数据


2.二进制文件 (存的是二进制数据,不一定是字符串了)

二进制文件中 数据就没有任何限制,可以存储任何你想要的数据。


那么这里就有一个问题 ,随便给你一个文件,如何区分该文件是文本还是二进制文件呢?

其实很简单,这里直接拿记事本打开 这个文件,如果乱码就说明是二进制文件,反之就是文本文件 。


注意 : 实际写代码 的时候,这两类文件的处理方式略有区别.


了解完上面这些背景知识, 下面就来了解一下 java 对于 文件的操作 .

java 中的文件操作


这里我们主要针对两方面 :


1.针对文件系统操作 , (文件的创建, 删除, 重命名 等)

2.针对文件内容操作 (文件的读和写操作)


下面继续 , 在 java 标准库中提供了一个 File类,这个 File 类就描述了一个文件/目录 , 基于这个对象就可以实现上面这两方面.

注意, 有File 对象,并不代表真实存在该文件


下面来看看 File 类 的属性和构造方法


File 类的属性

修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符(分隔符就是 / 或者 \ )
String 类型的表示
static char pathSeparator 依赖于系统的路径分隔符,char 类型的表示


File 类的构造方法

签名 说明
File(File parent, String child) 根据父目录 + 孩子文件路径,创建一个新的 File 实例
File(String pathname) 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径
File(String parent, String child) 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示


上面这些指定的路径参数,可以是绝对路径也可以是相对路径.

parent 表示当前文件所在的目录

child 自身的文件名


在 new File 对象的时候, 在构造方法的参数中,可以指定一个路径,此时File对象就代表 这个路径对应的文件了。


File 类的方法

修饰符及返回值类型 方法签名 说明
String getParent() 返回 File 对象的父目录文件路径
String getName() 返回 FIle 对象的纯文件名称
String getPath() 返回 File 对象的文件路径
String getAbsolutePath() 返回 File 对象的绝对路径
String getCanonicalPath() 返回 File 对象的修饰过的绝对路径
boolean exists() 判断 File 对象描述的文件是否真实存在
boolean isDirectory() 判断 File 对象代表的文件是否是一个目录
boolean isFile() 判断 File 对象代表的文件是否是一个普通文件
boolean createNewFile() 根据 File 对象,自动创建一个空文件。成功创建后返回 true
boolean delete() 根据 File 对象,删除该文件。成功删除后返回 true
void deleteOnExit() 根据 File 对象,标注文件将被删除,删除动作会到。JVM 运行结束时才会进行
String[] list() 返回 File 对象代表的目录下的所有文件名
File[] listFiles() 返回 File 对象代表的目录下的所有文件,以 File 对象表示
boolean mkdir() 创建 File 对象代表的目录
boolean mkdirs() 创建 File 对象代表的目录,如果必要,会创建中间目录
boolean renameTo(Filedest) 进行文件改名,也可以视为我们平时的剪切、粘贴操作
boolean canRead() 判断用户是否对文件有可读权限
boolean canWrite() 判断用户是否对文件有可写权限


别被这些方法吓到了, 不用背,用到的时候,查一下即可,下面就来使用几个方法 。


File 类的使用


图一 :

在这里插入图片描述


图二 : 相对路径

在这里插入图片描述


下面继续 :


这里来看三个比较实用的


1.判断文件是否真实存在 : exists()


2.判断文件是否是一个普通文件 : isFile()


3.判断文件是否是一个目录 : isDirectory()


可以看到我们再绝对路径下,使用这几个方法结果都是false , 表示在d盘下没有test.txt 这个文件,下面就来学习一下如何创建文件


这里需要使用 File 类 提供的方法 : createNewFile

在这里插入图片描述


既然有创建文件的方法,那么肯定有删除文件的方法 ,下面就来使用一下 delete 方法

在这里插入图片描述


关于删除文件的方法还有一个方法 : deleteOnExit ()


使用 deleteOnExit () 方法, 会在程序退出的时候,自动删除.

使用场景 : 程序中需要使用到一些 “临时文件” 的时候 ,就需要用到。


举个例子 :

在这里插入图片描述

了解完删除方法, 下面我们来创建一个目录 。


创建目录 : mkdir()

在这里插入图片描述


注意 : 这里的 mkdir方法只能创建 一级目录,如果想要创建多级目录 需要使用 mkdirs 方法


创建多级目录方法 : mkdirs ()

在这里插入图片描述

创建目录看完,下面使用 ,重命名方法 : renameTo ()


演示 :

在这里插入图片描述


重命名看完,下面来看看 list 、listFiles 这两个方法


list () : 返回 File 对象代表的目录下的所有文件名


返回 File 对象代表的目录下的所有文件,以 File 对象表示


演示 :

在这里插入图片描述


图二 :

在这里插入图片描述


到此 文件操作的相关方法差不多就看完了, 下面来学习一下, 关于 文件内容相关的操作

文件内容的相关操作


这里针对文件内容 ,使用 “流对象” 进行操作 .


解释一下 啥叫流 :

这里的流对象 是形象的比喻 , 在计算机里的很多概念,都使用了一定的修辞手法 .

这里的比喻就是一种常见的方法 .

另外 : 使用一个链表头节点/二叉树根节点,表示整个链表/二叉树 , 这也是一个修辞手法 。

此时使用的就是借代(局部表示整体),借代也是一种常见的修辞手法


下面继续

谈到流 这里第一印象 就是 水流 特点:生生不息 ,绵延不断 , 这里的流对象就是这样的 。


这里就可以想象一个 场景 :有一个水龙头 ,想要通过这个水龙头 可以接水 ,

假设 需要接100ml 的水 ,


这里就可以采用 一次接 100ml , 一次接完 , 也可以 一次接 50ml 分两次接 ,还可以一次接 10ml 分 10次 … 。


关于这里的装水,我们就可以随心所欲的安排 一次装多少水 ,只需要保证最终接收到 100ml 的水

这就是 流的一个特点 : 可以随心所欲的调整接收的数据量


对比一下 其他固体 的东西, 比如 体育室 里面的 篮球,这里就只能一个一个的拿, 能 一次性拿1.5 个吗显然是不可以的 ,这里就不是流 。


我们想要从文件读一百个字节

1.可以 一次 读 100个字节, 分一次读完

2.可以 一次 读 50个字节,分两次 读完

3.可以 一次 读 10个字节,分十次 读


这里我们读文件操作是不是就和 水流接水的操作相似 , 写文件同理 ,因此就把读写文件的相关操作称为 “流对象”


附上一张图:

在这里插入图片描述


知道了啥是流 , 下面来看看 java 标准库的流对象 .

这里从类型上,分为两个大类


1.字节流


字节流 : 针对二进制文件 ,以字节为单位进行读写


读相关的类

InputStream 抽象类FileInputStream 对应子类


写相关的类

OutputStream 抽象类FileOutPutStream 对应的子类


2.字符流


字符流 : 针对文本文件 , 以字符为单位进行读写

读相关的类

Reader 抽象类 , FileReader 对应的子类


写相关的类

Writer 抽象类 , FileWriter 对应的子类


别看这里的类很多,其实这里的类使用方式是非常固定的


核心就四个操作


1.打开文件. (构造对象)

2.关闭文件. (close)

3.读文件 . (read) --> 针对 InputStream / Reader

4.写文件 .(write) --> 针对 OutputStream / Write

说了这么多,下面来写个代码演示一下如何使用 .

字节流的读和写操作


读文件 : InputStream


图一 :
在这里插入图片描述


图二 : 带有一个参数版本的 read

在这里插入图片描述


这里输出型参数 ,就好比食堂打饭,将餐盘给 食堂阿姨,阿姨给你打饭 ,打完饭再将 餐盘还给你 .

在这里插入图片描述


这里 read 带有三个参数版本的 与 带有一个版本的类似 简单演示一下


buffer 名词解释 :

在这里插入图片描述


这里可以想象成 嗑瓜子 , 没有使用 缓冲区(buffer) , 磕一个瓜子 , 抛一次壳 (进行一次 IO 操作) , 这样嗑瓜子明显就非常麻烦 , 将我们的 手看成一个缓冲区, 当我们磕满一个手的瓜子壳 (此时相当于进行了一次IO操作, 读取了一个数组长度的数据 ),再去抛壳,是不是 效率就高了。。


读文件看完 , 下面瞧瞧写文件


写文件 :OutputStream


图一 :

在这里插入图片描述


有没有好奇,为啥 input 是读操作 , 而 output 是写操作 , input 翻译过来是 输入的意思 output 翻译过来是 输出的意思,那么为啥 输入不是写操作而是读操作呢 ?


关于这个问题 : 这里就需要注意 input 和 output 的方向,z这里的方向是以 CPU 为中心 进行看待的 ,

在这里插入图片描述


下面来看一个比较重要的东西 .


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


到此 字节流 的 读和写操作 就看完了, 下面来 简单 演示一下 字符流的读和写操作 (因为这些类的用法都一样)

字符流的读和写操作


1.字符流 读操作

在这里插入图片描述


2.写操作

在这里插入图片描述


补充 :flsush 方法 手动刷新缓冲区


上面 解释名词 buffer ,了解了一下缓冲区 ,并且知道了缓冲区存在的意义就是为了提高效率 .

下面就针对缓冲区 补充一个方法 flush , 这个方法是用来刷新缓冲区的 。


比如 : 在写数据的时候,需要把数据写到缓冲区里,然后再统一写入到硬盘, 如果当前的缓冲区已经写满了,就直接触发写硬盘操作,如果当前的缓冲区还没写满,如果 想提前将数据写入到硬盘里,这时就可以通过 flush 来手动 刷新缓冲区 .


之前 的代码 ,没有涉及到 flush , 是因为 代码并没有涉及到很多数据,很快执行完了 try 语句,而 try 语句结束的时候,会自动调用 close 方法 进行释放文件对象,关闭文件, 此时close 就会刷新缓冲区 。

在这里插入图片描述


再来补充一点 :Scanner 可以搭配流对象进行使用

在这里插入图片描述


此时 使用 scanner 读取 一个文件,就比较方便了 ,

在这里插入图片描述


最后再来补充一个 :使用 PrintWriter 类 进行写操作


PrintWriter 类 提供了 我熟悉的 print , printf , println 方法,方便我们进行写操作

在这里插入图片描述


此时关于文件内容就看完了,主要就是那几个类 , 下面就写几个关于文件的代码案例来熟悉一下 这些 API 。

代码案例

代码案例一 :


案例1:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件


这里会先 给顶一个目录, 目录里会包含很多的文件和子目录…


用户输入一个要查询的词 ,看看当前目录下(以及子目录里) 是否有匹配的结果 (文件名匹配) , 如果有匹配的结果,就进行删除



import java.io.File;
import java.util.Scanner;

public class Test4 {
    
    

    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
    
    

        // 让用户输入一个指定搜索的目录
        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入要搜索的路径: ");

        String basePath = scanner.next();

        // 针对用户输入进行简单判定
        File root = new File(basePath);

        // isDirectory() 判断当前的文件是否为目录文件
        if (!root.isDirectory()) {
    
    
            // 路径 不纯在, 或者知识一个普通的文件, 此时无法进行搜索
            System.out.println("输入的目录有误!");
            return;
        }

        // 前面没问题 此时再让用户输入一个 要删除的文件名
        System.out.println("请输入要删除的文件名: ");

        // 此处要使用 next , 而不要使用 nextLine !!!
        String nameToDelete = scanner.next();

        // 针对指定的路径进行扫描 , 这里就可以采用递归操作
        // 先从我们的 根目录触发 (root)
        // 先判定一下 , 当前的这个目录 ,是否包含要删除的文件,如果是就删除 ,否则就跳过下一个
        // 如果当前这里包含了一些目录 ,再针对子目录进行递归.

        scanDir(root, nameToDelete);
    }

    private static void scanDir(File root, String nameToDelete) {
    
    

        // 1. 先列除当前路径下包含的内容
        File[] files = root.listFiles();

        if (files == null) {
    
    
            // 当前 root 目录下没东西 , 是一个空目录
            // 此时就结束继续递归
            return;
        }

        // 2. 遍历当前的列出结果
        for (File f : files) {
    
    
            if (f.isDirectory()) {
    
    
                // 如果是目录就进一步 递归
                scanDir(f, nameToDelete);

            } else {
    
    
                // 如果是普通文件,则判定是否要删除
                if (f.getName().contains(nameToDelete)) {
    
    

                    // 这里可以再判断一下
                    System.out.println("确认是否要删除 " + f.getAbsolutePath() + " 吗?");

                    String choice = scanner.next();

                    if (choice.equals("y") || choice.equals("Y")) {
    
    
                        f.delete();
                        System.out.println("删除成功!");
                    } else {
    
    
                        System.out.println("删除取消!");
                    }
                }
            }
        }

    }


}

在这里插入图片描述

代码案例二 :


案例二 : 普通文件的复制


要求 : 把一个文件拷贝成另一个文件.


这里就非常简单, 就是把第一个文件按照字节依次读取,把结果写到另一个文件中 .


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

public class Test5 {
    
    
    public static void main(String[] args) {
    
    

        // 输入两个路径.
        // 源 和 目标 . (从那里拷贝到哪里)
        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("您当前输入的源路径有误");
        }

        File destFile = new File(destPath);
              
        if (destFile.isFile()) {
    
    
            // 如果已近存在 认为也不能拷贝 (比如说拷贝后的名字叫 坤.txt , 但是 目标目录下已经存在 坤.txt 此时拷贝相当于覆盖掉原来的文件)

            System.out.println("您当前输入的目标路径有误!");
            return;
        }

//          
//                if (destFile.exists()) {
    
    
//
//            if (destFile.isDirectory()) {
    
    
//                System.out.println("目标路径已存在 ,并且是一个目录,请确认路径是否正确");
//                return;
//            }
//
//            if (destFile.isFile()) {
    
    
//                System.out.println("目录路径已经存在,是否要进行覆盖? 覆盖 输入 y");
//                String ret = scanner.next();
//
//                if (!ret.toLowerCase().equals("y")) {
    
    
//                    System.out.println("停止覆盖");
//                    return;
//                }
//
//            }
//        }

//        try(InputStream inputStream = new FileInputStream(srcFile)){
    
    
//
//            try(OutputStream outputStream = new FileOutputStream(destFile)){
    
    
//
//            }
//
//        }catch (IOException e){
    
    
//            e.printStackTrace();
//        }

        // 进行拷贝操作
        try (InputStream inputStream = new FileInputStream(srcFile);

             OutputStream outputStream = new FileOutputStream(destFile)) {
    
    

            // 进行读文件操作.
            while (true) {
    
    
                byte[] buffer = new byte[1024];

                int len = inputStream.read(buffer);

                if (len == -1) {
    
    
                    break;
                }
                outputStream.write(buffer);
            }

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

    }
}

在这里插入图片描述

代码案例三 :


案例三 : 扫描指定目录 , 并找到名称或者内容中包含指定字符的所有普通文件 (不包含目录)


这里就是进行文件内容的查找

主要分为三步骤

1.输入一个路径

2.在输入一个要查找文件的关键字

3.递归的遍历文件 , 找到含有关键字的文件,将对应的文件路径打印出来。


其实 这个代码案例 就与第一个代码案例非常相似 .


import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;

public class Test6 {
    
    
    public static void main(String[] args) {
    
    

        Scanner sc = new Scanner(System.in);

        System.out.print("输入要扫描的路径: ");

        String rootDirectoryPath = sc.next();

        // 换一行
        System.out.println();

        System.out.print("输入要查询的文件名关键字: ");

        String keyword = sc.next();

        System.out.println();

        File file = new File(rootDirectoryPath);

        if (!file.isDirectory()) {
    
    
            // 此时不是目录文件, 表示当前的路径有误
            System.out.println("输入的路径错误! ");

            return;
        }

        // 进行递归遍历
        scannerDirectory(file, keyword);
    }

    private static void scannerDirectory(File file, String keyword) {
    
    

        // 1. 先列出 file 中 有那些内容
        File[] files = file.listFiles();

        if (files == null) {
    
    
            return;
        }
        for (File f : files) {
    
    
            if (f.isFile()) {
    
    
                // 此时 f 是普通的文件  ,
                // 针对普通文件的内容 进行判断 , 是否满足查询条件 (文件内容是否包含 关键字 keyword)
                if (containsKeyword(f, keyword)) {
    
    

                    // 此时普通文件中 含有我们需要查询的关键字, 将路径打印出来
                    System.out.println(f.getAbsolutePath());

                }
            } else if (f.isDirectory()) {
    
    

                // 此时 f 为目录文件,那么进入 目录 文件, 然后继续递归查找
                scannerDirectory(f, keyword);
            }
        }
    }

    private static boolean containsKeyword(File f, String keyword) {
    
    

        // 将 f 中的内容 读出来 ,进行遍历 ,放到一个 StringBuilder 中
        StringBuilder stringBuilder = new StringBuilder();

        try (Reader reader = new FileReader(f)) {
    
    

            char[] buffer = new char[1024];

            while (true) {
    
    
                int len = reader.read(buffer);

                if (len == -1) {
    
    
                    break;
                }

                // 将读取到的结果 放到 StringBuilder 中
                stringBuilder.append(buffer, 0, len);


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

        // indexOf 返回的是子串 (keyword) 的下标 , 如果 word 在StringBuilder 中不存在 ,则返回 -1
        return stringBuilder.indexOf(keyword) != -1;
    }
}


图示 :

在这里插入图片描述

小补充一点 : OutputStream 在写文件的时候,文件不存在 就会自动创建, InputStream 不行, 文件不存在,就抛异常了

到此 文件操作 就结束了, 比较简单, 就是一些 API 的使用 。

猜你喜欢

转载自blog.csdn.net/mu_tong_/article/details/128974308
今日推荐