Path 类
概述
在 Java NIO(java.nio.file
)中,Path
类是 Java 7 引入的新文件路径处理 API,属于 java.nio.file
包的一部分。相比于旧的 java.io.File
,Path
具有更强大的功能,包括:
- 支持跨平台路径处理(自动适配 Windows 和 Unix/Linux)
- 支持相对路径与绝对路径
- 提供丰富的路径操作方法
- 支持符号链接(Symbolic Links)
- 与
Files
类搭配使用,实现强大的文件操作
Path
不是文件或目录本身,而是文件路径的抽象表示,它可以表示一个文件或目录的路径,既可以是相对路径,也可以是绝对路径。
构造方法
Path 对象通过 java.nio.file.Paths 类的静态方法构造。
Path get(String first, String... more)
:通过字符串创建 Path 对象。
Path get(URI uri)
:通过 URI 对象创建 Path 对象。
常用方法
FileSystem getFileSystem()
:返回创建此对象的文件系统。
boolean isAbsolute()
:判断是否为绝对路径。
Path getRoot()
:获取根路径。
Path getFileName()
:获取文件名。
Path getParent()
:获取父路径。
int getNameCount()
:获取路径层级数量。
Path getName(int index)
:获取层级(目录或文件)的名称。
Path subpath(int beginIndex, int endIndex)
:返回一个相对路径,该路径是此路径的名称元素的子序列。
Path toAbsolutePath()
:将路径转换为绝对路径。
boolean startsWith(Path other)
:判断此路径是否从给定路径开始。
boolean startsWith(String other)
:判断此路径是否从给定路径开始。
boolean endsWith(Path other)
:判断此路径是否从给定路径结束。
boolean endsWith(String other)
:判断此路径是否从给定路径结束。
Path resolve(Path other)
:拼接两个 Path。
Path relativize(Path other)
:构造此路径和给定路径之间的相对路径。
URI toUri()
:返回此路径的 URI。
File toFile()
:返回此路径标识的 File 对象。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class PathDemo { public static void main(String[] args) throws IOException, URISyntaxException { Path path1 = Paths.get("nio-demo.txt"); Path path2 = Paths.get(new URI("file:///nio-demo.txt"));
System.out.println("文件系统:" + path1.getFileSystem());
path1 = path1.toAbsolutePath(); System.out.println("转换为绝对路径:" + path1);
System.out.println("是否为绝对路径:" + path1.isAbsolute());
System.out.println("根路径:" + path1.getRoot());
System.out.println("文件路径层级数量:" + path1.getNameCount()); for (int i = 0; i < path1.getNameCount(); i++) { System.out.println("层级 " + i + ":" + path1.getName(i)); }
System.out.println("子路径:" + path1.subpath(1, 2));
System.out.println("是否以指定路径开始:" + path1.startsWith(Paths.get("C:/"))); System.out.println("是否以指定路径结束:" + path1.endsWith("nio-demo.txt"));
System.out.println("拼接路径:" + path1.resolve("nio-demo2.txt"));
System.out.println("与 C:/ 的相对路径:" + path1.relativize(Paths.get("C:/")));
System.out.println("路径 URI:" + path1.toUri());
System.out.println("File 对象:" + path1.toFile()); } }
|
Files 类
概述
java.nio.file.Files
是 Java 7 引入的文件和目录操作工具类,提供了大量静态方法来简化文件创建、删除、复制、移动、读取、写入、属性管理等 操作。相比 java.io.File
,Files
类功能更丰富,并且可以与 Path
类无缝结合,适用于现代 Java 项目。
常用方法
Path createFile(Path path, FileAttribute<?>... attrs)
:创建文件。
Path createDirectory(Path dir, FileAttribute<?>... attrs)
:创建单层目录。
Path createDirectories(Path dir, FileAttribute<?>... attrs)
:创建多层目录。
Path createTempFile(String prefix, String suffix, FileAttribute<?>... attrs)
:创建临时文件。
Path createTempDirectory(String prefix, FileAttribute<?>... attrs)
:创建临时目录。
void delete(Path path)
:删除文件。
boolean deleteIfExists(Path path)
:安全地删除。
Path copy(Path source, Path target, CopyOption... options)
:将文件复制到目标文件。
Path move(Path source, Path target, CopyOption... options)
:将文件移动或重命名为目标文件。
Path write(Path path, byte[] bytes, OpenOption... options)
:将字节写入文件(通过参数控制追加、新建等)。
BufferedWriter newBufferedWriter(Path path, OpenOption... options)
:打开或创建用于写入的文件,返回BufferedWriter以高效的方式将文本写入文件。
List<String> readAllLines(Path path)
:读取文件中的所有行(UTF-8 字符集)。
BufferedReader newBufferedReader(Path path)
:打开文件进行读取,返回BufferedReader以高效的方式从文件中读取文本。
boolean exists(Path path, LinkOption... options)
:判断文件是否存在。
boolean notExists(Path path, LinkOption... options)
:判断文件是否不存在。
boolean isRegularFile(Path path, LinkOption... options)
:判断是否为文件。
boolean isDirectory(Path path, LinkOption... options)
:判断是否为目录。
boolean isHidden(Path path)
:判断文件是否被隐藏。
boolean isReadable(Path path)
:判断文件是否可读。
Stream<Path> list(Path dir)
:遍历目录下的文件。
Stream<Path> walk(Path start, FileVisitOption... options)
:递归遍历目录下的文件。
InputStream newInputStream(Path path, OpenOption... options)
:创建文件的输入流。
OutputStream newOutputStream(Path path, OpenOption... options)
:文件文件的输出流。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| @Slf4j public class FilesDemo { public static void main(String[] args) throws IOException { Path path = Paths.get("files-nio-demo.txt"); Files.createFile(path);
Files.createDirectory(Paths.get("files-nio-demo-dir"));
Files.createDirectories(Paths.get("files-nio-demo-dir1/dir2/dir3"));
Path tempFile = Files.createTempFile("temp-files", ".txt");
Path tempDir = Files.createTempDirectory("temp-dir");
Files.delete(path);
Files.deleteIfExists(Paths.get("files-nio-demo-dir"));
Files.copy(Paths.get("source.txt"), Paths.get("files-copy.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.move(Paths.get("source.txt"), Paths.get("files-move.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.write(path, "Hello, Files!".getBytes());
Files.write(path, "Hello, Files!".getBytes(), StandardOpenOption.APPEND);
try (BufferedWriter writer = Files.newBufferedWriter(path)) { writer.write("Hello, Files!"); } catch (IOException e) { log.error("写入文件失败", e); }
List<String> strings = Files.readAllLines(Paths.get("files-nio-demo.txt"));
List<String> lines = new ArrayList<>(); try (BufferedReader reader = Files.newBufferedReader(Paths.get("files-nio-demo.txt"))) { String line; while ((line = reader.readLine()) != null) { lines.add(line); } } catch (IOException e) { log.error("读取文件失败", e); }
System.out.println(Files.exists(path));
System.out.println(Files.notExists(path));
System.out.println(Files.isRegularFile(path));
System.out.println(Files.isDirectory(Paths.get("files-nio-demo-dir1")));
System.out.println(Files.isHidden(path));
System.out.println(Files.isReadable(path));
Files.list(Paths.get("files-nio-demo-dir1")).forEach(System.out::println);
Files.walk(Paths.get("files-nio-demo-dir1")).forEach(System.out::println);
Files.walk(Paths.get("files-nio-demo-dir1"), FileVisitOption.FOLLOW_LINKS).forEach(System.out::println);
try (InputStream inputStream = Files.newInputStream(path)) { byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); System.out.println(new String(bytes)); } catch (IOException e) { log.error("读取文件失败", e); }
try (OutputStream outputStream = Files.newOutputStream(path)) { outputStream.write("Hello, Files!".getBytes()); } catch (IOException e) { log.error("写入文件失败", e); }
} }
|
FileVisitor
介绍
java.nio.file.FileVisitor<T>
是 Java NIO 提供的文件遍历接口,用于递归遍历目录及其子目录,适用于文件搜索、批量删除、复制、统计等操作。
优势
对比 Files.walk()
方式 |
优点 |
缺点 |
Files.walk() |
代码简洁,适用于简单遍历 |
控制力较弱,无法细粒度定制操作 |
FileVisitor |
可精准控制遍历过程,如提前终止 |
代码较 walk() 复杂 |
四个回调方法
FileVisitor<T>
通过回调机制处理文件遍历,每当访问一个文件或目录时,会触发以下方法:
方法 |
触发时机 |
用途 |
preVisitDirectory(T dir, BasicFileAttributes attrs) |
进入目录前 |
可用于记录访问路径、统计目录数量等 |
visitFile(T file, BasicFileAttributes attrs) |
访问文件时 |
适用于文件分析、统计大小、复制等 |
visitFileFailed(T file, IOException exc) |
访问文件失败 |
处理无权限、文件损坏等异常 |
postVisitDirectory(T dir, IOException exc) |
退出目录后 |
可用于删除空目录等 |
方法返回值:
FileVisitResult.CONTINUE
:继续遍历
FileVisitResult.SKIP_SUBTREE
:跳过当前目录的所有子文件
FileVisitResult.SKIP_SIBLINGS
:跳过当前目录下的其他文件和子目录
FileVisitResult.TERMINATE
:立即终止遍历
SimpleFileVisitor
SimpleFileVisitor<T>
简化了 FileVisitor
,是 FileVisitor<T>
的 默认实现,已提供基本方法,只需重写需要的方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class SimpleFileVisitorDemo extends SimpleFileVisitor<Path> { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { System.out.println("访问文件: " + file); return FileVisitResult.CONTINUE; }
@Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { System.out.println("进入目录: " + dir); return FileVisitResult.CONTINUE; }
@Override public FileVisitResult visitFileFailed(Path file, IOException exc) { System.out.println("无法访问文件: " + file); return FileVisitResult.CONTINUE; }
@Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) { System.out.println("退出目录: " + dir); return FileVisitResult.CONTINUE; }
public static void main(String[] args) throws IOException { Path startPath = Paths.get("testDir"); Files.walkFileTree(startPath, new SimpleFileVisitorDemo()); } }
|
运行程序,控制台打印如下:
| makefile复制编辑进入目录: testDir 访问文件: testDir/file1.txt 访问文件: testDir/file2.txt 进入目录: testDir/subDir 访问文件: testDir/subDir/file3.txt 退出目录: testDir/subDir 退出目录: testDir
|
场景示例
递归删除目录(支持非空目录)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class DeleteDirectoryVisitor extends SimpleFileVisitor<Path> { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); System.out.println("删除文件: " + file); return FileVisitResult.CONTINUE; }
@Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); System.out.println("删除目录: " + dir); return FileVisitResult.CONTINUE; }
public static void main(String[] args) throws IOException { Path dirToDelete = Paths.get("testDir"); Files.walkFileTree(dirToDelete, new DeleteDirectoryVisitor()); } }
|
作用:删除非空目录,Files.delete()
直接删除空目录,但 FileVisitor
可递归删除所有文件和子目录。
统计目录中的文件大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class FileSizeCalculator extends SimpleFileVisitor<Path> { private long totalSize = 0;
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { totalSize += attrs.size(); return FileVisitResult.CONTINUE; }
public long getTotalSize() { return totalSize; }
public static void main(String[] args) throws IOException { Path path = Paths.get("testDir"); FileSizeCalculator calculator = new FileSizeCalculator(); Files.walkFileTree(path, calculator); System.out.println("目录总大小: " + calculator.getTotalSize() + " 字节"); } }
|
作用:递归计算目录的总大小,包括所有子目录的文件大小。
复制目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class CopyDirectoryVisitor extends SimpleFileVisitor<Path> { private Path sourceDir; private Path targetDir;
public CopyDirectoryVisitor(Path sourceDir, Path targetDir) { this.sourceDir = sourceDir; this.targetDir = targetDir; }
@Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path newDir = targetDir.resolve(sourceDir.relativize(dir)); Files.createDirectories(newDir); return FileVisitResult.CONTINUE; }
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path newFile = targetDir.resolve(sourceDir.relativize(file)); Files.copy(file, newFile, StandardCopyOption.REPLACE_EXISTING); return FileVisitResult.CONTINUE; }
public static void main(String[] args) throws IOException { Path source = Paths.get("sourceDir"); Path target = Paths.get("backupDir"); Files.walkFileTree(source, new CopyDirectoryVisitor(source, target)); System.out.println("目录复制完成"); } }
|
作用:递归复制目录,Files.copy()
只能复制单个文件,而 FileVisitor
可遍历所有子目录进行复制。
WatchService
概述
文件监听服务 WatchService
是 Java NIO 提供的文件监听服务,用于监视目录中的文件变化,如:
- 文件创建(
ENTRY_CREATE
)
- 文件修改(
ENTRY_MODIFY
)
- 文件删除(
ENTRY_DELETE
)
适用场景
- 实时文件监控
- 日志文件自动更新
- 自动重载配置
- 检测文件上传(FTP/云存储)
- 开发工具(如 IDE)监听文件变更
工作流程
- 创建
WatchService
- 注册目录到
WatchService
- 监听事件
- 处理触发的文件事件
核心 API
方法 |
作用 |
FileSystem.newWatchService() |
创建 WatchService |
Path.register(WatchService, WatchEvent.Kind<?>...) |
将目录注册到 WatchService |
watchService.poll() |
非阻塞地获取事件 |
watchService.take() |
阻塞等待事件 |
watchKey.pollEvents() |
获取事件列表 |
watchKey.reset() |
重新注册监听事件 |
监听目录变化示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class WatchServiceExample { public static void main(String[] args) throws IOException, InterruptedException { WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("watchedDir"); if (!Files.exists(dir)) { Files.createDirectory(dir); }
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("正在监听目录: " + dir);
while (true) { WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); Path filePath = dir.resolve((Path) event.context());
System.out.println("事件类型: " + kind + ",文件: " + filePath); }
boolean valid = key.reset(); if (!valid) { break; } }
watchService.close(); } }
|
运行程序,当 watchedDir
目录下有文件变化时,会输出:
| 正在监听目录: watchedDir 事件类型: ENTRY_CREATE,文件: watchedDir/newFile.txt 事件类型: ENTRY_MODIFY,文件: watchedDir/newFile.txt 事件类型: ENTRY_DELETE,文件: watchedDir/newFile.txt
|
拓展:
监听事件类型
WatchService
可监听三种文件事件:
事件类型 |
描述 |
ENTRY_CREATE |
文件或目录被创建 |
ENTRY_MODIFY |
文件被修改(写入、更新) |
ENTRY_DELETE |
文件或目录被删除 |
监听子目录
WatchService
默认不会监听子目录,如果需要递归监听子目录,可以使用 FileVisitor
注册所有子目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class RecursiveWatchService { public static void main(String[] args) throws IOException { WatchService watchService = FileSystems.getDefault().newWatchService(); Path rootDir = Paths.get("watchedDir");
if (!Files.exists(rootDir)) { Files.createDirectories(rootDir); }
Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); return FileVisitResult.CONTINUE; } });
System.out.println("监听目录及子目录: " + rootDir);
while (true) { WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { System.out.println("事件类型: " + event.kind() + ",文件: " + key.watchable()); } if (!key.reset()) { break; } } } }
|