前言
这段时间在写一个遍历三层文件夹找文件的四级分类,在这个过程中遇到了新学了些东西,也遇到了些问题,于此记录。
一、需求
这个需求是这样的,从一个多级文件夹中获取文件夹名称和文件名称,并将其封装成数组,格式的话是elementui中的el-cascader级联的options绑定的数据源格式,为了增强通用性,还需要在之后添加、删除、修改文件后能够同步获取,(这里使用的是监听)。
二、思路
在项目启动时,加载获取文件夹和文件,并将其转换成json存储在redis中,每次访问直接从redis中获取即可,监听到文件夹和文件的增删改、那么就重新获取一次,再存储到redis中,由于存储的是名称,不需要担心内存问题,因此直接永久存储即可
三、实现
0.监听文件变化基本流程:
- 自定义文件监听类继承FileAlterationListenerAdaptor实现对文件与目录的创建、修改、删除时间的处理;
- 自定义文件监控类,通过指定目录创建一个观察者FileAlterationObserver;
- 向监视器添加文件系统观察器,并添加文件监听器;
- 调用执行(启动自加载)
1.导入依赖:不同的版本支持不同的JDK,2.7需要JDK 8及以上版本
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
2.配置FileMonitorConfig :封装一个文件监控的工具类,核心就是创建一个观察者FileAlterationObserver,将文件路径Path和监听器FileAlterationListener进行封装,然后交给FileAlterationMonitor
@Configuration
public class FileMonitorConfig {
private FileAlterationMonitor monitor = new FileAlterationMonitor();
@Autowired
RedisTemplate redisTemplate;
//监听文件路径
@Value("${spring.file.path}")
String path;
//监听时间间隔
@Value("${spring.file.interval}")
long interval;
/**
* 给文件添加监听
* listener 文件监听器
*/
@Bean
public void monitor() {
FileAlterationListener listener = new FileListener(redisTemplate);
FileAlterationObserver observer = new FileAlterationObserver(new File(path));
monitor.addObserver(observer);
observer.addListener(listener);
}
public void stop() throws Exception {
monitor.stop();
}
@Bean
public void start() throws Exception {
//初始化
FileUtil.path = path;
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
monitor.start();
}
}
3.配置FileListener:创建文件监听器。根据需要在不同的方法内实现对应的业务逻辑处理。
public class FileListener extends FileAlterationListenerAdaptor {
private RedisTemplate redisTemplate;
public FileListener() {}
public FileListener(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void onStart(FileAlterationObserver observer) {
//轮询开始
super.onStart(observer);
// System.out.println("onStart");
}
@Override
public void onDirectoryCreate(File directory) {
// System.out.println("新建:" + directory.getAbsolutePath());
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
//待测试
//新增文件夹 修改文件夹--新增文件夹
}
@Override
public void onDirectoryChange(File directory) {
// System.out.println("修改:" + directory.getAbsolutePath());
//测试 文件夹下的文件在增删改后,是否会调用此方法
//新增文件 会先触发修改文件夹再触发新增文件
//修改文件 会先触发修改文件夹再触发新增和删除文件
//删除文件 会先触发修改文件夹再触发删除文件
//修改文件夹 修改文件夹 -- 新增文件夹 ---删除文件夹
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
}
@Override
public void onDirectoryDelete(File directory) {
// System.out.println("删除:" + directory.getAbsolutePath());
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
//删除文件夹 修改文件夹--删除文件夹
}
@Override
public void onFileCreate(File file) {
String compressedPath = file.getAbsolutePath();
// System.out.println("新建:" + compressedPath);
if (file.canRead()) {
// TODO 读取或重新加载文件内容
// System.out.println("文件变更,进行处理");
String data = FileUtil.getAllFilesToJson();
if (data != null) {
// redisTemplate.delete("file");
// System.out.println(redisTemplate);
// redisTemplate.opsForValue().get("file");
redisTemplate.opsForValue().set("file", data);
}
}
}
@Override
public void onFileChange(File file) {
String compressedPath = file.getAbsolutePath();
// System.out.println("修改:" + compressedPath);
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
}
@Override
public void onFileDelete(File file) {
// System.out.println("删除:" + file.getAbsolutePath());
String data = FileUtil.getAllFilesToJson();
redisTemplate.opsForValue().set("file", data);
}
@Override
public void onStop(FileAlterationObserver observer) {
super.onStop(observer);
// System.out.println("onStop");
}
}
4.编写核心代码
方法一:普通for循环
public static String getAllFilesToJson1() {
int i = 0;
File file = new File(path); //获取其file对象
File[] fs = file.listFiles(); //遍历path下的文件和目录,放在File数组中
if (fs == null) {
// System.out.println("null");
return null;
}
FileInf[] data = new FileInf[fs.length];
for (File f : fs) {//一级分类
if (f.isDirectory()) {
String[] str = f.getAbsolutePath().split("\\\\");
FileInf fileInfo = new FileInf();
fileInfo.setValue(str[str.length - 1]);
fileInfo.setLabel(str[str.length - 1]);
File[] files = f.listFiles();
FileInf[] fileInfos1 = new FileInf[files.length];//用于存放二级分类
//二级分类
int j = 0;
for (File f1 : files) {
if (f1.isDirectory()) {
String[] str1 = f1.getAbsolutePath().split("\\\\");
FileInf fileInfo1 = new FileInf();
fileInfo1.setValue(str1[str1.length - 1]);
fileInfo1.setLabel(str1[str1.length - 1]);
File[] files1 = f1.listFiles();
FileInf[] fileInfos2 = new FileInf[files1.length];//用于存放三级分类
//三级分类
int k = 0;
for (File f2 : files1) {
if (f2.isDirectory()) {
String[] str2 = f2.getAbsolutePath().split("\\\\");
FileInf fileInfo2 = new FileInf();
fileInfo2.setValue(str2[str2.length - 1]);
fileInfo2.setLabel(str2[str2.length - 1]);
// fileInfo1.setChildren(fileInfos2);
fileInfos2[k++] = fileInfo2;
File[] files2 = f2.listFiles();//四级:获取图片名称即可,无children
FileInf[] fileInfos3 = new FileInf[files2.length];//用于存放四级图片
int l = 0;
for (File f3 : files2) {
if (f3.isFile()) {
if (f3.getName().equals("Thumbs.db")) {//排除Thumbs.db文件
FileInf[] tempArr = new FileInf[fileInfos3.length - 1];
System.arraycopy(fileInfos3, 0, tempArr, 0, fileInfos3.length - 1);
fileInfos3 = tempArr;
continue;
}
String[] str3 = f3.getAbsolutePath().split("\\\\");
FileInf fileInfo3 = new FileInf();
fileInfo3.setValue(str3[str3.length - 1]);
fileInfo3.setLabel(str3[str3.length - 1]);
fileInfos3[l++] = fileInfo3;
}
}
fileInfo2.setChildren(fileInfos3);
}
}
fileInfo1.setChildren(fileInfos2);
fileInfos1[j++] = fileInfo1;
}
}
fileInfo.setChildren(fileInfos1);
data[i++] = fileInfo;
}
}
Gson gson = new Gson();
String sData = gson.toJson(data);
return sData;
}
方法二:递归
之后,又使用递归的方式实现了一遍,在使用递归的方式中原来的递归方法中是没有返回值的,但是因为我在排除**Thumsb.db文件(此文件是数据库文件,用于缓存Windows中图片文件的缩略图。当用户打开包含多张图片的文件夹后,系统会遍历整个目录并将该目录中的图片缩略图逐个缓存到Thumbs.db中)的时候,将data数组形参的引用地址改变了,通过"="改变,这种改变无法同步到传递之前的data数组,因此,我通过添加返回值的方式来解决这一问题。代码如下
/**
* 使用递归的方式遍历文件夹及文件并将其转换成json格式
* @return
*/
public static String getAllFilesToJson() {
int i = 0;
// String path = "D:\\mes系统文件"; //要遍历的路径
File file = new File(path); //获取其file对象
// if(file.exists())return;
File[] fs = file.listFiles(); //遍历path下的文件和目录,放在File数组中
if (fs == null) {
return null;
}
FileInf[] data = new FileInf[fs.length];
//data 地址的拷贝,通过数据本身的方法修改内容,data的值同步修改
dg(file, data);
Gson gson = new Gson();
String sData = gson.toJson(data);
System.out.println("data: " + sData);
return sData;
}
public static FileInf[] dg( File file,FileInf[] data) {
//1.递归退出条件
if (!file.isDirectory()) {
return data;
}
//2.处理当前层逻辑
File[] fs = file.listFiles();
int x = 0;
for (File f : fs) {
if (f.isDirectory()) {
String[] str = f.getAbsolutePath().split("\\\\");
FileInf fileInfo = new FileInf();
fileInfo.setValue(str[str.length-1]);
fileInfo.setLabel(str[str.length-1]);
File[] files = f.listFiles();
// FileInf[] fileInfos1 = new FileInf[files.length];//存储子级的children属性
//3.递归调用
FileInf[] fileInfos1 = dg(f, new FileInf[files.length]);
System.out.println("len: " + fileInfos1.length);
fileInfo.setChildren(fileInfos1);
data[x++] = fileInfo;//存储本级文件夹
}
else {
if (f.getName().equals("Thumbs.db")) {//排除Thumbs.db文件
//这里改变data的长度,没有影响到fileInfos1,所以将dg方法原来的无返回值改为返回FileInf[],通过一级一级返回修改fileInfos1的地址
FileInf[] tempArr = new FileInf[data.length - 1];
System.arraycopy(data, 0, tempArr, 0, data.length - 1);
//通过=赋值,修改了形参data的地址,但是没有修改传之前data的地址,所以递归方法加入返回值,在最下面返回data
data = tempArr;
continue;
}
String[] str = f.getAbsolutePath().split("\\\\");
FileInf fileInfo = new FileInf();
fileInfo.setValue(str[str.length - 1]);
fileInfo.setLabel(str[str.length - 1]);
data[x++] = fileInfo;//存储四级图片
}
}
return data;
}
四、总结
在编写递归时,对方法传参有了更深的了解,并写了一个测试类,对我的几种猜测进行了验证,得出了一下结论
方法传参
1.基本类型,传值的拷贝,改变的是形参的值,不改变传之前参数的值
2.引用类型,传地址的拷贝
通过=赋值改变的是形参的地址,不改变传之前参数的地址
通过set等(num[x] = x)对象本身的方法改变,改变的是内容
@SpringBootTest
public class ParamTest {
@Test
public void test() {
//基本类型传参
int a = 1;
test2(a);
System.out.println("a: "+a);//输出:1
//引用类型传参,使用赋值=改变地址
int[] arr = {1, 2, 3};
test3(arr);
System.out.println(Arrays.toString(arr));//输出:[1,2,3]
//引用类型传参,使用对象内部方法改变地址
test4(arr);
System.out.println(Arrays.toString(arr));//输出:[3,3,3]
}
public void test2(int a) {
a = 2;
}
public void test3(int[] arr) {
int[] num = {1,2};
arr = num;
}
public void test4(int[] arr) {
arr[0] = 3;
arr[1] = 3;
}
}