1. 概述

本文将聚焦 Java 平台中的新 I/O API —— NIO2,用于实现基础文件操作。NIO2 中的文件 API 是 Java 7 引入的重大功能之一,属于新文件系统 API 的子集,与 Path API 协同工作。

2. 环境准备

使用文件 API 只需简单导入以下包:

import java.nio.file.*;

考虑到代码示例可能在不同环境运行,我们先获取用户主目录路径(跨操作系统通用):

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

Files 类是 java.nio.file 包的核心入口点,提供了丰富的文件/目录读写和操作 API。其所有方法都作用于 Path 对象实例。

3. 检查文件或目录

Path 实例可表示文件系统中的文件或目录。通过文件操作可验证其是否存在、是否可访问等。为简化表述,下文中的"文件"均指代文件和目录(除非特别说明)。

3.1 检查存在性

✅ 使用 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));
}

3.2 检查文件类型

使用 isRegularFile 区分普通文件(如 myfile.txt)和目录:

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

3.3 检查文件权限

  • ✅ 可读性检查:isReadable
  • ✅ 可写性检查:isWritable
  • ✅ 可执行性检查:isExecutable
@Test
public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() {
    Path p = Paths.get(HOME);
    assertTrue(Files.isReadable(p));
}

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

3.4 检查路径一致性

验证两个路径是否指向同一文件:

@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
    Path p1 = Paths.get(HOME);
    Path p2 = Paths.get(HOME);
    assertTrue(Files.isSameFile(p1, p2));
}

4. 创建文件

文件系统 API 提供了单行操作创建文件的方法。

4.1 创建普通文件

使用 createFile 创建文件,路径中除文件名外的所有元素必须存在,否则抛出 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));
}

4.2 创建目录

使用 createDirectory 创建目录,同样要求父路径存在:

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

⚠️ 踩坑:父路径不存在时直接抛出异常:

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

4.3 递归创建目录

使用 createDirectories 可自动创建缺失的父目录:

@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. 创建临时文件

许多应用运行时会产生临时文件,文件系统通常有专用目录存储这类文件。NIO2 提供了专用 API 处理此场景。

5.1 基本临时文件创建

使用 createTempFile,需指定路径、前缀和后缀:

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

5.2 使用默认参数

  • 省略前缀/后缀:仅生成随机数字 + .tmp 扩展名
  • 省略路径:使用系统临时目录
@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
    Path p = Paths.get(HOME + "/");
    Files.createTempFile(p, null, null);
    assertTrue(Files.exists(p));
}

@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

5.3 创建临时目录

createTempFile 替换为 createTempDirectory 即可创建临时目录。

6. 删除文件

6.1 基本删除操作

使用 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);
}

6.2 安全删除

使用 deleteIfExists 避免文件不存在时的异常(多线程场景尤其有用):

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

6.3 删除目录

⚠️ 踩坑:默认不支持递归删除,非空目录会抛出 DirectoryNotEmptyException

@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); // 此处抛出异常
}

@Test
public void givenPath_whenCopySucceedsWithReplace_thenCorrect() {
    // ... 同上初始化代码 ...
    Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING); // 覆盖现有文件
}

简单粗暴:复制目录时不会递归复制内容,仅创建空目录。

8. 移动文件

使用 move API 移动文件或目录,类似于 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));
}

⚠️ 踩坑:目标文件已存在时需指定 REPLACE_EXISTING 选项(与复制操作一致):

@Test(expected = FileAlreadyExistsException.class)
public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() {
    // ... 初始化代码同复制操作 ...
    Files.move(file1, file2); // 抛出异常
}

@Test
public void givenFilePath_whenMoveSucceedsWithReplace_thenCorrect() {
    // ... 初始化代码同复制操作 ...
    Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); // 覆盖移动
}

9. 总结

本文深入探讨了 Java 7 引入的 NIO2 文件系统 API 中的核心文件操作,包括:

  • ✅ 文件/目录检查(存在性、类型、权限)
  • ✅ 文件/目录创建(普通文件、目录、临时文件)
  • ✅ 文件删除(含安全删除)
  • ✅ 文件复制与移动(含覆盖选项)

这些 API 简化了文件操作,提供了更健壮的异常处理机制。完整代码示例可在 GitHub 项目 中获取。


原始标题:Introduction to Java NIO2 File API