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 项目 中获取。