JavaEE学习日记(2)---文件操作和IO

1.文件操作的路径

1.1 绝对路径

绝对路径是指文件的直接位置,没有参照物,一般是以一个盘符开头的,例如:D:\program\java\text 。

1.2 相对路径

而相对路径,一般是有一个基准目录。

1.3 分隔符

我们在文件中遇到的目录的分隔符一般会有两种:" / " 斜杠 + "\ " 反斜杠。在生活中我们多使用 " / " 斜杠,有较多的操作系统支持,且在使用过程中不需要转义,而反斜杠就需要转义。

2.文件系统操作(File类)

2.1 File 的构造方法

File(String pathname) 可以根据文件路径创建一个新的File实例,路径可以是绝对路径或者相对路径

2.2 File 的一些操作方法

2.2.1 getParent

2.2.2 getName
2.2.3 getPath
2.2.4 getAbsolutePath
2.2.5 getCanonicalPath

2.2.6 其他核心操作

3.文件内容操作

文件内容的操作主要包括 " 读 " + " 写 ",这些操作主要是需要借助一些类实现,是操作系统提供了相关的 api 函数,Java 对这些 api 进行了进一步的封装。

3.1 流对象

流对象是由操作系统提出的一个概念,在 Java 标准库中分了两类,一类是字节流(以字节为基本单位),另一类是字符流(以字符为基本单位)。

3.2 字节流

字节流主要是处理二进制文件的,主要根据对CPU而言又分为两类,一类是InputStream(输入),另一类是OutputStream(输出)。这两类都是抽象类,都依赖于实现它们的子类而去起作用。

3.3 字符流

字符流主要适合处理文本文件,主要分为两类:(Reader)读和(Writer)写,同样它们两个也都是抽象类,也需要依赖于它们的子类实现。

//如何区分二进制文件和文本文件

4. InputStream

4.1 打开文件

首先先处理一下异常

创建流对象的过程,就是相当于 " 打开文件 " ,打开完毕就可以进行读操作(InputStream 只能进行读操作),我们使用 read 方法进行读操作

4.2 read

使用 read 方法,我们就会发现有三个版本的 read 方法

4.2.1 read( )

表示一次只读一个字节,根据返回值来判断,这个方法返回类型是 Int

返回类型不是 Byte 的原因:当返回值为-1时,可以作为读取到文件末尾的标志,如果返回值为Byte 就没有这种效果了;

返回类型不使用 Short 的原因:受限于 " 字节对齐 " ,CPU处理4字节、8字节的效率高于处理1字节、2字节;

4.2.2 read( byte [ ] b )

一次读若干个字节, byte [ ] b 是一个 " 输出型参数 " ,参数初填写时,为空白数组,在方法内部,再对数组进行内容的填充,方法执行结束后,数据就保存在数组中了。返回值为数组已读取数据的长度。

4.2.3 read( byte [ ] b, int off, int len )

也是一次读若干个字节,但可以控制读取的长度,offset 是偏移量,可以理解为是数组的下标,len 是预期想要读取数组的长度。

4.2.4 使用

因为 FileNotFoundException 是 IOException 的子类,所以直接抛 IOException 就可以了

我们创建一个循环让它一直读取文件内容

看一下执行效果

虽然版本1可以帮我读取到文件中的内容,但我们一般不这么写,每次读取一个字节就需要触发一次 read ,read 是读硬盘,读硬盘操作比读内存会慢很多,所以我们尽量减少触发 read 的次数,此时我们就可以尝试使用版本2

即使是使用版本2,我们也需要搭配循环使用,因为我们也不知道文件的大小,不确定1个1024是否能把所有的数据读完

4.3 close

我们在创建资源,使用资源后,需要释放资源,一般是通过 close 方法来关闭文件释放资源

为什么我们之前在大多数情况都没有怎么手动释放文件?因为我们的操作系统有一个垃圾回收机制" GC "

//垃圾回收机制" GC "

Java的垃圾回收机制(Garbage Collection, GC)是JVM自动管理内存的核心机制,它自动识别并回收不再使用的对象所占内存,无需开发者手动干预

那为什么现在我们需要手动释放资源?因为在 JDBC 和流对象中,close 释放的不仅仅是内存,主要是文件描述符表,文件描述符表是一个文件资源,主要用来存储一些我们打开的文件的位置信息等,无法通过 " GC " 机制回收。文件描述符表本质上是一个顺序表,不能自动扩容,一但文件描述符表满了,系统就无法再打开文件。如果文件描述符表没有得到较好的释放,就有可能会出现 " 文件资源泄露 "

4.4 try catch

不知道我们有没有注意到,我们之前的解决异常的方式是直接抛了一个 IOException ,那如果我们现在要使用 try catch 应该如何使用呢?

去掉 IOException 后我们发现这里有三个地方都需要处理异常,如果我们用一个大的 try catch 将三个异常一次性处理就会发现,如果我们在读取处发现错误,就会略过下面的操作包括关闭资源,直接到打印错误了

此时我们可能会想到 finally ,那如果我们把 close 放在 finally 里面是否能解决这个问题呢?

结果是不能的,因为我们定义的变量只能在 try 里面生效,出了大括号(作用域)就失效了,那我们只能将变量的声明放在外面。

此时又发现 下面的 close 又出问题了,因为如果外面在实现变量的时侯就抛出异常,那么程序就会直接跳到下面的 finally 而,此时的变量还没有实现还为空,就会出现空指针,所以我们还要针对这个问题进行判断,还有包括 close 本身的异常,我们也需要去处理一下

这一整个流程下来,我们不难发现,这个过程太太太复杂繁琐了,那有没有更好的方法来解决这个问题呢?所以我们推出了 try with resources

在 try with resources 中,我们并不需要手动去处理 close 的问题,当 try 代码块执行完毕后,这个方法会帮我们自动处理 close 的问题。这是我们以后比较常见的写法。

//这个写法可以运用在 ReentrantLock(可重入锁)吗?

不可以, try with resources 要求 try() 里定义的对象需要实现 Closable 接口, Closable 本身提供了一个 close 方法让我们调用,而 ReentrantLock 只提供了 unlock() 方法,所以不能运用。

5. OutputStream

相对于 InputStream 是用来读文件的,而 OutputStream 是可以用来写文件的,我们通过实现抽象类 OutputStream 的子类 FileOutputStream ,然后调用其中的 write 方法来写文件

5.1 write

和上面的 read 相同, write 方法也有三种形式

与 read 的不同的一点是 write( byte[ ] b),此时并不是一个输出型参数了,其他用法和 read 相同

5.2 write 的使用

根据调用结果,我们发现好像调用前的文件内容消失了,因为 OutputStream 在默认情况下打开文件会清空文件内容!!!所以我们在使用前,需要仔细思考一下文件之前的内容是否需要删除。如果我们不想删除,有没有什么办法呢?有的,可以在构造方法中添加参数,让 OutputStream 不清空文件内容。

6. 代码案例

6.1 示例一

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

java 复制代码
import java.io.File;
import java.util.Scanner;

public class text1 {
    //示例一
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //1.让用户输入一个要扫描的路径
        System.out.println("请输入要扫描的路径:");
        String rootPath = scanner.next();

        //2.让用户输入一个要搜索的文件名关键字
        System.out.println("请输入要搜索的文件名关键字:");
        String word = scanner.next();

        //3.验证用户输入是否合法
        if (word.isEmpty()) {
            System.out.println("输入的关键字为空!");
            return;
        }
        File rootFile= new File(rootPath);
        if (!rootFile.isDirectory()) {
            //路径不是一个目录(路径不存在或者路径对应的是普通文件)
            System.out.println("路径输入有误!");
            return;
        }

        //4.开始搜索
        search(rootFile,word);
    }

    private static void search(File rootFile ,String word) {
        //列出 rootFile 内部所有的内容
        File[] files = rootFile.listFiles();
        if (files == null) {
            return;
        }
        //遍历每个文件
        for (File file : files) {
            //判断当前 file 是否是文件
            if (file.isFile()) {
                //判断当前文件是否包含word
                tryDelete(file,word);
            } else if (file.isDirectory()) {
                //对当前目录进行递归
                search(file,word);
            } else {
                break;
            }
        }
    }
    private static void tryDelete(File file, String word) {
        Scanner scanner = new Scanner(System.in);
        if (file.getName().contains(word)) {
            System.out.println("找到文件:"+file.getAbsolutePath());
            System.out.println("是否要删除:(Y/N)");
            String choice = scanner.next();
            if(choice.equals("Y")||choice.equals("y")) {
                file.delete();
            }else {
                System.out.println("取消删除");
            }
        }
    }
}

6.2 示例二

进行普通文件的复制

java 复制代码
import java.io.*;
import java.util.Scanner;

public class text2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //1.让用户输入要复制的文件路径,和要复制到的目标路径
        System.out.println("请输入要复制的源文件路径:");
        String srcPath = scanner.next();
        System.out.println("请输入要复制到的目标文件路径:");
        String destPath = scanner.next();

        //2.验证用户输入的路径是否合法
        File srcFile = new File(srcPath);
        if(!srcFile.isFile()) {
            System.out.println("源文件路径错误!");
            return;
        }
        //判断目标路径是否合法(不是要判断destPath本身是否存在,
        // 而是要判定destPath的parent是否存在且要是目录)
        File destFile = new File(destPath);
        File destParent = destFile.getParentFile();
        if (!destParent.isDirectory()){
            System.out.println("目标文件路径错误!");
            return;
        }

        //3.复制文件.打开两个文件,读取srcFile的内容,写入destFile
        //需要打开文件,参数直接使用 File 对象构造流对象
        try(InputStream inputStream = new FileInputStream(srcFile);
            OutputStream outputStream = new FileOutputStream(destFile)) {
            while (true) {
                byte[] bytes = new byte[1024];
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                outputStream.write(bytes,0,n);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("复制完成!");
    }
}

6.3 示例三

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

注意!!这个案例的性能一般,不要在大的文件下测试

java 复制代码
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;

public class text3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //1.让用户输入一个要搜索的根目录
        System.out.println("请输入要搜索的根目录:");
        String rootPath = scanner.next();

        //2.让用户输入一个要搜索的关键字
        System.out.println("请输入要搜索的关键字:");
        String word = scanner.next();

        //3.验证用户输入是否合法
        if (word.isEmpty()) {
            System.out.println("输入的关键字为空!");
            return;
        }
        File rootFile = new File(rootPath);
        if (!rootFile.isDirectory()) {
            System.out.println("输入的根目录有误!");
            return;
        }

        //4.进行递归搜索
        search(rootFile,word);
    }

    private static void search(File rootFile, String word) {
        //1.列出 rootFile 中所有的内容
        File[] files = rootFile.listFiles();
        if (files == null) {
            return;
        }

        //2.遍历每个文件对象
        for (File file:files) {
            if (file.isFile()) {
                //判断文件内容
                trySearch(file,word);
            } else if(file.isDirectory()) {
                search(file,word);
            }
        }
    }

    private static void trySearch(File file,String word) {
        //1.判断文件名是否包含
        if (file.getName().contains(word)) {
            System.out.println("找到了匹配的文件!文件名匹配!"+file.getAbsolutePath());
            return;
        }

        //2.判断文件内容是否包含
        //把文件完整的读取出来,再以字符串的形式查找
        try(Reader reader = new FileReader(file)) {
           StringBuilder stringBuilder = new StringBuilder();
           while (true) {
               char[] chars = new char[1024];
               int n = reader.read(chars);
               if (n == -1) {
                   break;
               }
               stringBuilder.append(chars,0,n);
           }
           //判断是否包含word
           if (stringBuilder.indexOf(word) >= 0) {
               System.out.println("找到了匹配的文件!内容匹配!"+file.getAbsolutePath());
           }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}
相关推荐
南境十里·墨染春水1 小时前
linux学习进展 Redis详解
linux·redis·学习
无风听海1 小时前
OAuth 2.0 response_type完全指南
java·开发语言·oauth
Cyan_RA91 小时前
SpringMVC 数据格式化处理 详解
java·开发语言·spring·mvc·ssm·springmvc·数据格式化
SunnyDays10111 小时前
Java 实现 PDF 中文文本查找与高亮的四种方法
java·pdf·查找文字
倒流时光三十年1 小时前
PostgreSQL 一次由 string_agg 引发的数据错位 Bug 深度复盘
java·postgresql·string_agg
Gofarlic_OMS1 小时前
Mastercam浮动许可利用率低:软件许可浪费,回收再分配
java·大数据·开发语言·架构·制造
AC赳赳老秦1 小时前
OpenClaw与飞书多维表格联动:自动同步工作数据、生成统计图表,实现高效管理
java·数据库·python·信息可视化·飞书·deepseek·openclaw
吃好睡好便好1 小时前
在Matlab中用sphere( )函数绘制球面图
开发语言·前端·javascript·学习·算法·matlab·信息可视化
开开心心就好1 小时前
带可视化界面的目录文件合并工具
java·运维·科技·游戏·tomcat·自动化·powerpoint