1、接触FileVisitor接口的背景
在这疫情期间,在家办公补了些java的基础知识,上学时学的囫囵吞枣,现在补的时候才发现遗漏了好些好东西。
接了这样的一个任务,每月定时删除系统产生的大量废弃文件,数量达到很多万份,大小达几十个G(50G吧)。哦,删除文件当然是有条件的嘛,删除一个月之外的文件,不然直接使用java调用命令端口,删除存放垃圾文件的目录,让操作系统自己去递归删除就行了,我就不用费这个心劲儿了。
刚开始没注意这个量,随手就写了个递归删除。试运行的时候就感觉不对劲儿了,反正很耗时。。。
所以在各种的了解查询下,接触到了FileVisitor<T>
这个接口。
首先,使用这个接口遍历文件的速度确实比之前的递归块,大概快个1/2,但是它消耗的内存却要比之前的递归要多些(至于这一点,由于个人能力限制,想着各种办法都没调出理想的内存消耗。。。)。但是整体来说,使用FileVisitor<T>
遍历文件树,确实方便得多;
其次,FileVisitor<T>
对于不熟悉这个接口的用法的同学是非常麻烦的,因为它代码可读性低,不像直接写的递归那个直观。相反的FileVisitor<T>
的代码扩展性却比直接写的递归高很多。
2、FileVisitor介绍
2.1、基本介绍
实现FileVisitor<T>
接口,重写这个接口的四个方法:preVisitDirectory
、visitFile
、visitFileFailed
、postVisitDirectory
。
这几个方法的返回值都是枚举类型:
CONTINUE:继续;
SKIP_SIBLINGS:继续遍历,不过忽略当前节点的兄弟节点,直接返回上一层继续遍历
SKIP_SUBTREE:继续遍历,忽略后代目录,但继续访问子文件
TERMINATE:终止遍历
2.2、运作流程
preVisitDirectory作为遍历文件目录的一个入口,携带得有该目录的基本属性。如果preVisitDirectory
在访问目录中的条目时发现是文件,而非目录,这时调用visitFile
方法,来访问该文件,这个方法同样携带得有此文件的基本属性。当被访问的文件无法被正常访问时调用visitFileFailed
方法。当preVisitDirectory
访问的某个目录里的条目(已经访问了所有的后代)被全部访问完时,调用postVisitDirectory
。
2.3、文件访问者
该接口的实现被提供给Files.walkFileTree方法来访问文件树中的每个文件
Files.walkFileTree(Path start, Set options,int maxDepth,FileVisitor<? super Path> visitor )
参数介绍:
Path start:起始文件,也就是起始文件地址
Set<FileVisitOption> options:配置遍历的选项,一般为符号链接
简单理解一下符号链接(软连接):
是一种特殊的文件在这个文件中,保存了另一个文件的路径,打开符号链接的时候,如不特别说明,实际上打开的是保存的路径所对应的文件。
简单的理解就像:符号链接就像是快捷方式
还有一种叫做硬连接:硬链接就像是一个同步的副本,删掉其中一个也不会影响另一个的存在。
连接软、硬连接可以参考:https://blog.youkuaiyun.com/qq_42777071/article/details/105537540
int maxDepth: 要访问的目录级别的最大数量,就是要遍历的深度,要具体遍历到第几层
FileVisitor<? super Path> visitor :为每个文件调用的文件访问者
3.0、删除文件代码
public class NioDelFile implements FileVisitor<Path> {
/**
* 符号链接:是一种特殊的文件
*在这个文件中,保存了另一个文件的路径,打开符号链接的时候,
* 如不特别说明,实际上打开的是保存的路径所对应的文件。
* 符号链接就像是快捷方式。
* 而硬链接就像是一个同步的副本,删掉其中一个也不会影响另一个的存在。
*/
public static void main(String[] args){
Path directory = Paths.get("E:\\RuiPuKeJi\\del_file(2)");
EnumSet opts1 = EnumSet.of(FileVisitOption.FOLLOW_LINKS); // 遵循符号链接
try {
Runtime runtime = Runtime.getRuntime();
long time1 = System.currentTimeMillis();
long memory1 = runtime.freeMemory();
Files.walkFileTree(directory, opts1, 5, new NioDelFile());
long memory2 = runtime.freeMemory();
long time2 = System.currentTimeMillis();
System.out.println("---------------------------------------------------------");
System.out.println("运行时间--->"+(time2-time1));
System.out.println("运行内存--->"+(memory2-memory1));
System.gc();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* preVisitDirectory:被遍历文件目录的一个入口,并且携带得有该目录的基本属性
* @param dir
* @param attrs
* @return
* @throws IOException
*/
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
/**
* 访问文件时的一个方法,并会携带文件的基本属性
* @param file 对该文件的引用
* @param attrs 里面存放的是文件的基本属性
* @return
* @throws IOException
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
deleteFileByFile(file,attrs);
return FileVisitResult.CONTINUE;
}
/**
* 为无法访问的文件时调用的方法
* @param file
* @param exc
* @return
* @throws IOException
*/
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
/**
* 当文件目录中的所以子文件访问结束后再调用目录,并且已经访问了所有的后代
* @param dir
* @param exc
* @return
* @throws IOException
*/
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
/**
*
* @param file 对该文件的引用
* @param attrs 该文件的基本属性
* @throws IOException
*/
public void deleteFileByFile(Path file,BasicFileAttributes attrs) throws IOException {
if (getLastTime()-attrs.lastModifiedTime().toMillis()>0){
boolean flag = Files.deleteIfExists(file);
System.out.println("删除成功了?"+(flag?"成功":"失败"));
}
}
/**
* 获取一个月之前的时间
*/
private long getLastTime() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MONTH,-1);
return calendar.getTimeInMillis();
}
}