Java NIO2 文件 API 介绍
上次更新:2024 年 1 月 8 日
1. 概述
在本文中,我们将专注于 Java 平台中的新 I/O API – NIO2 – 来进行基本的文件操作。
NIO2 中的文件 API 构成了 Java 平台的主要新功能领域之一,该平台随 Java 7 一起发布,特别是新的文件系统 API 以及 Path API 的一个子集。
2. 设置
将您的项目设置为使用文件 API 只是进行此导入的问题
import java.nio.file.*;
由于本文中的代码示例可能将在不同的环境中运行,让我们掌握用户的主目录,这将适用于所有操作系统
private static String HOME = System.getProperty("user.home");
Files 类是 java.nio.file 包的主要入口点之一。这个类提供了一组丰富的 API,用于读取、写入和操作文件和目录。Files 类的方法适用于 Path 对象。
3. 检查文件或目录
我们可以有一个 Path 实例,代表文件系统上的文件或目录。无论该文件或目录指向的是否存在,是否可访问,都可以通过文件操作来确认。
为简单起见,每当我们使用术语 file 时,我们将指代文件和目录,除非另有明确说明。
要检查文件是否存在,我们使用 exists API
@Test
public void givenExistentPath_whenConfirmsFileExists_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.exists(p));
}
要检查文件不存在,我们使用 notExists API
@Test
public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() {
Path p = Paths.get(HOME + "/inexistent_file.txt");
assertTrue(Files.notExists(p));
}
我们还可以检查文件是否为常规文件,如 myfile.txt,或者只是一个目录,我们使用 isRegularFile API
@Test
public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() {
Path p = Paths.get(HOME);
assertFalse(Files.isRegularFile(p));
}
还有静态方法来检查文件权限。要检查文件是否可读,我们使用 isReadable API
@Test
public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isReadable(p));
}
要检查它是否可写,我们使用 isWritable API
@Test
public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isWritable(p));
}
同样,要检查它是否可执行
@Test
public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isExecutable(p));
}
当有两个路径时,我们可以检查它们是否都指向底层文件系统上的相同文件
@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
Path p1 = Paths.get(HOME);
Path p2 = Paths.get(HOME);
assertTrue(Files.isSameFile(p1, p2));
}
4. 创建文件
文件系统 API 提供单行操作来创建文件。要创建常规文件,我们使用 createFile API 并将一个 Path 对象传递给它,该对象代表我们要创建的文件。
路径中的所有名称元素都必须存在,除了文件名之外,否则我们将获得一个 IOException:
@Test
public void givenFilePath_whenCreatesNewFile_thenCorrect() {
String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt";
Path p = Paths.get(HOME + "/" + fileName);
assertFalse(Files.exists(p));
Files.createFile(p);
assertTrue(Files.exists(p));
}
在上述测试中,当我们第一次检查路径时,它不存在,然后在 createFile 操作之后,发现它存在。
要创建目录,我们使用 createDirectory API
@Test
public void givenDirPath_whenCreatesNewDir_thenCorrect() {
String dirName = "myDir_" + UUID.randomUUID().toString();
Path p = Paths.get(HOME + "/" + dirName);
assertFalse(Files.exists(p));
Files.createDirectory(p);
assertTrue(Files.exists(p));
assertFalse(Files.isRegularFile(p));
assertTrue(Files.isDirectory(p));
}
此操作要求路径中的所有名称元素都存在,否则,我们也会收到一个 IOException
@Test(expected = NoSuchFileException.class)
public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() {
String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir";
Path p = Paths.get(HOME + "/" + dirName);
assertFalse(Files.exists(p));
Files.createDirectory(p);
}
但是,如果我们希望使用单个调用创建目录层次结构,我们使用 createDirectories 方法。与之前的操作不同,当它遇到路径中任何缺失的名称元素时,它不会抛出 IOException,它会递归地创建它们直到最后一个元素
@Test
public void givenDirPath_whenCreatesRecursively_thenCorrect() {
Path dir = Paths.get(
HOME + "/myDir_" + UUID.randomUUID().toString());
Path subdir = dir.resolve("subdir");
assertFalse(Files.exists(dir));
assertFalse(Files.exists(subdir));
Files.createDirectories(subdir);
assertTrue(Files.exists(dir));
assertTrue(Files.exists(subdir));
}
5. 创建临时文件
许多应用程序在运行时在文件系统中创建一堆临时文件。因此,大多数文件系统都有一个专门的目录来存储此类应用程序生成的临时文件。
新的文件系统 API 提供了针对此目的的特定操作。createTempFile API 执行此操作。它接收一个路径对象、一个文件前缀和一个文件后缀
@Test
public void givenFilePath_whenCreatesTempFile_thenCorrect() {
String prefix = "log_";
String suffix = ".txt";
Path p = Paths.get(HOME + "/");
Files.createTempFile(p, prefix, suffix);
assertTrue(Files.exists(p));
}
这些参数足以满足需要此操作的要求。但是,如果您需要指定文件的特定属性,则有一个第四个可变参数。
上述测试在 HOME 目录中创建了一个临时文件,分别预先附加和追加提供的字符串前缀和后缀。最终我们将得到一个文件名,如 log_8821081429012075286.txt。长的数字字符串是系统生成的。
但是,如果我们不提供前缀和后缀,那么文件名将仅包含长数字字符串和一个默认的.tmp扩展名
@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
Path p = Paths.get(HOME + "/");
Files.createTempFile(p, null, null);
assertTrue(Files.exists(p));
}
上述操作会创建一个类似于8600179353689423985.tmp的文件。
最后,如果既不提供路径、前缀也不提供后缀,那么该操作将使用所有默认值。创建文件的默认位置是文件系统提供的临时文件目录
@Test
public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() {
Path p = Files.createTempFile(null, null);
assertTrue(Files.exists(p));
}
在 Windows 上,这将会默认设置为类似C:\Users\user\AppData\Local\Temp\6100927974988978748.tmp的路径。
以上所有操作都可以通过使用createTempDirectory而不是createTempFile来适应创建目录而不是常规文件。
6. 删除文件
要删除文件,我们使用delete API。为了清晰起见,以下测试首先确保文件不存在,然后创建它并确认它现在存在,最后删除它并确认它不再存在
@Test
public void givenPath_whenDeletes_thenCorrect() {
Path p = Paths.get(HOME + "/fileToDelete.txt");
assertFalse(Files.exists(p));
Files.createFile(p);
assertTrue(Files.exists(p));
Files.delete(p);
assertFalse(Files.exists(p));
}
但是,如果文件在文件系统中不存在,则删除操作将使用IOException失败
@Test(expected = NoSuchFileException.class)
public void givenInexistentFile_whenDeleteFails_thenCorrect() {
Path p = Paths.get(HOME + "/inexistentFile.txt");
assertFalse(Files.exists(p));
Files.delete(p);
}
我们可以通过使用deleteIfExists来避免这种情况,该方法在文件不存在时会静默失败。当多个线程执行此操作并且我们不希望因为一个线程早于当前线程执行操作而导致失败消息时,这一点很重要
@Test
public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() {
Path p = Paths.get(HOME + "/inexistentFile.txt");
assertFalse(Files.exists(p));
Files.deleteIfExists(p);
}
在处理目录而不是常规文件时,我们应该记住删除操作默认情况下不递归工作。因此,如果目录不为空,它将使用IOException失败
@Test(expected = DirectoryNotEmptyException.class)
public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() {
Path dir = Paths.get(
HOME + "/emptyDir" + UUID.randomUUID().toString());
Files.createDirectory(dir);
assertTrue(Files.exists(dir));
Path file = dir.resolve("file.txt");
Files.createFile(file);
Files.delete(dir);
assertTrue(Files.exists(dir));
}
7. 复制文件
您可以使用copy API来复制文件或目录
@Test
public void givenFilePath_whenCopiesToNewLocation_thenCorrect() {
Path dir1 = Paths.get(
HOME + "/firstdir_" + UUID.randomUUID().toString());
Path dir2 = Paths.get(
HOME + "/otherdir_" + UUID.randomUUID().toString());
Files.createDirectory(dir1);
Files.createDirectory(dir2);
Path file1 = dir1.resolve("filetocopy.txt");
Path file2 = dir2.resolve("filetocopy.txt");
Files.createFile(file1);
assertTrue(Files.exists(file1));
assertFalse(Files.exists(file2));
Files.copy(file1, file2);
assertTrue(Files.exists(file2));
}
如果目标文件存在,复制将失败,除非指定了REPLACE_EXISTING选项
@Test(expected = FileAlreadyExistsException.class)
public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() {
Path dir1 = Paths.get(
HOME + "/firstdir_" + UUID.randomUUID().toString());
Path dir2 = Paths.get(
HOME + "/otherdir_" + UUID.randomUUID().toString());
Files.createDirectory(dir1);
Files.createDirectory(dir2);
Path file1 = dir1.resolve("filetocopy.txt");
Path file2 = dir2.resolve("filetocopy.txt");
Files.createFile(file1);
Files.createFile(file2);
assertTrue(Files.exists(file1));
assertTrue(Files.exists(file2));
Files.copy(file1, file2);
Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING);
}
但是,当复制目录时,内容不会递归复制。这意味着如果/baeldung包含/articles.db和/authors.db文件,则将/baeldung复制到新位置将创建一个空目录。
8. 移动文件
您可以使用move API来移动文件或目录。它在许多方面类似于copy操作。如果复制操作类似于 GUI 系统中的复制和粘贴操作,那么move类似于剪切和粘贴操作
@Test
public void givenFilePath_whenMovesToNewLocation_thenCorrect() {
Path dir1 = Paths.get(
HOME + "/firstdir_" + UUID.randomUUID().toString());
Path dir2 = Paths.get(
HOME + "/otherdir_" + UUID.randomUUID().toString());
Files.createDirectory(dir1);
Files.createDirectory(dir2);
Path file1 = dir1.resolve("filetocopy.txt");
Path file2 = dir2.resolve("filetocopy.txt");
Files.createFile(file1);
assertTrue(Files.exists(file1));
assertFalse(Files.exists(file2));
Files.move(file1, file2);
assertTrue(Files.exists(file2));
assertFalse(Files.exists(file1));
}
与copy操作一样,如果目标文件存在,则move操作将失败,除非指定了REPLACE_EXISTING选项
@Test(expected = FileAlreadyExistsException.class)
public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() {
Path dir1 = Paths.get(
HOME + "/firstdir_" + UUID.randomUUID().toString());
Path dir2 = Paths.get(
HOME + "/otherdir_" + UUID.randomUUID().toString());
Files.createDirectory(dir1);
Files.createDirectory(dir2);
Path file1 = dir1.resolve("filetocopy.txt");
Path file2 = dir2.resolve("filetocopy.txt");
Files.createFile(file1);
Files.createFile(file2);
assertTrue(Files.exists(file1));
assertTrue(Files.exists(file2));
Files.move(file1, file2);
Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
assertTrue(Files.exists(file2));
assertFalse(Files.exists(file1));
}
9. 结论
在本文中,我们了解了 Java 7 随附的新文件系统 API (NIO2) 中的文件 API,并了解了大多数重要的文件操作。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















