Java NIO.2 (java.nio.file) 包详解
·
Java NIO.2(New I/O 2)是 Java 7 引入的全新文件系统 API,位于 java.nio.file 包中。
它提供了比传统 java.io.File 更强大、更灵活的文件操作功能。
核心组件概览
1. 主要类和接口
类/接口 作用
Paths |用于创建 Path 实例的工厂类
Path |替代 File,表示文件系统路径:接口
Files |提供丰富的静态文件操作方法
FileSystem |表示文件系统
FileSystems |文件系统的工厂类
FileStore |表示文件存储(如磁盘分区)
WatchService |监控文件系统变化
StandardOpenOption |文件打开选项枚举
StandardCopyOption |文件复制选项枚举
LinkOption |链接处理选项枚举
#详细解析
1. Path 接口 - 核心路径表示
Path 接口替代了 File 类,提供了更强大的路径操作功能。
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathExample {
public static void main(String[] args) {
// 创建 Path 对象
Path path1 = Paths.get("/home/user/documents/file.txt");
Path path2 = Paths.get("C:", "Users", "John", "file.txt");
Path path3 = Paths.get("relative/path/to/file.txt");
// 常用方法
System.out.println("文件名: " + path1.getFileName());
System.out.println("父目录: " + path1.getParent());
System.out.println("根目录: " + path1.getRoot());
System.out.println("名称元素数量: " + path1.getNameCount());
// 路径解析和组合
Path base = Paths.get("/base/directory");
Path resolved = base.resolve("subdir/file.txt");
System.out.println("解析后路径: " + resolved);
// 相对化路径
Path pathA = Paths.get("/a/b/c");
Path pathB = Paths.get("/a/b/d/e");
Path relative = pathA.relativize(pathB);
System.out.println("相对路径: " + relative); // 输出: ../d/e
// 规范化路径(处理 . 和 ..)
Path complex = Paths.get("/a/./b/../c/d");
Path normalized = complex.normalize();
System.out.println("规范化后: " + normalized); // 输出: /a/c/d
}
}
[root@host-11java_project]# javac -d . PathExample.java
[root@host-11java_project]# java PathExample
文件名: file.txt
父目录: /home/user/documents
根目录: /
名称元素数量: 4
解析后路径: /base/directory/subdir/file.txt
相对路径: ../d/e
规范化后: /a/c/d
2. Files 类 - 强大的文件操作工具
Files 类提供了丰富的静态方法,是 NIO.2 的核心。
#文件检查和属性获取
import java.nio.file.*;
import java.io.IOException;
import java.util.Set;
public class FilesBasicOperations {
public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
// 文件存在性检查
System.out.println("文件存在: " + Files.exists(path));
System.out.println("是常规文件: " + Files.isRegularFile(path));
System.out.println("是目录: " + Files.isDirectory(path));
System.out.println("可读: " + Files.isReadable(path));
System.out.println("可写: " + Files.isWritable(path));
System.out.println("可执行: " + Files.isExecutable(path));
System.out.println("是隐藏文件: " + Files.isHidden(path));
// 文件属性
System.out.println("文件大小: " + Files.size(path) + " bytes");
System.out.println("最后修改时间: " + Files.getLastModifiedTime(path));
System.out.println("所有者: " + Files.getOwner(path));
// 文件类型检测
System.out.println("MIME类型: " + Files.probeContentType(path));
// 获取文件存储信息
FileStore store = Files.getFileStore(path);
System.out.println("总空间: " + store.getTotalSpace());
System.out.println("可用空间: " + store.getUsableSpace());
}
}
#输出
[root@host-11java_project]# javac -d . FilesBasicOperations.java
[root@host-11java_project]# java FilesBasicOperations
文件存在: true
是常规文件: true
是目录: false
可读: true
可写: true
可执行: false
是隐藏文件: false
文件大小: 164 bytes
最后修改时间: 2025-09-26T08:27:39Z
所有者: root
MIME类型: text/plain
总空间: 106288885760
可用空间: 32214536192
########################################################
#文件创建、复制、移动和删除
import java.nio.file.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
public class FileOperations {
public static void main(String[] args) throws IOException {
// 创建目录(支持多级创建)
Path dir = Paths.get("my_directory/subdir");
Files.createDirectories(dir);
// 创建文件
Path file = Paths.get("example.txt");
Files.createFile(file);
// 写入文件
List<String> lines = Arrays.asList("第一行", "第二行", "第三行");
Files.write(file, lines, StandardCharsets.UTF_8);
// 追加内容
Files.write(file, Arrays.asList("追加行"),
StandardCharsets.UTF_8, StandardOpenOption.APPEND);
// 读取文件
List<String> content = Files.readAllLines(file, StandardCharsets.UTF_8);
content.forEach(System.out::println);
// 复制文件
Path copy = Paths.get("example_copy.txt");
Files.copy(file, copy, StandardCopyOption.REPLACE_EXISTING);
// 移动/重命名文件
Path moved = Paths.get("renamed_example.txt");
Files.move(file, moved, StandardCopyOption.REPLACE_EXISTING);
// 删除文件
Files.deleteIfExists(copy);
}
}
[root@host-11java_project]# java FileOperations
第一行
第二行
第三行
追加行
##########################################################################
#文件和目录遍历
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;
public class FileTraversal {
public static void main(String[] args) throws IOException {
Path startDir = Paths.get(".");
// 使用 Files.walk 递归遍历(深度优先)
System.out.println("=== 递归遍历所有文件 ===");
try (Stream<Path> stream = Files.walk(startDir)) {
stream.forEach(path -> {
try {
String type = Files.isDirectory(path) ? "目录" : "文件"; //如果是目录返回"目录",否则范围"文件"
String size = Files.isDirectory(path) ? "" : " (" + Files.size(path) + " bytes)";
System.out.println(type + ": " + path + size);
} catch (IOException e) {
e.printStackTrace();
}
});
}
// 使用 Files.list 非递归遍历(仅当前目录)
System.out.println("\n=== 当前目录内容 ===");
try (Stream<Path> stream = Files.list(startDir)) {
stream.forEach(System.out::println);
}
// 使用 Files.find 带条件查找
System.out.println("\n=== 查找 .java 文件 ===");
try (Stream<Path> stream = Files.find(startDir, Integer.MAX_VALUE,(path, attr) -> path.toString().endsWith(".java"))) {
stream.forEach(System.out::println);
}
}
}
#
[root@host-11java_project]# javac -d . FileTraversal.java
[root@host-11java_project]# java FileTraversal
=== 递归遍历所有文件 ===
目录: .
文件: ./FileTraversal.class (3849 bytes)
文件: ./PrintDir.java (242 bytes)
文件: ./PrintDir.class (600 bytes)
文件: ./FileMethodsDemo.java (2530 bytes)
文件: ./FileMethodsDemo.class (2723 bytes)
文件: ./PathExample.java (1344 bytes)
文件: ./PathExample.class (1788 bytes)
文件: ./FilesBasicOperations.java (1366 bytes)
文件: ./FilesBasicOperations.class (2416 bytes)
文件: ./FileOperations.java (1378 bytes)
文件: ./FileOperations.class (2672 bytes)
目录: ./my_directory
目录: ./my_directory/subdir
文件: ./renamed_example.txt (40 bytes)
文件: ./FileTraversal.java (1466 bytes)
=== 当前目录内容 ===
./FileTraversal.class
./PrintDir.java
./PrintDir.class
./FileMethodsDemo.java
./FileMethodsDemo.class
./PathExample.java
./PathExample.class
./FilesBasicOperations.java
./FilesBasicOperations.class
./FileOperations.java
./FileOperations.class
./my_directory
./renamed_example.txt
./FileTraversal.java
=== 查找 .java 文件 ===
./PrintDir.java
./FileMethodsDemo.java
./PathExample.java
./FilesBasicOperations.java
./FileOperations.java
./FileTraversal.java
###############################################################################
3. 文件属性和权限管理
#NIO.2 提供了更精细的文件属性控制。
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;
import java.util.Set;
public class FileAttributes {
public static void main(String[] args) throws IOException {
Path path = Paths.get("test_file.txt");
Files.createFile(path);
// 基本文件属性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("创建时间: " + attrs.creationTime());
System.out.println("最后访问时间: " + attrs.lastAccessTime());
System.out.println("最后修改时间: " + attrs.lastModifiedTime());
System.out.println("是符号链接: " + attrs.isSymbolicLink());
// 设置文件权限(POSIX系统)
if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
Files.setPosixFilePermissions(path, perms);
Set<PosixFilePermission> currentPerms = Files.getPosixFilePermissions(path);
System.out.println("当前权限: " + PosixFilePermissions.toString(currentPerms));
}
// 设置文件所有者
UserPrincipal owner = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName(System.getProperty("user.name"));
Files.setOwner(path, owner);
// 使用 FileAttribute 在创建时设置属性
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
Path newFile = Paths.get("new_file.txt");
Files.createFile(newFile, attr);
// 清理
Files.deleteIfExists(path);
Files.deleteIfExists(newFile);
}
}
###
[root@host-11 java_project]# javac -d . FileAttributes.java
[root@host-11 java_project]# java FileAttributes
创建时间: 2025-09-26T09:01:24Z
最后访问时间: 2025-09-26T09:01:24Z
最后修改时间: 2025-09-26T09:01:24Z
是符号链接: false
当前权限: rwxr-x---
4. 文件系统监控 (WatchService)
WatchService 可以监控目录的变化,非常适合需要实时响应文件变化的场景。
#java
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class DirectoryWatcher {
public static void main(String[] args) throws IOException, InterruptedException {
Path watchDir = Paths.get(".").toAbsolutePath().normalize();
System.out.println("监控目录: " + watchDir);
// 创建 WatchService
WatchService watchService = FileSystems.getDefault().newWatchService();
// 注册要监控的事件类型
watchDir.register(watchService,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
System.out.println("开始监控目录变化 (Ctrl+C 退出)...");
while (true) {
WatchKey key;
try {
// 等待事件发生,最多等待1分钟
key = watchService.poll(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
break;
}
if (key == null) {
System.out.println("监控超时,继续等待...");
continue;
}
// 处理所有事件
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 处理溢出事件(可能丢失了一些事件)
if (kind == OVERFLOW) {
System.out.println("事件可能丢失");
continue;
}
// 获取触发事件的文件名
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path fileName = ev.context();
System.out.printf("事件类型: %s, 文件: %s%n", kind.name(), fileName);
// 根据事件类型执行不同操作
if (kind == ENTRY_CREATE) {
System.out.println("新文件创建: " + fileName);
} else if (kind == ENTRY_DELETE) {
System.out.println("文件删除: " + fileName);
} else if (kind == ENTRY_MODIFY) {
System.out.println("文件修改: " + fileName);
}
}
// 重置key,继续接收事件
boolean valid = key.reset();
if (!valid) {
System.out.println("监控Key无效,退出监控");
break;
}
}
watchService.close();
}
}
#####
[root@host-11 java_project]# javac -d . DirectoryWatcher.java
[root@host-11 java_project]# java DirectoryWatcher
监控目录: /root/java_project
开始监控目录变化 (Ctrl+C 退出)...
事件类型: ENTRY_CREATE, 文件: mysqljava.txt
新文件创建: mysqljava.txt
事件类型: ENTRY_DELETE, 文件: mysqljava.txt
文件删除: mysqljava.txt
事件类型: ENTRY_CREATE, 文件: 1.txt
新文件创建: 1.txt
事件类型: ENTRY_MODIFY, 文件: 1.txt
文件修改: 1.txt
事件类型: ENTRY_CREATE, 文件: .1.txt.swp
新文件创建: .1.txt.swp
事件类型: ENTRY_CREATE, 文件: .1.txt.swx
新文件创建: .1.txt.swx
事件类型: ENTRY_DELETE, 文件: .1.txt.swx
文件删除: .1.txt.swx
事件类型: ENTRY_DELETE, 文件: .1.txt.swp
文件删除: .1.txt.swp
事件类型: ENTRY_CREATE, 文件: .1.txt.swp
新文件创建: .1.txt.swp
事件类型: ENTRY_MODIFY, 文件: .1.txt.swp
文件修改: .1.txt.swp
事件类型: ENTRY_MODIFY, 文件: .1.txt.swp
文件修改: .1.txt.swp
事件类型: ENTRY_CREATE, 文件: 4913
新文件创建: 4913
事件类型: ENTRY_MODIFY, 文件: 4913
文件修改: 4913
事件类型: ENTRY_DELETE, 文件: 4913
文件删除: 4913
事件类型: ENTRY_DELETE, 文件: 1.txt
文件删除: 1.txt
事件类型: ENTRY_CREATE, 文件: 1.txt~
新文件创建: 1.txt~
事件类型: ENTRY_CREATE, 文件: 1.txt
新文件创建: 1.txt
事件类型: ENTRY_MODIFY, 文件: 1.txt
文件修改: 1.txt
事件类型: ENTRY_MODIFY, 文件: 1.txt
文件修改: 1.txt
事件类型: ENTRY_MODIFY, 文件: .1.txt.swp
文件修改: .1.txt.swp
事件类型: ENTRY_DELETE, 文件: 1.txt~
文件删除: 1.txt~
事件类型: ENTRY_DELETE, 文件: .1.txt.swp
文件删除: .1.txt.swp
事件类型: ENTRY_MODIFY, 文件: 1.txt
文件修改: 1.txt
5. 符号链接和硬链接
#java
import java.nio.file.*;
import java.io.IOException;
public class LinkOperations {
public static void main(String[] args) throws IOException {
Path original = Paths.get("original.txt");
Files.write(original, "原始文件内容".getBytes());
// 创建符号链接(需要管理员权限或在支持的系统上)
Path symLink = Paths.get("symbolic_link.txt");
try {
Files.createSymbolicLink(symLink, original);
System.out.println("符号链接创建成功");
// 检查是否是符号链接
System.out.println("是符号链接: " + Files.isSymbolicLink(symLink));
// 读取符号链接目标
Path target = Files.readSymbolicLink(symLink);
System.out.println("符号链接指向: " + target);
} catch (UnsupportedOperationException | IOException e) {
System.out.println("符号链接创建失败: " + e.getMessage());
}
// 创建硬链接
Path hardLink = Paths.get("hard_link.txt");
try {
Files.createLink(hardLink, original);
System.out.println("硬链接创建成功");
} catch (UnsupportedOperationException | IOException e) {
System.out.println("硬链接创建失败: " + e.getMessage());
}
// 清理
Files.deleteIfExists(original);
Files.deleteIfExists(symLink);
Files.deleteIfExists(hardLink);
}
}
#NIO.2 相比传统 I/O 的优势
更好的性能:使用更高效的缓冲区管理和非阻塞 I/O
更丰富的功能:符号链接、文件属性视图、文件系统监控等
更一致的 API:统一的异常处理,更好的方法命名
更强的扩展性:支持自定义文件系统提供程序
更好的错误处理:更详细的异常信息
#最佳实践
优先使用 NIO.2:在新项目中使用 java.nio.file 而不是 java.io.File
使用 try-with-resources:确保资源正确关闭
处理异常:NIO.2 方法可能抛出多种 IOException
考虑性能:对于大文件操作,使用适当的缓冲策略
注意平台差异:某些功能(如符号链接)在不同操作系统上表现不同
NIO.2 提供了现代化、功能丰富的文件操作 API,是 Java 文件处理的推荐方式。
更多推荐
所有评论(0)