Java文件IO操作及案例

news2024/10/5 14:17:35

文章目录

  • 一. 文件概述
    • 1. 狭义和广义上的文件
    • 2. 文件的分类
    • 3. 文件的路径
  • 二. 针对文件系统的操作
    • 1. File类的属性和构造
    • 2. File类的获取操作
    • 3. File类的判断操作
    • 4. 文件的创建和删除
    • 5. 其他的常用方法
  • 三. 对文件内容进行读写
    • 1. IO流对象
    • 2. 文件的读操作
    • 3. 文件的写操作
    • 4. Scanner搭配流对象进行读取
    • 5. PrintStream和PrintWriter
    • 6. 补充: 为什么使用后一定要关闭文件资源
  • 四. 文件操作案例
    • 1. 扫描指定文件并根据文件名删除指定普通文件
    • 2. 普通文件的复制
    • 3. 获取含有指定字符串的普通文件

一. 文件概述

1. 狭义和广义上的文件

平时我们所提到的文件, 常见的比如 .jpg,.txt, .mp3, .mp4这些, 还有放置这些格式文件的文件夹, 像这些文件都是存储在硬盘上的格式文件或者目录(文件夹), 这些就是狭义上的文件.

img

而在计算机中文件是一个广义的概念, 广义上的文件泛指计算机中很多的软件硬件资源, 操作系统把许多软硬件资源抽象成了文件, 按照文件的方式来进行统一管理, 比如网络设备中的网卡, 操作系统将网卡也抽象成了一种 “文件”, 以简化程序的开发, 除了网卡, 还有我们所使用的键盘和显示器等也被操作系统抽象为了文件.

而在下面的内容中, 只涉及到狭义的文件.

2. 文件的分类

常见的许多格式文件(如word, execl, 图片, 音频, 视频…), 整体可以归纳到两类中:

  1. 文本文件(存放的是文本, 字符串), 由字符构成, 都是指定字符集编码表里的数据.
  2. 二进制文件(存放的是二进制数据), 可以存放任何想存放的数据.

文本文件使用记事本打开里面显示的是正常的文本内容, 而二进制文件使用记事本打开里面大概率是会乱码的, 实际写代码的时候,这两类文件的处理方式略有差别.

3. 文件的路径

每个文件, 在硬盘上都有一个具体的 “路径”, 文件的路径包括两种, 一种是绝对路径, 以盘符(C:或者D:)开头, 另一种是相对路径, 以...开头的路径, 相对路径是基于当前所在目录(工作目录)来说的, 使用/(在windows上也可以使用\, 但是更建议使用的是/, 使用\在字符串中容易被解析为转义字符)来分割不同的目录级别.

C:/Program Files/Java/jdk1.8.0_192/bin目录为例, 以该路径为工作目录, 假设我们要在bin文件夹中找到javac.exe文件, 则使用相对路径表示为./javac.exe, 这里的.就表示当前所在目录的路径C:/Program Files/Java/jdk1.8.0_192/bin, 而使用绝对路径表示就更清晰了C:/Program Files/Java/jdk1.8.0_192/bin/javac.exe.

img

还是以C:/Program Files/Java/jdk1.8.0_192/bin目录为工作目录, 我们要表示与bin文件夹在同一目录中的src.zip文件, 我们可以使用..表示工作目录的父路径C:/Program Files/Java/jdk1.8.0_192, 该文件的相对路径为../src.zip, 绝对路径为C:/Program Files/Java/jdk1.8.0_192/src.zip.

img

二. 针对文件系统的操作

针对文件系统的操作, 主要是文件/目录的创建和删除, 文件的重命名等.

Java标准库中提供了一个File类, 能够完成对某一路径上的文件进行操作.

1. File类的属性和构造

File类位于import java.io包下, 该类相当于一个抽象的文件路径, 能够在指定的路径中进行文件的创建, 删除, 修改文件等, 但是不能对文件的内容进行操作.

下面先来介绍File类中的常见属性和方法.

🎯File类的属性:

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

🎯File类的构造方法:

方法说明
File(File parent, String child)根据父文件对象 + 孩子文件路径, 创建文件
File(String path name)根据一个文件路径去创建文件
File(String parent, String child)根据父目录(路径) + 孩子路径,创建文件

File类实例对象就是表示一个文件路径, 上面构造方法的参数中涉及到的String表示的路径, 可以是绝对路径, 也可以是相对路径; 如果通过绝对路径创建文件的实例对象, 那么就是在所给的绝对路径目录下进行相关的文件操作, 而如果是相对路径, 在不同的环境下, 工作目录是不相同的, 比如如果是在控制台的命令行中操作, 则会以在命令行运行程序的目录作为工作目录, 如果在IDEA中操作, 那工作目录默认就是你的当前项目所在的目录.

public static void main(String[] args) throws IOException 
    File file = new File("D:/test.txt");
}

要注意的的是上面传入的路径是D:/test.txt, 但实际上在这里并不要求在在D:/这里真的有个test.txt文件, 后序如果需要使用这个文件但没有的话是可以通过createNewFile方法创建的.

2. File类的获取操作

返回值方法说明
StinggetParent()返回File对象父目录文件路径
StringgetName()返回File对象文件名
StringgetPath()返回File对象文件路径
StringgetAbsolutePath()返回File对象的绝对路径
StringgetCanonicalPath()返回File对象修饰过的绝对路径

代码示例:

import java.io.File;
import java.io.IOException;

public class TestDemo {
    public static void main(String[] args) throws IOException {

        //通过绝对路径字符串创建文件对象, 创建文件对象时不会自动创建文件
        File file = new File("D:/test.txt");
        //获取当前文件对象的文件名
        System.out.println(file.getName());
        //获取当前文件的绝对路径字符串
        System.out.println(file.getAbsolutePath());
        //获取当前文件省略后的绝对路径字符串
        System.out.println(file.getCanonicalPath());
        //获取当前文件父目录的路径字符串
        System.out.println(file.getParent());
        //获取当前文件对象构造时所传入的路径字符串
        System.out.println(file.getPath());

        System.out.println("================");

        //通过相对路径字符串创建文件对象
        File file2 = new File("./test.txt");
        //获取当前文件对象的文件名
        System.out.println(file2.getName());
        //获取当前文件的绝对路径字符串
        System.out.println(file2.getAbsolutePath());
        //获取当前文件省略后的绝对路径字符串
        System.out.println(file2.getCanonicalPath());
        //获取当前文件父目录的路径字符串
        System.out.println(file2.getParent());
        //获取当前文件对象构造时所传入的路径字符串
        System.out.println(file2.getPath());
    }
}

执行结果:

img

3. File类的判断操作

返回值方法说明
booleanexists()判断File对象代表的文件是否真实存在
booleanisDirectory()判断File对象代表的文件是否是一个目录
booleanisFile()判断File对象代表的文件是否是一个普通文件
booleanisHidden()判断File对象代表的文件是否是一个隐藏文件
booleanisAbsolute()判断File对象路径名是否是绝对路径
booleancanRead()判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限
booleancanExecute()判断用户是否对文件有可执行权限

代码示例:

在IDEA的在工作目录下存在一个文件test.txt, 这里使用File来判断该文件的一些相关属性.

img

import java.io.File;

public class TestDemo1 {
    public static void main(String[] args) {
        File file = new File("./test.txt");

        //判断文件是否存在
        System.out.println(file.exists());
        //判断File对象的路径是否是绝对路径
        System.out.println(file.isAbsolute());
        //判断文件是否是一个普通文件
        System.out.println(file.isFile());
        //判断文件是否是一个目录(文件夹)
        System.out.println(file.isDirectory());
        //判断文件是否是一个隐藏文件
        System.out.println(file.isHidden());
        //判断文件是否能够执行/读/写
        System.out.println(file.canExecute());
        System.out.println(file.canRead());
        System.out.println(file.canWrite());
    }
}

执行结果:

img

4. 文件的创建和删除

返回值方法说明
booleancreateNewFile()自动创建一个新文件,创建成功返回true
booleanmkdir()创建File对象表示的目录
booleanmkdir()创建File对象表示的多级目录
booleandelete()根据File对象删除该文件,删除成功返回true
voiddeleteOnExist()根据File对象,标注该文件将被删除,当JVM运行结束才会执行

代码示例:

还是在IDEA默认的工作目录下进行演示, 当前目录中只有之前保留的test.txt文件和项目配置文件.

img

🍂创建文件演示:

import java.io.File;
import java.io.IOException;

public class TestDemo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./abc.txt");
        //文件未创建的情况下创建新文件
        if (!file.exists()) {
            file.createNewFile();
            System.out.println("创建文件成功!");
        }
        //创建一个目录(文件夹)
        file = new File("./temp");
        file.mkdir();
        System.out.println("创建目录成功!");
        //创建多个目录
        file = new File("./aaa/bbb/ccc");
        file.mkdirs();
        System.out.println("创建多个文件夹成功!");
    }
}

执行结果:

img

如果程序运行后工作目录中没有实时看到对应文件或者目录创建的话, 可以右键项目, 然后选择Reload from Disk刷新一下即可.

img

🍂删除文件演示:

import java.io.File;

public class TestDemo3 {
    public static void main(String[] args) {
        //删除操作
        File file = new File("./test.txt");
        file.delete();
    }
}

执行结果:

img

关于删除方法中的deleteOnExist()不太好演示, 这里解释一下, 这个方法的功能是在程序退出的时候, 自动删除文件, 当我们在程序中需要用到一些 “临时文件” 的时候, 可以用到这个方法, 保证在程序退出的同时把临时文件给删除掉.

什么时候会用到临时文件的呢, 比如这里打开一个word文档, 在打开的同时就会在同级目录下生成出一个临时文件, 关闭word, 这个临时文件被删除了, 临时文件发挥的功能是实时保存我们实时编辑的内容(尤其是没有保存的内容), 防止我们编辑了很多内容但由于一些问题导致我们没有进行保存, 比如工作一半突然停电了, 东西还没来得及保存, 但由于临时文件中实时保存了我们编辑的内容, 等到再次启动word文档时, 就会提醒我们是否要恢复之前的编辑内容.

5. 其他的常用方法

返回值方法说明
String[]list()返回File对象目录下所有的文件名
File[]listFiles()返回File对象目录下的所有文件,以File对象的形式表示
booleanrenameTo(File dest)对文件进行改名

代码示例:

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class TestDemo4 {
    public static void main(String[] args) throws IOException {
        File file = new File("./");
        File tmp1 = new File("./aaa");
        File tmp2 = new File("./eee");

        //获取当前路径目录下所有文件名
        System.out.println(Arrays.toString(file.list()));
        //将目录aaa更名为eee
        System.out.println(tmp1.renameTo(tmp2));
        //重新获取当前路径目录下所有文件名
        System.out.println(Arrays.toString(file.list()));
    }
}

执行结果:

img

三. 对文件内容进行读写

1. IO流对象

针对文件内容的读写, Java标准库中提供了两组类和接口, 分别用来针对两类文件的内容进行读写, 一组是字节流对象, 可以针对二进制文件进行读写, 另外一组是字符流对象, 可以针对文本文件进行读写.

对于二进制文件内容的读写可以使用InputStream(读)和OutputStream(写)来操作, 对于文本文件内容的读写可以使用Reader(读)和Writer(写)来操作, 这四个java提供的标准类都是抽象类, 可以认为不同的输入/输出设备都可以对应一个前面的类, 但这里只关心文件的读写, 我们在使用时使用这几个实现类来创建实例对象即可, 分别对应的是FileInputStream, FileOutputStream, FileReaderFileWriter类.

img

那么为什么针对文件内容操作这些类叫做 “流对象”? 其实这是一个形象的比喻, 想象一下水流的滔滔不绝, 接水时我们可以选择一次接一点水, 也可以选择一次接很多水; 而我们使用这些类对文件进行读写操作时, 可以一次读一个字节/字符, 也可以一次读多个字节/字符, 就像水流一样, 所以将这些对象叫做流对象; 想象有一个羽毛球筒, 筒里有很多羽毛球, 但羽毛球只能一个一个取, 这就不是流.

对文件进行写操作称为输出流, 对文件进行读操作称为输入流.

img

还有一个点这里简单说一下, 计算机中的读和写的方向, 是以CPU为中心来看待的, 内存更接近CPU, 而硬盘相较于内存离CPU更远;

img

数据朝着CPU的方向流, 就是输入, 所以就把数据从硬盘到内存这个过程称为读(input); 数据向远离CPU的方向流向, 就是输出, 所以就把数据从内存到硬盘这个过程称为写(output).

那么下面就介绍具体的使用了, 虽然这里涉及到的类很多, 但在使用时规律性很强, 核心就是四个操作:

  1. 打开文件(构造对象).
  2. 读文件(read), 针对InputStream/Reader.
  3. 写文件(write), 针对OutputStream/Writer.
  4. 关闭文件(close).

不过还是有一些需要注意的细节, 具体看下面的内容.

2. 文件的读操作

🍂首先先来看读文件的操作, 读文件涉及到的抽象类和实现类有, 字节流: InputStream, FileInputStream, 字符流: Reader, FileReader.

FileInputStream的构造方法:

方法解释
FileInputStream(File file)利用 File 构造文件输入流
FileInputStream(String name)利用文件路径构造文件输入流

InputStream读文件涉及的方法:

返回值方法解释
intread()读取一个字节的数据, 返回值就是读取到的字节值, 返回 -1 代表已经完全读完了
intread(byte[] b)最多读取 b.length 字节的数据到 b 中,返回实际读到的字节数, -1 代表以及读完了, b是输出型参数
intread(byte[] b, int off, int len)最多读取 len - off 字节的数据到 b 中, 放在从 off 开始,返回实际读到的字节数, -1 代表已经读完了
voidclose()关闭字节流

对于上面无参版本的read()有一个小问题, 它的返回值是读取到的字节值, 那它的返回值按理来说设置为byte就行了, 为什么这里的返回值设置为了int呢?

其实这样设置是为了处理文件读完了情况, 我们知道使用这个方法文件读完了的情况下是返回值是一个-1, 如果使用byte类型作为返回值会出现一个问题, byte能表示的范围是-128到127, 当返回值为-1时区分不了文件是读完了还是读到的字节值就是-1, 而如果将读到一个字节的内容放在更大范围的内存当中(int), 此时读到的byte(-128到127)范围内的值就可以在int范围内表示为0到255这样的值, 此时返回-1就表示读到了文件的末尾了.

FileReader的构造方法:

方法解释
FileReader(File file)创建字符输人流与源文件对象接通
FileReader(File file,Charset charset)创建字符输人流与源文件对象接通,指定字符集
FileReader(String fileName)创建字符输人流与源文件路径接通
FileReader(String fileName,Charset charset)创建字符输人流与源文件路径接通,指定字符集

Reader读文件涉及的方法:

返回值方法解释
intread()读取一个字符的数据, 返回值就是读取到的字符值, 返回-1代表已经完全读完了
intread(char[] b)最多读取 b.length 个字符的数据到 b 中, 返回实际读到的字符数; -1 代表以及读完了, b是输出型参数
intread(char[] b, int off, int len)最多读取 len - off 个字符的数据到 b 中, 放在从 off 开始,返回实际读到的字符数, -1 代表已经读完了
voidclose()关闭字节流

字符流无参版本的read()返值设置值为int的原因和字节流是类似的.

代码示例:

img

项目所在的工作目录下有一个abc.txt的文本文件, 该文件中有9个字母aaabbbccc, 使用一个缓冲数组接收数据, 使用字节流对象读取, 得到的结果就是它们的Unicode码值(Unicode码中包含ASCll码), 如果要去验证对照, 首先要知道你使用的编译器是按照什么样的方式编码, 然后再去对应码表查询对照, IDEA默认是utf8(utf8是Unicode的实现方式之一), 如果你这里的文件是其他编码, 比如gbk, 就需要去和abk码表去对照, 使用字符流对象读取, 得到的就是文件中的字符.

要注意理解下面代码中read方法的使用, 参数是一个数组, 这就需要我们提前准备好一个缓冲数组, 把这个数组传入read方法, 让read方法内部针对这个数组进行填充, read会尽可能的把参数传进来的数组给填满, 然后我们需要对数组中读到的内容进行处理, 如果文件内容没读完的话就循环进行读取再次将数据填充到数组中(会覆盖上一次读取的内容).

而这个缓冲数组存在的意义是为了提高IO操作的效率, 因为访问硬盘/IO设备都是比较耗时的, 而缓冲数组的存在降低了IO的次数.

import java.io.*;

public class TestDemo5 {
    public static void main(String[] args) {
        try {
            //以字节为单位读取
            InputStream inputStream = new FileInputStream("./abc.txt");
            //以字符为单位读取
            Reader reader = new FileReader("./abc.txt");

            //字节流
            while (true) {
                byte[] bytes = new byte[1024];
                int len = inputStream.read(bytes);
                if (len == -1) {
                    break;
                }
                for (int i = 0; i < len; i++) {
                    System.out.print(bytes[i] + " ");
                }
            }
            //关闭文件
            inputStream.close();
            System.out.println();

            //字符流
            while (true) {
                char[] chars = new char[1024];
                int len = reader.read(chars);
                if (len == -1) break;
                for (int i = 0; i < len; i++) {
                    System.out.print(chars[i] + " ");
                }
            }
            //关闭文件
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

观察结果, 虽说字节流和字符流都可以读取文件内容, 但使用字符流读到的内容明显更直观一些.

img

文件读取完之后, 要记得关闭文件资源, 在上面的写法中有个问题是如果读取时发生异常就无法及时关闭文件了, 为了能够及时的关闭资源, 我们可以使用finally这个语法, 但更推荐下面的这种写法吗将读写对象定义在try中, 当try代码块执行完毕后会自动执行close关闭文件资源.

import java.io.*;

public class TestDemo6 {
    public static void main(String[] args) {
        //字节流
        try (InputStream inputStream = new FileInputStream("./abc.txt")) {
            while (true) {
                byte[] bytes = new byte[1024];
                int len = inputStream.read(bytes);
                if (len == -1) {
                    break;
                }
                for (int i = 0; i < len; i++) {
                    System.out.print(bytes[i] + "  ");
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println();

        //字符流
        try (Reader reader = new FileReader("./abc.txt");) {
            while (true) {
                char[] chars = new char[1024];
                int len = reader.read(chars);
                if (len == -1) {
                    break;
                }
                for (int i = 0; i < len; i++) {
                    System.out.print(chars[i] + "  ");
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

但注意并不是随便拿个对象放到try中都可以自动释放资源的, 只有实现了Closeable接口的类才能放到try中去释放, Closeable接口中提供的就是close方法.

3. 文件的写操作

🍂接下来看写文件的操作, 写文件涉及到的抽象类和实现类有, 字节流: OutputStream, FileOutputStream, 字符流: Writer, FileWriter.

FileOutputStream的构造方法:

方法解释
FileInputStream(File file)利用 File 构造文件输入流
FileInputStream(String name)利用文件路径构造文件输入流
FileOutputStream(String filepath)创建字节输出流与源文件路径接通
FileOutputStream((String filepath),boolean append)创建字节输出流与源文件路径接通,可追加数据

OutputStream写文件涉及的方法:

返回值方法解释
voidwrite(int b)写入一个字节的数据
voidwrite(byte[] b)将 b 这个字节数组中的数据全部写入 os 中
intwrite(byte[] b, int off, int len)将 b 这个字节数组中从 off 开始的数据写入 os 中,一共写 len 个
voidclose()关闭字节流
voidflush()刷新缓冲区

FileWriter的构造方法:

方法解释
FileWriter(File file)创建字符输出流管道与源文件对象接通
FileWriter(File file,boolean append)创建字符输出流管道与源文件接通,可追加数据
FileWriter(String filepath)创建字符输出流管道与源文件路径接通
FileWriter(String filepath,boolean append)创建字符输出流管道与源文件路径接通,可追加数据

Writer写文件涉及的方法:

返回值方法解释
voidwrite(int b)写入一个字符的数据
voidwrite(char[] b)将 b 这个字符数组中的数据全部写入 os 中
intwrite(char[] b, int off, int len)将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个
voidclose()关闭字符流
voidflush()刷新缓冲区

对于FileOutputStreamFileWriter, 默认会打开路径指向的文件, 如果没有对应路径的普通文件, 就会自动创建, 并且会清空文件里面原有内容再进行写入, 如果实现的效果是追加, 就需要设置构造方法中的append参数为true.

重要: 我们知道 I/O 的速度是很慢的, 所以, 大多的OutputStream为了减少设备操作的次数, 在写数据的时候都会将数据先暂时写入内存的一个指定区域里, 直到该区域满了或者其他指定条件时才真正将数据写入设备中, 这个区域一般称为缓冲区; 但造成一个结果, 就是我们写的数据, 很可能会遗留一部分在缓冲区中; 需要在最后或者合适的位置, 调用flush(刷新缓冲区)操作, 将数据刷到设备中, 当然close操作也会触发缓冲区的刷新.

代码示例:

img

向工作目录下的abc.txt文件中使用字节流追加写入一个helloworld1, 使用字符流追加写入一个helloworld2.

import java.io.*;

public class TestDemo7 {
    public static void main(String[] args) {
        //字节流
        try (OutputStream outputStream = new FileOutputStream("./abc.txt", true);) {
            String str = " hellowold1";
            //需要将字符串转化为字节数组
            outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }

        //字符流
        try (Writer writer = new FileWriter("./abc.txt", true);) {
            writer.write(" helloworld2");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

img

4. Scanner搭配流对象进行读取

首先看一个我们平时常见的一行代码, 这里的System.in其实就是一个输入流对象.

img

那么我们可以将构造参数传入为一个InputStream, 此时就可以使用Scannner从文件中进行读取了.

代码示例:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class TestDemo8 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./abc.txt")) {
            Scanner scanner = new Scanner(inputStream);
            //此时就是从文件中读取内容了
            while (scanner.hasNext()) {
                String str = scanner.next();
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

img

5. PrintStream和PrintWriter

PrintStream字节打印流是, PrintWriter是字符打印流, 一般使用的是PrintWriter, 灵活性更强.

打印流只有输出流, 没有输入流, 主要用于写入, 会把信息写到指定的一个位置(显示器或文件等).

构造方法:

方法解释
PrintStream(File file) / PrintWriter(File file)传入一个一个File对象
PrintStream(OutputStream out) / PrintWriterPrintStream传入一个输出流
PrintStream(OutputStream out, boolean autoFlush) / PrintWriter(Writer out, boolean autoFlush)传入一个输出流, autoFlush 表示是否自动刷新
PrintStream(String fileName, String csn) / 传入文件路径和文件编码传入的是文件路径和字符编码

PrintWriterPrintStream类中提供了我们熟悉的print/println/printf方法, 相当于是将OutputStream重新包装了一下, 可以更方便的输出数据.

代码示例:

要注意使用使用PrintWriter必须刷新缓冲区才能将数据成功写入文件.

import java.io.*;
public class TestDemo9 {
    public static void main(String[] args) throws IOException {
        //字节流
        try (OutputStream outputStream = new FileOutputStream("./abc.txt");) {
            PrintStream printStream = new PrintStream(outputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);

            printStream.println("张三");
            printWriter.print("李四");
            
            //使用PrintWriter必须刷新缓冲区才能将数据写入文件
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
   }
}

执行结果:

img

6. 补充: 为什么使用后一定要关闭文件资源

上面提到过, 文件读写结束后一定要记得使用close方法关闭文件资源, 那么这是为什么呢?

我们知道进程在操作系统内核中是使用PCB这样的数据结构来表示的, 一个线程对应一个PCB, 一个进程可以对应一个PCB, 也可以对应多个, PCB中有一个重要的属性是文件描述符表(相当于是一个数组)记录了该进程打开了哪些文件, 进程中的这些PCB是共用一个文件描述符表的.

img

每次打开一个文件就在文件描述符表中申请一个位置把, 把代表这个文件的对象放进去, 每次关闭文件时, 就会把文件描述符表的中对应位置的内容释放掉.

要知道文件描述符表是有大小是存在上限的(最大长度在不同系统上都不太一样, 基本就是几百到几千左右), 在文件资源不在使用的时候, 如果不能及时的释放, 那么就意味着文件描述符表可能很快就被占满了, 就会导致之后再想要打开文件资源的时候就会打开失败.

那Java当中不是有GC垃圾回收机制吗? 这里为啥还需要我们手动来释放资源, 要注意, GC操作会在回收流对象的时候去完成释放资源的操作, 但GC的这个回收时间不是即时的, 释放的可能就不是那么及时, 所以使用close方法去即时使用, 即时的手动释放是最合适的做法.

四. 文件操作案例

1. 扫描指定文件并根据文件名删除指定普通文件

🍂实现要求:

用户给定一个指定目录和一个要删除的文件, 查看给定目录中是否包含要删除的文件, 找到以后让用户选择是否要删除这个文件.

🍂实现思路:

我们知道文件的组织结构是多叉树的树形结构, 我们根据给定目录要去扫描目录及子目录下是否有匹配的结果, 这里的扫描无非就是多叉树的遍历, 和二叉树的遍历是类似的, 这里是基于多叉树的前序遍历来实现的.

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

/**
 * 扫描指定目录,判断指定目录中是否包含指定文件,
 * 如果包含,让用户选择是否删除文件
 */
public class TestDemo9 {
    private static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) {
        //首先输入一个指定的搜索目录
        System.out.println("请输入要搜索的路径: ");
        String basePath = scanner.next();

        //判断路径是否存在
        File root = new File(basePath);
        //判断路径是否是一个目录
        if (!root.isDirectory()) {
            //路径不存在,或者是一个普通文件,无法进行搜索
            System.out.println("输入的路径有误!");
        }

        //再输入一个要删除的文件名
        System.out.println("请输入一个要删除的文件名: ");
        String namrToDelete = scanner.next();

        //递归搜索路径和文件
        // 先从根目录出发(root)
        // 首先判定当前目录中是否存在要删除的文件,包含结束递归进行删除
        // 如果当前目录中还包含了其他子目录,再针对子目录进行递归
        scanDir(root, namrToDelete);
    }

    private static void scanDir(File root, String namrToDelete) {
        // 1. 先列出 root 目录下的文件和目录
        File[] files = root.listFiles();
        if (files == null) {
            // root目录是一个空目录,结束递归
            return;
        }
        // 2. 遍历当前列出的结果
        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是目录,继续递归
                scanDir(file, namrToDelete);
            } else {
                // 如果是普通文件,判断是否要删除的文件
                if (file.getName().contains(namrToDelete)) {
                    //选择是否要删除
                    System.out.println("确认是否要删除 " + file.getAbsolutePath() + " 吗? Y(y)/N(n)");
                    String choice = scanner.next();
                    if (choice.equals("y") || choice.equals("Y")) {
                        file.delete();
                        System.out.println("删除成功!");
                    } else {
                        System.out.println("删除取消!");
                    }
                }
            }
        }
    }
}

🍂程序测试:

  • 注意: 我们现在的方案性能较差, 所以尽量不要在太复杂的目录下或者大文件下实验.

现在测试将如下目录中的4.txt文件删除

img

🍂执行结果:

img

img

2. 普通文件的复制

文件的复制需要知道源路径和目标路径, 而给出源路径指向的必须是一个普通文件, 然后就读取源路径的文件, 将读到的数据写入到目标文件路径下即可.

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

/**
 * 普通文件的复制
 */
public class TestDemo10 {
    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("您当前输入的源文件的路径有误!");
            return;
        }

        File destFile = new File(destPath);
        if (destFile.isFile()) {
            //如果该文件已经存在,也不能进行拷贝
            System.out.println("您输入的目标路径有误");
            return;
        }

        //完成拷贝操作
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            byte[] buffer = new byte[1024];
            while (true) {
                int b = inputStream.read(buffer);
                if (b == -1) {
                    break;
                }
                outputStream.write(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🍂程序测试:

有如下两个目录, 第一目录是源目录, 其中有一图片, 第二目录是目标目录, 里面是什么都没有, 这里测试将源目录中的图片拷贝到目标中.

img

🍂执行结果:

img

img

3. 获取含有指定字符串的普通文件

🍂实现要求:

用户给定一个指定目录和字符串, 要求找到该目录下内所有文件内容含有该字符串的普通文件.

🍂实现思路:

其实这个案例相当于是上面两个案例的结合, 需要先对文件进行扫描, 找到其中的普通文件, 然后再去校验文件的内容中是否含有该字符串, 如果存在这样的文件, 则输出文件的路径.

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

/**
 * 扫描指定目录,查找文件内容中包含指定字符的所有普通文件
 */
public class TestDemo11 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        //首先输入一个指定的搜索目录
        System.out.println("请输入要搜索的路径: ");
        String basePath = scanner.next();

        //判断路径是否存在
        File root = new File(basePath);
        //判断路径是否是一个目录
        if (!root.isDirectory()) {
            //路径不存在,或者是一个普通文件,无法进行搜索
            System.out.println("输入的路径有误!");
        }

        System.out.println("输入你所指定的要包含的字符");
        String token = scanner.next();

        List<File> result = new ArrayList<>();
        scanDirWithContent(root, token, result);
        System.out.println("共找到了符合条件的文件" + result.size() + " 个,具体如下:");
        for (File file : result)  {
            System.out.println(file.getCanonicalPath());
        }
    }

    private static void scanDirWithContent(File root, String token, List<File> result) {
        // 1. 先列出 root 目录下的文件和目录
        File[] files = root.listFiles();
        if (files == null) {
            // root目录是一个空目录,结束递归
            return;
        }
        // 2. 遍历当前列出的结果
        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是目录,继续递归
                scanDirWithContent(file, token, result);
            } else {
                //判断该文件中是否包含指定字符
                if(isContentContains(file, token)){
                    result.add(file.getAbsoluteFile());
                }
            }
        }
    }

    private static boolean isContentContains(File file, String token) {
        StringBuilder stringBuilder = new StringBuilder();
        try (Reader reader = new FileReader(file)) {
            while (true) {
                char[] chars = new char[1024];
                int len = reader.read(chars);
                if (len == -1) {
                    break;
                }
                stringBuilder.append(chars, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return stringBuilder.indexOf(token) != -1;
    }
}

🍂程序测试:

在如下目录中查找所有文件内容中包含字符串helloworld的普通文件.

img

🍂执行结果:

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vim使用入门

目录vim模式介绍1.1 模式介绍1.2 模式之间切换1.3 进入vim1.4 退出vimvim模式介绍 1.1 模式介绍 vim具有6种基本模式和5种派生模式。 6种基本模式如下&#xff1a; 普通模式 vim启动后的默认模式。使用编辑器命令&#xff0c;比如移动光标&#xff0c;删除文本等等 普通模式进…

智能指针(二)—— unique_ptr、shared_ptr 的简单模拟实现

智能指针其实就是资源管理权限的转移&#xff0c;自己不想手动释放&#xff0c;交给一个对象管理&#xff0c;对象什么时候被销毁&#xff0c;这块资源也就什么时候被释放。unique_ptr 、shared_ptr 和weak_ptr 之间的区别如下&#xff1a; unique_ptr&#xff1a;字面意思是…

非全研究生开题-室内定位最新研究论文总结-1

序言 时间飞逝,23年3月底之前要把开题报告和综述写好。开学后研究了2个方向:serverless冷启动和室内定位; 春节期间在这2个选题之间犹豫不决,不断在心里面分析 经过反复比较对比,决定研究室内定位,也可以为后续完善产品,甚至后面创业打下基础。 后面我会持续深入,在…

【数据结构】链表必做题

写在开头学完链表&#xff0c;我们就要适当做一些题目来巩固知识&#xff0c;下面的一些精选题难度适中&#xff0c;很适合初学者练手。做题之前我们来了解一点概念&#xff0c;就是链表调用传参时&#xff0c;我们什么时候使用一级指针&#xff0c;什么时候使用二级指针&#…

在Linux上安装Maven和配置Maven仓库(v3.8.7)

记录&#xff1a;369场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用apache-maven-3.8.7安装Maven和配置Maven仓库。版本&#xff1a;JDK 1.8apache-maven-3.8.7名词&#xff1a;Apache Maven&#xff1a;Apache Maven is a software project management and comprehe…

31_内存马

内存马 一、php内存马(不死马) 原不死马文件是生成一个一句话木马文件之后,自动删除自身,并且抑制报错 即使删除生成的一句话木马文件 也会不断的继续生成一句话木马文件 除非重启服务,才能停止继续生成 <?php error_reporting(0);//抑制报错 unlink(__FILE__);//删除…

【工具】iOS代码混淆工具-iOS源码混淆

最新更新记录 V2.0.3&#xff08;2022年12月11日&#xff09;更新内容&#xff1a; 1、新增导入映射列表的逻辑&#xff1b; 2、优化修复其他混淆逻辑&#xff1b; 3、更新地址 - github 主要功能 ZFJObsLib是专业的iOS源码混淆工具&#xff0c;具体功能有方法混淆、属性…

AcWing 1082. 数字游戏(数位DP)

AcWing 1082. 数字游戏&#xff08;数位DP&#xff09;一、问题二、数位DP三、思路分析四、代码一、问题 二、数位DP 这道题是在一个区间内挑选满足某个条件的数&#xff0c;这是一个非常典型的数位DP的特点。 这道题是一道数位DP的题目&#xff0c;其实数位DP更像我们在高中…

5-TAMRA-TSA,5-TAMRA-Tyramide,5-四甲基罗丹明-酪酰胺

【中文名称】5-四甲基罗丹明-酪酰胺&#xff0c;5四甲基罗丹明酪酰胺【英文名称】 5-Tamra-Tyramide&#xff0c;5-TAMRA-Tyramide&#xff0c;5-Tamra-TSA&#xff0c;5-TAMRA-TSA【CAS】N/A【纯度标准】95%【包装规格】5mg&#xff0c;10mg&#xff0c;25mg【是否接受定制】可…

spring springboot关于异步线程实践案例

前言&#xff1a; 关于多线程的异步处理&#xff0c;由于项目的需求有个方法需要使用异步的方法来调用&#xff0c;方法是调用外部的接口&#xff0c;执行时间会比较长导致的没有办法同步拿去到结果&#xff0c;所以需要写一个异步线程的方法进行该接口的调用&#xff0c;下面是…

Unity - UI适配方案记录

1.普通屏不同分辨率适配及预览 1.背景图 界面背景图建议至少宽于21:9&#xff1b;其中16:9的范围为精细范围&#xff0c;12:9的范围为真机最小可见范围。 2.组件 各组件通过设置RectTransform属性来达到正确适配的效果。 3.预览 在unity中&#xff0c;设置Game窗口左上角…

【C++入门第一期】命名空间 缺省参数 函数重载 的使用方法及注意事项

目录简介命名空间为何会有命名空间命名空间的定义嵌套定义命名空间的使用作用域限定符using 将命名空间中某个成员引入using namespace 将该空间所有成员引入缺省参数全缺省参数函数半缺省参数函数如何给缺省值函数重载函数重载的概念函数重载的三种类型1&#xff1a;函数参数类…

【蓝桥日记①】2017第八届省赛(软件类)JavaA组❤️答案解析

【蓝桥日记①】2017第八届省赛&#xff08;软件类&#xff09;JavaA组❤️答案解析 文章目录【蓝桥日记①】2017第八届省赛&#xff08;软件类&#xff09;JavaA组❤️答案解析A、迷宫B、9数算式C、魔方状态D、方格分割E、字母组串F、最大公共子串G、正则问题H、包子凑数I、分巧…

PID控制和误差曲线分析

PID控制模型 负反馈控制模型&#xff0c;到处都挺常见的&#xff0c;我记得高中的时候生物上就有一堆&#xff0c;什么体液调节之类的。PID控制也算是经典控制了&#xff0c;大家讲的都是经验之谈&#xff0c;这里我从误差曲线调节的角度来讲&#xff08;误差曲线调节一般都是…

Java集合 Map 集合 与 操作集合的工具类: Collections 的详细说明

Java集合 Map 集合 与 操作集合的工具类: Collections 的详细说明 每博一文案 别把人生&#xff0c;输给心情 师父说&#xff1a;心情不是人生的全部&#xff0c;却能左右人生的全部。 你有没有体会到&#xff0c;当你心情好的时候&#xff0c;生活仿佛阳光灿烂&#xff0c;顺…

第一章:ElasticSearch简介

Elaticsearch&#xff0c;简称为es&#xff0c; es是一个开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b; 本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据。 es也使用Java开发并使用Lucene作为其核…

MFC|各控件的使用

参考&#xff1a; MFC学习笔记-4-选项控件Combox的使用&#xff08;https://dandelioncloud.cn/article/details/1517727978783109122&#xff09; 文章目录控件与变量关联各控件的使用Combo Box添加设置默认选项插入 index从0开始删除获取1号索引的具体内容添加事件获取当前in…

前端学习--async

文章目录async函数await使用await等待Promise异步函数await等待普通函数什么时候使用async/awaitasync也是用于异步任务的&#xff0c;可以说是异步任务处理的另一种方式async函数 async修饰函数&#xff0c;表示这个函数中可以处理异步函数 async修饰的函数&#xff0c;会返…

每天10个前端小知识 【Day 3】

前端面试基础知识题 1. 使用js生成1-10000的数组 实现的方法很多&#xff0c;除了使用循环&#xff08;for,while,forEach等&#xff09;外&#xff0c;最简单的是使用Array.from //方法一&#xff1a; Array.from(new Array(10001).keys()).slice(1) //方法二&#xff1a;…

基于paddlex图像分类模型训练(二):训练自己的分类模型、熟悉官方demo

0. 前言 相关系列博文&#xff1a;基于paddlex图像分类模型训练&#xff08;一&#xff09;&#xff1a;图像分类数据集切分&#xff1a;文件夹转化为imagenet训练格式 代码在线运行&#xff1a; https://aistudio.baidu.com/aistudio/projectdetail/5440569 1. 官方demo&a…