Introduction to the Java NIO2 File API – Java NIO2文件API简介

最后修改: 2016年 11月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’re going to focus on the new I/O APIs in the Java Platform – NIO2 – to do basic file manipulation.

在这篇文章中,我们将重点介绍Java平台中新的I/O APIs–NIO2–做基本的文件操作

File APIs in NIO2 constitute one of the major new functional areas of the Java Platform that shipped with Java 7, specifically a subset of the new file system API alongside Path APIs .

NIO2中的文件API构成了与Java 7一起发货的Java平台的主要新功能区之一,特别是新文件系统API的一个子集,与Path APIs一起。

2. Setup

2.设置

Setting up your project to use File APIs is just a matter of making this import:

设置你的项目以使用File APIs只是一个导入的问题。

import java.nio.file.*;

Since the code samples in this article will probably be running in different environments, let’s get a handle on the home directory of the user, which will be valid across all operating systems:

由于本文中的代码样本可能会在不同的环境中运行,让我们来掌握用户的主目录,这将在所有的操作系统中都有效。

private static String HOME = System.getProperty("user.home");

The Files class is one of the primary entry points of the java.nio.file package. This class offers a rich set of APIs for reading, writing, and manipulating files and directories. The Files class methods work on instances of Path objects.

Files类是java.nio.file包的主要入口之一。该类提供了一套丰富的 API,用于读取、写入和操作文件和目录。Files类的方法在Path对象的实例上工作。

3. Checking a File or Directory

3.检查一个文件或目录

We can have a Path instance representing a file or a directory on the file system. Whether that file or directory it’s pointing to exists or not, is accessible or not can be confirmed by a file operation.

我们可以有一个Path实例代表文件系统中的一个文件或一个目录。它所指向的那个文件或目录是否存在,是否可以访问,可以通过文件操作来确认。

For the sake of simplicity, whenever we use the term file, we will be referring to both files and directories unless stated explicitly otherwise.

为了简单起见,每当我们使用术语file时,我们将同时指代文件和目录,除非另有明确说明。

To check if a file exists, we use the exists API:

为了检查一个文件是否存在,我们使用exists API。

@Test
public void givenExistentPath_whenConfirmsFileExists_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.exists(p));
}

To check that a file does not exist, we use the notExists API:

为了检查一个文件是否不存在,我们使用notExists API。

@Test
public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistent_file.txt");

    assertTrue(Files.notExists(p));
}

We can also check if a file is a regular file like myfile.txt or is just a directory, we use the isRegularFile API:

我们还可以检查一个文件是否是像myfile.txt这样的普通文件,或者只是一个目录,我们使用isRegularFile API。

@Test
public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() {
    Path p = Paths.get(HOME);

    assertFalse(Files.isRegularFile(p));
}

There are also static methods to check for file permissions. To check if a file is readable, we use the isReadable API:

还有一些静态方法来检查文件的权限。为了检查一个文件是否可读,我们使用isReadable API。

@Test
public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isReadable(p));
}

To check if it is writable, we use the isWritable API:

为了检查它是否可写,我们使用isWritable API。

@Test
public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isWritable(p));
}

Similarly, to check if it is executable:

同样地,要检查它是否可执行。

@Test
public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() {
    Path p = Paths.get(HOME);
    assertTrue(Files.isExecutable(p));
}

When we have two paths, we can check if they both point to the same file on the underlying file system:

当我们有两个路径时,我们可以检查它们是否都指向底层文件系统的同一个文件。

@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
    Path p1 = Paths.get(HOME);
    Path p2 = Paths.get(HOME);

    assertTrue(Files.isSameFile(p1, p2));
}

4. Creating Files

4.创建文件

The file system API provides single line operations for creating files. To create a regular file, we use the createFile API and pass to it a Path object representing the file we want to create.

文件系统API提供了创建文件的单行操作。要创建一个普通文件,我们使用createFile API,并向其传递一个Path对象,代表我们要创建的文件。

All the name elements in the path must exist, apart from the file name, otherwise, we will get an IOException:

除了文件名之外,路径中的所有名称元素必须存在,否则,我们将得到一个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));
}

In the above test, when we first check the path, it is inexistent, then after the createFile operation, it is found to be existent.

在上面的测试中,当我们第一次检查路径时,它是不存在的,然后在createFile操作后,发现它是存在的。

To create a directory, we use the createDirectory API:

为了创建一个目录,我们使用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));
}

This operation requires that all name elements in the path exist, if not, we also get an IOException:

这个操作要求路径中的所有名称元素都存在,如果不存在,我们也会得到一个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);
}

However, if we desire to create a hierarchy of directories with a single call, we use the createDirectories method. Unlike the previous operation, when it encounters any missing name elements in the path, it does not throw an IOException, it creates them recursively leading up to the last element:

然而,如果我们希望通过一次调用来创建目录的层次结构,我们可以使用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. Creating Temporary Files

5.创建临时文件

Many applications create a trail of temporary files in the file system as they run. As a result, most file systems have a dedicated directory to store temporary files generated by such applications.

许多应用程序在运行时都会在文件系统中创建一些临时文件的痕迹。因此,大多数文件系统都有一个专门的目录来存储由这类应用程序产生的临时文件。

The new file system API provides specific operations for this purpose. The createTempFile API performs this operation. It takes a path object, a file prefix, and a file suffix:

新的文件系统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));
}

These parameters are sufficient for requirements that need this operation. However, if you need to specify specific attributes of the file, there is a fourth variable arguments parameter.

这些参数对于需要这种操作的要求是足够的。然而,如果你需要指定文件的具体属性,还有第四个变量参数。

The above test creates a temporary file in the HOME directory, pre-pending and appending the provided prefix and suffix strings respectively. We will end up with a file name like log_8821081429012075286.txt. The long numeric string is system generated.

上述测试在HOME目录下创建一个临时文件,分别预置和追加所提供的前缀和后缀字符串。我们最终会得到一个文件名,如log_8821081429012075286.txt。这个长的数字字符串是系统生成的。

However, if we don’t provide a prefix and a suffix, then the file name will only include the long numeric string and a default .tmp extension:

然而,如果我们不提供前缀和后缀,那么文件名将只包括长数字字符串和默认的.tmp扩展名。

@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
    Path p = Paths.get(HOME + "/");

    Files.createTempFile(p, null, null);
        
    assertTrue(Files.exists(p));
}

The above operation creates a file with a name like 8600179353689423985.tmp.

上述操作创建了一个文件,名称为8600179353689423985.tmp

Finally, if we provide neither path, prefix nor suffix, then the operation will use defaults throughout. The default location of the created file will be the file system provided temporary-file directory:

最后,如果我们既不提供路径、前缀也不提供后缀,那么该操作将全程使用默认值。创建文件的默认位置将是文件系统提供的临时文件目录。

@Test
public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() {
    Path p = Files.createTempFile(null, null);

    assertTrue(Files.exists(p));
}

On windows, this will default to something like C:\Users\user\AppData\Local\Temp\6100927974988978748.tmp.

在windows下,这将默认为类似于C:\Users\user\AppData\Local\Temp\61009279749889748.tmp

All the above operations can be adapted to create directories rather than regular files by using createTempDirectory instead of createTempFile.

所有上述操作都可以通过使用createTempDirectory而不是createTempFile来创建目录而不是普通文件。

6. Deleting a File

6.删除一个文件

To delete a file, we use the delete API. For clarity purpose, the following test first ensures that the file does not already exist, then creates it and confirms that it now exists and finally deletes it and confirms that it’s no longer existent:

要删除一个文件,我们使用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));
}

However, if a file is not existent in the file system, the delete operation will fail with an IOException:

然而,如果一个文件在文件系统中不存在,删除操作将以IOException失败。

@Test(expected = NoSuchFileException.class)
public void givenInexistentFile_whenDeleteFails_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistentFile.txt");
    assertFalse(Files.exists(p));

    Files.delete(p);
}

We can avoid this scenario by using deleteIfExists which fail silently in case the file does not exist. This is important when multiple threads are performing this operation and we don’t want a failure message simply because a thread performed the operation earlier than the current thread which has failed:

我们可以通过使用deleteIfExists来避免这种情况,在文件不存在的情况下,它会默默地失败。当多个线程执行该操作时,这一点非常重要,我们不希望仅仅因为某个线程比当前线程更早执行该操作而出现失败信息。

@Test
public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistentFile.txt");
    assertFalse(Files.exists(p));

    Files.deleteIfExists(p);
}

When dealing with directories and not regular files, we should remember that the delete operation does not work recursively by default. So if a directory is not empty it will fail with an IOException:

当处理目录而不是普通文件时,我们应该记住,删除操作在默认情况下不会递归工作。因此,如果一个目录不是空的,它将以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. Copying Files

7.复制文件

You can copy a file or directory by using the copy API:

你可以通过使用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));
}

The copy fails if the target file exists unless the REPLACE_EXISTING option is specified:

如果目标文件存在,则复制失败,除非指定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);
}

However, when copying directories, the contents are not copied recursively. This means that if /baeldung contains /articles.db and /authors.db files, copying /baeldung to a new location will create an empty directory.

然而,在复制目录时,内容不会被递归复制。这意味着,如果/baeldung包含/articles.db/authors.db文件,将/baeldung复制到一个新的位置将创建一个空目录。

8. Moving Files

8.移动文件

You can move a file or directory by using the move API. It is in most ways similar to the copy operation. If the copy operation is analogous to a copy and paste operation in GUI based systems, then move is analogous to a cut and paste operation:

你可以通过使用move API来移动一个文件或目录。它在大多数方面与copy操作相似。如果复制操作类似于基于GUI系统中的复制和粘贴操作,那么移动就类似于剪切和粘贴操作。

@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));
}

The move operation fails if the target file exists unless the REPLACE_EXISTING option is specified just like we did with the copy operation:

如果目标文件存在,move操作就会失败,除非像我们对copy操作那样指定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. Conclusion

9.结论

In this article, we learned about file APIs in the new file system API (NIO2) that was shipped as a part of Java 7 and saw most of the important file operations in action.

在这篇文章中,我们了解了作为Java 7一部分发货的新文件系统API(NIO2)中的文件API,并看到了大多数重要的文件操作。

The code samples used in this article can be found in the article’s Github project.

本文中使用的代码样本可以在文章的Github项目中找到。