Java 目录操作
介绍
在Java编程中,目录(文件夹)操作是文件I/O操作的重要部分。Java NIO (New I/O) 包提供了比传统java.io
更强大和灵活的API来处理目录。本文将介绍如何使用Java NIO中的Path
和Files
类进行目录操作,这些类位于java.nio.file
包下。
目录操作包括:
- 创建目录
- 列出目录内容
- 检查文件或目录是否存在
- 复制和移动目录
- 删除目录
Path接口和Files类
在Java NIO中,Path
接口和Files
类是目录操作的核心。
Path接口
Path
接口代表文件系统中的路径。路径可以指向文件或目录,可以是绝对路径或相对路径。
import java.nio.file.Path;
import java.nio.file.Paths;
// 创建Path实例
Path path1 = Paths.get("C:\\Users\\Documents\\test"); // 绝对路径
Path path2 = Paths.get("test", "subfolder"); // 相对路径
Files类
Files
类提供了各种静态方法来操作文件和目录。
创建目录
使用Files.createDirectory()
方法可以创建单个目录,而使用Files.createDirectories()
方法可以创建多级目录(如果父目录不存在,会自动创建)。
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
public class DirectoryCreationExample {
public static void main(String[] args) {
// 创建单个目录
Path singleDir = Paths.get("test_directory");
try {
Files.createDirectory(singleDir);
System.out.println("目录创建成功: " + singleDir);
} catch (IOException e) {
System.err.println("创建目录失败: " + e.getMessage());
}
// 创建多级目录
Path multipleDir = Paths.get("parent/child/grandchild");
try {
Files.createDirectories(multipleDir);
System.out.println("多级目录创建成功: " + multipleDir);
} catch (IOException e) {
System.err.println("创建多级目录失败: " + e.getMessage());
}
}
}
输出:
目录创建成功: test_directory
多级目录创建成功: parent/child/grandchild
如果目录已经存在,createDirectory()
会抛出FileAlreadyExistsException
,而createDirectories()
会正常执行不抛出异常。
检查文件或目录是否存在
使用Files.exists()
和Files.notExists()
方法可以检查路径是否指向存在的文件或目录。
Path path = Paths.get("test_directory");
boolean exists = Files.exists(path);
System.out.println("目录存在: " + exists);
// 检查是否是目录
if (exists && Files.isDirectory(path)) {
System.out.println("该路径是一个目录");
}
列出目录内容
使用Files.list()
方法可以获取目录中的直接内容(文件和子目录),而使用Files.walk()
方法可以递归地获取目录中的所有内容。
列出直接内容
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;
public class ListDirectoryContentsExample {
public static void main(String[] args) {
Path dir = Paths.get("parent");
try (Stream<Path> entries = Files.list(dir)) {
System.out.println("目录内容:");
entries.forEach(path -> System.out.println(path.getFileName()));
} catch (IOException e) {
System.err.println("无法列出目录内容: " + e.getMessage());
}
}
}
输出可能如下:
目录内容:
child
file1.txt
file2.log
递归列出所有内容
Path dir = Paths.get("parent");
try (Stream<Path> entries = Files.walk(dir)) {
System.out.println("递归列出所有内容:");
entries.forEach(System.out::println);
} catch (IOException e) {
System.err.println("无法遍历目录: " + e.getMessage());
}
输出可能如下:
递归列出所有内容:
parent
parent\child
parent\child\grandchild
parent\child\grandchild\file3.txt
parent\file1.txt
parent\file2.log
过滤目录内容
结合Java 8的Stream API,我们可以轻松过滤目录内容。
Path dir = Paths.get("parent");
// 只列出.txt文件
try (Stream<Path> entries = Files.walk(dir)) {
System.out.println("所有.txt文件:");
entries
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("无法遍历目录: " + e.getMessage());
}
// 只列出目录
try (Stream<Path> entries = Files.list(dir)) {
System.out.println("直接子目录:");
entries
.filter(Files::isDirectory)
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("无法列出目录: " + e.getMessage());
}
复制和移动目录
使用Files.copy()
和Files.move()
方法可以复制和移动目录。
复制目录
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.io.IOException;
public class CopyDirectoryExample {
public static void main(String[] args) {
Path source = Paths.get("source_dir");
Path target = Paths.get("target_dir");
try {
// 创建源目录和一些文件(为了演示)
Files.createDirectories(source);
Files.writeString(source.resolve("test.txt"), "Hello World");
// 只复制目录本身,不复制内容
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("目录已复制");
// 注意:此方法不会递归复制目录内容
// 如果需要递归复制,需要自己编写方法或使用第三方库
} catch (IOException e) {
System.err.println("复制目录失败: " + e.getMessage());
}
}
}
标准的Files.copy()
方法不会递归复制目录内容,只会复制目录本身。如果需要递归复制整个目录树,可以编写自定义方法或使用第三方库,如Apache Commons IO。
移动目录
Path source = Paths.get("source_dir");
Path target = Paths.get("new_location");
try {
// 移动目录(包括其内容)
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("目录已移动到: " + target);
} catch (IOException e) {
System.err.println("移动目录失败: " + e.getMessage());
}
删除目录
使用Files.delete()
方法可以删除空目录,而使用Files.walkFileTree()
配合FileVisitor
可以递归删除非空目录。
删除空目录
Path dir = Paths.get("empty_dir");
try {
Files.delete(dir);
System.out.println("目录已删除");
} catch (IOException e) {
System.err.println("删除目录失败: " + e.getMessage());
}
递归删除非空目录
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
public class DeleteDirectoryExample {
public static void main(String[] args) {
Path dir = Paths.get("non_empty_dir");
try {
// 创建测试目录结构(为了演示)
Files.createDirectories(dir.resolve("subdir/nested"));
Files.writeString(dir.resolve("file1.txt"), "Test content");
Files.writeString(dir.resolve("subdir/file2.txt"), "More content");
// 递归删除整个目录树
Files.walkFileTree(dir, new 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;
}
});
System.out.println("目录树已完全删除");
} catch (IOException e) {
System.err.println("删除目录树失败: " + e.getMessage());
}
}
}
输出:
删除文件: non_empty_dir\file1.txt
删除文件: non_empty_dir\subdir\file2.txt
删除目录: non_empty_dir\subdir\nested
删除目录: non_empty_dir\subdir
删除目录: non_empty_dir
目录树已完全删除
实际应用案例
案例1:备份系统
以下是一个简单的代码片段,展示如何创建一个指定目录的备份:
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DirectoryBackupExample {
public static void main(String[] args) {
Path sourceDir = Paths.get("project_files");
// 创建带有时间戳的备份目录名
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
Path backupDir = Paths.get("backup_" + timestamp);
try {
// 创建备份目录
Files.createDirectory(backupDir);
// 遍历源目录并复制所有内容
Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetDir = backupDir.resolve(sourceDir.relativize(dir));
Files.createDirectories(targetDir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path targetFile = backupDir.resolve(sourceDir.relativize(file));
Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
System.out.println("备份完成,备份目录: " + backupDir);
} catch (IOException e) {
System.err.println("备份失败: " + e.getMessage());
}
}
}
案例2:目录监控
以下示例展示如何使用Java NIO的WatchService
来监控目录变化:
import java.nio.file.*;
import java.io.IOException;
public class DirectoryWatcherExample {
public static void main(String[] args) {
Path dir = Paths.get("watched_directory");
try {
// 确保目录存在
if (!Files.exists(dir)) {
Files.createDirectory(dir);
}
// 创建WatchService
WatchService watchService = FileSystems.getDefault().newWatchService();
// 注册要监控的事件类型
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("开始监控目录: " + dir);
while (true) {
WatchKey key;
try {
key = watchService.take(); // 等待事件
} catch (InterruptedException e) {
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 忽略OVERFLOW事件
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
// 获取文件名
@SuppressWarnings("unchecked")
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path fileName = pathEvent.context();
System.out.printf("检测到事件 %s: %s%n", kind.name(), fileName);
}
// 重置key以继续接收事件
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (IOException e) {
System.err.println("目录监控失败: " + e.getMessage());
}
}
}
总结
Java NIO提供了强大的API来执行各种目录操作。在本教程中,我们学习了如何:
- 创建单个和多级目录
- 检查文件或目录是否存在
- 列出目录内容(直接内容和递归获取所有内容)
- 过滤目录内容
- 复制和移动目录
- 删除空目录和非空目录
- 通过实际案例(备份系统和目录监控)应用这些知识
通过Java NIO的目录操作API,我们可以编写更高效和灵活的文件系统操作代码,这对于开发需要文件I/O的应用程序非常有用。
练习
- 编写一个程序,递归列出给定目录中的所有Java源文件(
.java
文件)。 - 创建一个工具类,提供递归复制整个目录树的方法。
- 实现一个简单的文件整理程序,根据文件扩展名将文件移动到不同的子文件夹。
- 修改目录监控示例,使其只监控特定类型的文件变化(例如,只监控
.txt
文件)。 - 创建一个程序,计算给定目录中所有文件的总大小。
附加资源
通过这些资源和练习,你将能够掌握Java NIO中的目录操作,并将其应用到实际项目中。