需求:在指定文件路径中,有多个文件夹,查询每个文件下是否有规定格式的图片,并将文件夹名称打印出来。
一、 什么是 SimpleFileVisitor?
SimpleFileVisitor
是 Java NIO
库中的一个类,实现了 FileVisitor
接口,提供了文件树遍历的简单实现。可以通过继承 SimpleFileVisitor
并重写其中的方法,自定义遍历文件和目录时的操作。SimpleFileVisitor
中的几个关键方法:
preVisitDirectory
: 在访问目录之前调用,可以用于初始化状态。visitFile
: 在访问文件时调用,是文件处理的核心方法。visitFileFailed
: 当文件访问失败时调用,例如权限问题。postVisitDirectory
: 在访问完目录及其内容之后调用,用于收尾工作。
1. preVisitDirectory(T dir, BasicFileAttributes attrs)
- 作用:在访问目录之前调用。
- 参数:
dir
: 当前目录的路径。attrs
: 该目录的基本文件属性。
- 返回值:返回
FileVisitResult
,决定如何继续遍历:CONTINUE
: 继续遍历。SKIP_SUBTREE
: 跳过当前目录的子项,但继续遍历其他部分。SKIP_SIBLINGS
: 跳过当前目录的兄弟节点。TERMINATE
: 终止遍历。
- 默认实现:返回
CONTINUE
。
用途示例:可以用来在进入目录前做一些初始化工作,比如重置标志变量。
2. visitFile(T file, BasicFileAttributes attrs)
- 作用:在访问文件时调用。
- 参数:
file
: 当前文件的路径。attrs
: 该文件的基本文件属性。
- 返回值:返回
FileVisitResult
,决定如何继续遍历。 - 默认实现:返回
CONTINUE
。
用途示例:可以用来检查文件的类型、读取文件内容或统计文件数量等。
3. visitFileFailed(T file, IOException exc)
- 作用:当文件访问失败时调用,比如文件权限不足或文件不存在。
- 参数:
file
: 访问失败的文件路径。exc
: 抛出的异常信息。
- 返回值:返回
FileVisitResult
,决定如何继续遍历。 - 默认实现:抛出异常,从而终止遍历。
用途示例:可以用来记录日志或者处理错误,防止遍历因某个错误而中断。
4. FileVisitResult postVisitDirectory(T dir, IOException exc)
- 作用:在访问完目录及其内容之后调用。
- 参数:
dir
: 当前访问的目录路径。exc
: 如果访问过程中出现异常,则传入异常;否则为null
。
- 返回值:返回
FileVisitResult
,决定如何继续遍历。 - 默认实现:返回
CONTINUE
。
用途示例:可以用来执行在访问目录之后的操作,比如统计目录中的文件数量或者清理资源。
二、代码示例
1.代码实现
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
/**
* @author shijiangyong
* @date 2024/9/2 11:14
* @description:
*/
public class SimpleVisitController {
public static void main(String[] args) {
// 指定需要遍历的文件夹路径
Path dir = Paths.get("E:/software/test");
try {
// 调用方法,获取没有图片的文件夹名
List<String> noImageFiles = findFilesWithoutImages(dir);
noImageFiles.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error accessing files: " + e.getMessage());
}
}
// 查找没有图片的文件夹的方法
public static List<String> findFilesWithoutImages(Path dir) throws IOException {
List<String> noImageFiles = new ArrayList<>();
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
// 标记文件夹中是否有图片
private boolean hasImage = false;
// 排除搜索路径
private Path topDir;
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (topDir == null) {
// 设置顶级目录
topDir = dir;
}
hasImage = false;
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (isImageFile(file)) {
// 如果发现图片文件,将标记设置为true
hasImage = true;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
// 捕获并处理文件权限问题
if (exc instanceof AccessDeniedException) {
System.err.println("Access denied: " + file.toString());
// 继续遍历其他文件
return FileVisitResult.CONTINUE;
} else {
// 对于其他异常,可以选择继续或终止遍历
// 或者记录日志并返回 CONTINUE
throw exc;
}
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (!hasImage && !dir.equals(topDir)) {
noImageFiles.add(dir.toString());
}
return FileVisitResult.CONTINUE;
}
// 判断文件是否包含指定图片格式
private boolean isImageFile(Path file) {
String fileName = file.toString().toLowerCase();
return fileName.endsWith(".jpg") || fileName.endsWith(".png") ||
fileName.endsWith(".gif") || fileName.endsWith(".bmp") ||
fileName.endsWith(".jpeg");
}
});
return noImageFiles;
}
}
我在指定目录下新建了三个文件夹,没有图片。
运行结果:
E:\software\test\example
E:\software\test\simple
E:\software\test\visit
在simple文件下添加一张图片运行结果:
E:\software\test\example
E:\software\test\visit
2.拓展用法
这里显示的是查找,也可以批量命名或删除文件。
2.1.重命名
对指定格式的文件重命名,调整visitFile方法的代码:
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (isImageFile(file)) {
// 如果发现图片文件,将标记设置为true
hasImage = true;
}
// 检查是否是 .txt 文件
if (file.toString().toLowerCase().endsWith(".txt")) {
// 构建新的文件名
Path renamedFile = file.resolveSibling("processed_" + file.getFileName());
// 执行重命名操作
Files.move(file, renamedFile);
System.out.println("Renamed: " + file + " to " + renamedFile);
}
return FileVisitResult.CONTINUE;
}
运行结果:
Renamed: E:\software\test\visit\test.txt to E:\software\test\visit\processed_test.txt
2.2 删除指定文件
比如示例代码逻辑调整为没有找到指定格式图片,且文件夹是空的,则删除文件夹,调整postVisitDirectory代码:
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (!hasImage && !dir.equals(topDir)) {
try {
Files.delete(dir);
System.out.println("Deleted folder: " + dir);
} catch (DirectoryNotEmptyException e) {
// 捕获并处理文件夹不为空的情况
System.err.println("Failed to delete folder (not empty): " + dir);
} catch (IOException e) {
// 处理其他可能的IO异常
System.err.println("Failed to delete folder: " + dir + " due to " + e.getMessage());
}
}
return FileVisitResult.CONTINUE;
}
运行结果(visit有个txt文件,simple中有一张图片,example啥也没有被删除了):
若是非空文件夹也要删除,继续调整代码:
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (!hasImage && !dir.equals(topDir)) {
try {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
System.out.println("Deleted file: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
System.out.println("Deleted directory: " + dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
System.err.println("Failed to delete directory tree: " + dir + " due to " + e.getMessage());
}
}
return FileVisitResult.CONTINUE;
}
运行结果:
在处理大量文件或深层次文件结构时,单线程遍历可能效率较低。可以使用 Java 8 的并行流(Parallel Streams)或者 ForkJoinPool
来提高处理速度。
删除操作是不可逆的,确保执行前备份重要数据或确认文件夹内容可以安全删除。
人生而自由,却无往不在枷锁之中。 – 卢梭