浅谈Java中的文件处理

浅谈Java中的文件处理

前言

本篇文章面向小白,各位大佬无需浪费时间

今天来总结一下Java中的文件处理api,主要以java.nio.file包中的api为主(事实上,java.io.file中的相关api在过去更多的被使用,但nio包中的api技术更新,因此主要展示nio包中的api)

Path api

path api 所对应的是文件的地址(可以是绝对的,也可以是相对的),从性质上来讲,它是一个引用。下面是一些简单的实例:

1
2
3
4
Path path1 =Path.of("E:/some/s.txt");//注意,你无需区分/与\\,无论哪一种都可以被正常识别
System.out.println(path1);
Path path2 =Path.of("E:/some/").resolve("some.txt");
System.out.println(path2);

(如果你使用的是Java11或更以前的版本,应当使用Path.get而不是of)

我们可以先看看运行结果:

1
2
E:\some\s.txt
E:\some\some.txt

这里其实很难给出一个恰当的对resolve的翻译,你可以简单的理解为在原来的Path上继续增加一段字符串,到后面会有进一步的解释

另外,path.of还支持另一种使用方法

像这样Path path3 =Path.of("E:","/some","/some.txt");但我个人对此并不推荐,显然它会使输入更加的麻烦

应当注意的是path只是一个引用,他不能实际的确定文件是否存在,应当对此单独做出检查

1
System.out.println(Files.exists(path1));//注意,是Files而不是File

输出

1
false

常见的文件操作

接下来我们将了解一些与文件的增删改查相关的操作

首先是创建目录与文件,这里我们需要使用Files包中相关的方法

1
2
3
4
Path nweDir=Files.createDirectories(Path.of("E:/some"));//创建目录
System.out.println(nweDir);
Path newFile=Files.createFile(nweDir.resolve("newFile.txt"));//创建文件
System.out.println(newFile);

在这里其实就可以看的出resolve的作用是什么

1
2
E:\some
E:\some\newFile.txt

此时目录与文件就被顺利的创建了

另外,其实还存在着创建临时目录与文件的Files.createTempDirectory() Files.createTempFile()方法,但同样的,我并不推荐,应为它们虽然被称为临时文件,但并不会在某个时间点自动被删除,与其如此,不如直接使用一般的文件创建方式

接下来我们看一看文件的移动

就像这样

1
2
Files.move(Path.of("E:/some/newFile.txt"), Path.of("E:/some/s/newFile.txt"));//这一句的语法是正确的
Files.move(Path.of("E:/some/newFile.txt"), Path.of("E:/some/s"));//这是错误的

在移动对应的文件时应当指明其所对应的绝对路径,而不是指明将其移向哪个目录下

当然,如果文件已存在,该操作不会被执行,而是会抛出对应的异常,也可以这样

1
Files.move(Path.of("E:/some/newFile.txt"), Path.of("E:/some/s/newFile.txt"),StandardCopyOption.REPLACE_EXISTING);

此时文件将会被强制覆盖

此外,我们应当了解如何获取当前目录下的文件列表

这分为两种情况,

1.仅获取当前目录下的文件,不对子目录的文件做要求

就像这样

1
2
Path dir=Path.of("E:/some");
Files.list(dir).forEach(System.out::println);

值得一提的是list方法返回的是一个流,你可以对它进行一切流可以进行的操作,下面是输出结果

1
2
E:\some\newFile.txt
E:\some\s//这是一个目录

2.获取内容包括子目录的文件

1
2
Path dir=Path.of("E:/some");
Files.walk(dir).forEach(System.out::println);

walk方法会遍历所有的子目录,这是输出结果

1
2
3
4
E:\some
E:\some\newFile.txt
E:\some\s
E:\some\s\newFile2.txt

但我们发现.有时候我们并不需要目录名,而只需要获取文件名,这时可以这样

1
Files.newDirectoryStream(dir,"*.txt").forEach(System.out::println);

这样我们就可以只获得对应目录下的TXT文件

然后是目录与文件的删除,这点略有麻烦,java并没有提供删除一个非空目录的方法,而是仅提供了删除空目录的Files.delete方法,类似于这样

1
2
3
4
5
6
Path dir=Path.of("E:/some/s");
try{
Files.delete(dir);
}catch (DirectoryNotEmptyException e){
System.out.println("Directory does not exist");
}

好吧我收回之前的话,再查了一通资料后我发现java.io.file包中就有直接的删除目录的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static boolean deleteDirectory(File directory) {
if (!directory.exists()) {
System.out.println("目录不存在: " + directory.getAbsolutePath());
return false;
}

// 如果是文件,直接删除
if (directory.isFile()) {
return directory.delete();
}

// 如果是目录,递归删除子文件和子目录
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
deleteDirectory(file);
}
}

// 删除目录本身
return directory.delete();
}

public static void main(String[] args) {
String dirPath = "path/to/directory"; // 替换为你的目录路径
File directory = new File(dirPath);

boolean result = deleteDirectory(directory);
if (result) {
System.out.println("目录删除成功: " + dirPath);
} else {
System.out.println("目录删除失败: " + dirPath);
}
}

这是我从网上找到的一段较为标准的删除目录树的方法,只使用了java.io.file包中的方法,相较于调用java.nio.file.path中的方法显得可读性更强,也可一看看使用path包的删除流程

1
2
3
4
5
6
7
8
9
10
try (Stream<Path> walk = Files.walk(tmpDir)) {
walk.sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
// something could not be deleted..
e.printStackTrace();
}
});
}

显然,还是java.io.file中的方法可读性强

在这一部分的最后,我们可以了解一下关于绝对路径与相对路径的关系,我个人推荐在使用过程中尽可能使用绝对路径如E:/some/s而不是

./s(这代指当前目录下的s子目录)这样的相对路径,这总会带来奇奇怪怪的bug.所以涉及到的路径的转换方法略去不谈,有需要可以自行寻找

文件的读与写

文件的读写是程序很重要的一部分,但幸运的是,得益于前人的完善,这部分的工作相对比较简单

首先是读取文件

1
2
3
4
Path path=Path.of("E:/some/newFile.txt");
String s= Files.readString(path);以字符串的形式读取
String s2=Files.readString(path, StandardCharsets.UTF_8);//以字符串的形式读取,并声明格式为UTF-8
String s3= Arrays.toString(Files.readAllBytes(path));//以字节的形式读取,声明格式为UTF-8

默认将以utf-8的格式读取,你也可以通过对应的参数指定,就像s2与s3所展示的那样

当然,也可以选择使用流的方式进行读取,就像这样

1
2
BufferedReader bufferedReader = Files.newBufferedReader(utfFile)
InputStream is = Files.newInputStream(utfFile)

接下来是文件的写入,同样的非常简单

1
2
Files.write(path,"this is a String".getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
Files.writeString(path, "this is my string ää öö üü", StandardCharsets.ISO_8859_1);

写和上面的读一一对应,默认的格式为UTF-8,但你也可以自己指定

不过需要强调的是,

  1. 如果对应的path不存在,文件将会被自行创建

  2. 在默认模式下,原文件的内容将会被覆盖,可以添加参数StandardOpenOption.APPEND使添加的内容会在原内容尾被添加,下面是一些可用的(但不是全部)StandardOpenOption,可以了解一下

    APPEND 将数据写入文件末尾(追加模式)。如果文件不存在,会抛出异常,除非同时指定了 CREATE
    CREATE 如果文件不存在,则创建文件。如果文件已存在,则不会影响文件的内容。
    CREATE_NEW 如果文件不存在,则创建新文件。如果文件已存在,则抛出 FileAlreadyExistsException
    DELETE_ON_CLOSE 在关闭文件时删除文件。这通常用于临时文件操作。

同时,流的使用以没有任何问题

1
2
BufferedWriter bufferedWriter = Files.newBufferedWriter(utfFile)
OutputStream os = Files.newOutputStream(utfFile)

最后,推荐在有能力的情况下亲自取阅读官方给出的文档,并了解:

  1. 内存文件系统
  2. 文件变化监测

写在最后

终于写完了!!!

当然,虽然花费了不少的心血,但受限于个人的学识与能力,难免会有各种疏漏之处,还望见谅,如有疑问或发现错误,欢迎通过邮箱联系

老样子,放张图镇楼


浅谈Java中的文件处理
http://soulmate.org.cn/2024/11/17/浅谈Java中的文件处理/
作者
Soul
发布于
2024年11月17日
许可协议