跳到主要内容

Java 目录操作

介绍

在Java编程中,目录(文件夹)操作是文件I/O操作的重要部分。Java NIO (New I/O) 包提供了比传统java.io更强大和灵活的API来处理目录。本文将介绍如何使用Java NIO中的PathFiles类进行目录操作,这些类位于java.nio.file包下。

目录操作包括:

  • 创建目录
  • 列出目录内容
  • 检查文件或目录是否存在
  • 复制和移动目录
  • 删除目录

Path接口和Files类

在Java NIO中,Path接口和Files类是目录操作的核心。

Path接口

Path接口代表文件系统中的路径。路径可以指向文件或目录,可以是绝对路径或相对路径。

java
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()方法可以创建多级目录(如果父目录不存在,会自动创建)。

java
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()方法可以检查路径是否指向存在的文件或目录。

java
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()方法可以递归地获取目录中的所有内容。

列出直接内容

java
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

递归列出所有内容

java
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,我们可以轻松过滤目录内容。

java
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()方法可以复制和移动目录。

复制目录

java
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。

移动目录

java
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可以递归删除非空目录。

删除空目录

java
Path dir = Paths.get("empty_dir");

try {
Files.delete(dir);
System.out.println("目录已删除");
} catch (IOException e) {
System.err.println("删除目录失败: " + e.getMessage());
}

递归删除非空目录

java
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:备份系统

以下是一个简单的代码片段,展示如何创建一个指定目录的备份:

java
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来监控目录变化:

java
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来执行各种目录操作。在本教程中,我们学习了如何:

  1. 创建单个和多级目录
  2. 检查文件或目录是否存在
  3. 列出目录内容(直接内容和递归获取所有内容)
  4. 过滤目录内容
  5. 复制和移动目录
  6. 删除空目录和非空目录
  7. 通过实际案例(备份系统和目录监控)应用这些知识

通过Java NIO的目录操作API,我们可以编写更高效和灵活的文件系统操作代码,这对于开发需要文件I/O的应用程序非常有用。

练习

  1. 编写一个程序,递归列出给定目录中的所有Java源文件(.java文件)。
  2. 创建一个工具类,提供递归复制整个目录树的方法。
  3. 实现一个简单的文件整理程序,根据文件扩展名将文件移动到不同的子文件夹。
  4. 修改目录监控示例,使其只监控特定类型的文件变化(例如,只监控.txt文件)。
  5. 创建一个程序,计算给定目录中所有文件的总大小。

附加资源

通过这些资源和练习,你将能够掌握Java NIO中的目录操作,并将其应用到实际项目中。