File Name Prefix Processor——一个用Java写的文件名前缀批量处理工具

某些时候,你磁盘中有一大堆文件出现了重复的前缀,而你又不想一个一个地重命名时……

前言:我为什么想做这个小工具?

前一段时间,我使用Downie这个下载工具下载网站上的视频集的时候,发现它下载到的文件都是以这个格式命名的:

1
视频集名字 - 数字编号 - 这一集的名字

当我想回看这些视频的时候,能帮助我快速定位我想找的视频的文件名,其实只有后面的这一集的名字,前面的其实都是无用的内容,对于几个零散的文件,我直接上手重命名就好了,但是我面对的是…
海量文件
这谁顶得住啊!
所以,我就想着能不能写一个小工具,帮我完成批量重命名这件繁琐而又重复的工作。

懒汉往往是会推动科技进步的

既然我不想动手,那么我就开始借助程序的力量了。
上号

Coding Time!

在一切开始之前,先来理一下逻辑:

直接上图方便大家理解哈。
流程图

首先,编写一个方法,输出一堆花里胡哨的Banner

其实这是我个人编程风格,编写的程序执行时往往是会先输出ASCII艺术字风格Banner
使用http://patorjk.com/software/taag/ 这个网站将英文字母转换为ASCII大艺术字,输入【File Name Prefix Processor】,然后挑一个喜欢的艺术字风格,放在System.out.Println()语句中进行输出。

如果你想一下一下地逐行输出,可以使用Thread.sleep(300);延迟一下每一行ASCII艺术字的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static void banner(){
System.out.println(
"███████╗ ██╗ ██╗ ███████╗ \n" +
"██╔════╝ ██║ ██║ ██╔════╝ \n" +
"█████╗ ██║ ██║ █████╗ \n" +
"██╔══╝ ██║ ██║ ██╔══╝ \n" +
"██║ ██║ ███████╗ ███████╗ \n" +
"╚═╝ ╚═╝ ╚══════╝ ╚══════╝ \n"
);
relax();
System.out.println(
"███╗ ██╗ █████╗ ███╗ ███╗ ███████╗ \n" +
"████╗ ██║ ██╔══██╗ ████╗ ████║ ██╔════╝ \n" +
"██╔██╗ ██║ ███████║ ██╔████╔██║ █████╗ \n" +
"██║╚██╗██║ ██╔══██║ ██║╚██╔╝██║ ██╔══╝ \n" +
"██║ ╚████║ ██║ ██║ ██║ ╚═╝ ██║ ███████╗ \n" +
"╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ \n"
);
relax();
System.out.println(
"██████╗ ██████╗ ███████╗ ███████╗ ██╗ ██╗ ██╗ \n" +
"██╔══██╗ ██╔══██╗ ██╔════╝ ██╔════╝ ██║ ╚██╗██╔╝ \n" +
"██████╔╝ ██████╔╝ █████╗ █████╗ ██║ ╚███╔╝ \n" +
"██╔═══╝ ██╔══██╗ ██╔══╝ ██╔══╝ ██║ ██╔██╗ \n" +
"██║ ██║ ██║ ███████╗ ██║ ██║ ██╔╝ ██╗ \n" +
"╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ \n"
);
relax();
System.out.println(
"██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ███████╗ ██████╗ ██████╗ \n" +
"██╔══██╗ ██╔══██╗ ██╔═══██╗ ██╔════╝ ██╔════╝ ██╔════╝ ██╔════╝ ██╔═══██╗ ██╔══██╗\n" +
"██████╔╝ ██████╔╝ ██║ ██║ ██║ █████╗ ███████╗ ███████╗ ██║ ██║ ██████╔╝\n" +
"██╔═══╝ ██╔══██╗ ██║ ██║ ██║ ██╔══╝ ╚════██║ ╚════██║ ██║ ██║ ██╔══██╗\n" +
"██║ ██║ ██║ ╚██████╔╝ ╚██████╗ ███████╗ ███████║ ███████║ ╚██████╔╝ ██║ ██║\n" +
"╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝\n"
);
}
static void relax(){
try{
Thread.sleep(300);
}catch (Exception e){
e.printStackTrace();
}
}

完成之后,编写主类中的main()方法:

因为我不想让main()方法显得过于臃肿,所以我将功能性的代码拆分到其他工具类中,main()方法中只留下了一些提示性的输出语句和调用工具类方法的代码。
(英语渣渣,请不要在意英文提示内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) {
banner();
// 要求用户输入路径
System.out.println("请输入要处理文件名前缀的目录路径:\nPlease enter the directory path you want to process the file name prefix:");
Scanner sc = new Scanner(System.in);
String folderPath = sc.nextLine();
FileReader.check(folderPath);
System.out.println("即将处理【" + folderPath + "】下的文件...\nWe will process the files in the【" + folderPath + "】folder...");
// 要求用户输入需要处理的前缀
System.out.println("请输入你要删除的文件名前缀(Please enter the file name prefix that you want to delete):");
String prefix = sc.nextLine();
System.out.println("要删除的文件名前缀是(The prefix of the file name you want to delete is):" + prefix);
// 询问用户是否是从Downie下载的
System.out.println("上述文件是通过下列哪个下载器下载的?\nIf the above file(s) was downloaded via a downloader, please indicate which downloader you used.");
System.out.println(
"【1】Downie\t\t"+"【n】我没有使用下载器(I did not use any downloader)"
);
String choice = sc.nextLine();
switch (choice){
case "1":
PrefixProcessor.downiePrefixProcess(folderPath,prefix);
break;
case "n":
PrefixProcessor.commonProcess(folderPath,prefix);
break;
default:
System.out.println("不支持的操作!即将退出...\nUnsupported!Exiting...");
System.exit(0);
}
}

编写工具类FileReader,判断目录是否存在:

如果用户启动程序后,输入一个不存在的路径,程序可能会产生java.io.fileNotFoundException
为了避免这种情况,首先对这个问题进行处理,编写check()方法,参数是用户输入的目录路径,类型为字符串。

1
2
3
4
5
6
7
8
9
10
11
​```java
public class FileReader {
public static void check(String folderPath){
File f = new File(folderPath);
if (!f.exists()) { // 如果这个路径不存在,就结束程序
System.out.println("目录【" + folderPath + "】不存在!");
System.out.println("Path【" + folderPath + "】does not exist!");
System.exit(0);
}
}
}

为什么check(String folderPath)是静态方法?

因为我懒得在main()方法中实例化对象,这会增加不必要的语句,并且执行效率也高不了多少,还不如一开始就将这个方法载入到内存中。

编写根据前缀匹配出要处理的文件的方法readFiles()

当目录存在,并且用户输入了要处理的前缀时,就应该开始寻找符合条件的文件了。
当然,这个程序处理的是文件,不是文件夹,所以我在程序中设置了忽略文件夹的操作。
FileReader类中追加readFiles()方法,传入两个参数folderPath(用户输入的路径)和prefix(要处理的前缀),参数类型均为字符串类型。返回值是ArrayList<File>数组,用于被其他方法调用并处理这个数组中的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static ArrayList<File> readFiles(String folderPath, String prefix){
File f = new File(folderPath);
File[] filesInDirectory = f.listFiles();
// 创建存放文件的列表
ArrayList<File> fileListArray = new ArrayList<>();
// 遍历并添加需要的文件到待处理列表
for (File fs : Objects.requireNonNull(filesInDirectory)) {
if (fs.isDirectory()) {
System.out.println(fs.getName() + "(是目录,不支持重命名;\n" + fs.getName() + "Is a directory, doesn't support renaming!)");
} else if (fs.getName().startsWith(prefix)) {
System.out.println("找到文件(Found File):" + fs.getName());
fileListArray.add(fs);
}
}
// 检查有没有指定前缀的文件
if (fileListArray.size()==0){
System.out.println("找不到文件名以【" + prefix +"】开头的文件!即将退出...");
System.out.println("Cannot find the file whose name starts with 【" + prefix + "】, exiting...");
System.exit(0);
}
return fileListArray;
}

在这里,我先将用户指定的文件夹中的文件全部读出来,然后使用前缀进行文件名匹配,将匹配出来的文件放到ArrayList中,并将这个ArrayList返回,方便这个方法的调用者处理。

编写两个处理规则方法,实现重命名文件:

目前我能接触到的,就两种情况:普通的前缀和Downie下载的文件前缀(带编号的前缀),所以我只编写两个前缀处理方法。
创建PrefixProcessor类,分别编写commonProcess()downiePrefixProcess()方法,传入两个参数folderPath(用户输入的路径)和prefix(要处理的前缀),参数类型均为字符串类型。在这两个方法中调用上面写好的readFiles(String folderPath, String prefix)方法,并处理其中的文件的文件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.cf.util;

import java.io.File;
import java.util.ArrayList;

public class PrefixProcessor {
// Downie处理
public static void downiePrefixProcess(String folderPath, String prefix){
ArrayList<File> fileList = FileReader.readFiles(folderPath, prefix);
// 获取前缀长度
int length = prefix.length();
for (File file : fileList) {
String originName = file.getName();
// 建立新的文件名,因为Downie会添加「 - xxx - 」作为中间修饰,所以要往后多切割
String newName = originName.substring(length + 9);
file.renameTo(new File(folderPath + File.separator + newName)); // 别忘记添加「File.separator」,否则会将被处理的文件移动到上级目录,并在文件名中增加原来目录的名字作为前缀
}
}
// 未指定,一般处理
public static void commonProcess(String folderPath, String prefix){
ArrayList<File> fileList = FileReader.readFiles(folderPath, prefix);
// 获取前缀长度
int length = prefix.length();
for (File file : fileList) {
String originName = file.getName();
// 建立新的文件名
String newName = originName.substring(length);
file.renameTo(new File(folderPath + File.separator + newName));
}
}
}

大家在这里可以注意到有一个坑,在代码第11行和23行,我在文件路径和文件名中间拼接了File.separator,如果你不这么做,那么被处理的文件将会被移动到当前目录的上一层,并且添加当前文件夹名称作为新的前缀(做了跟没做一样)。

编写结果报告方法,输出DONE !语句:

和上面输出Banner的方式一样,制作ASCII大艺术字,然后放在System.out.println()语句中输出就好。

后记

当然,这个程序并不是非常完美的,其中还有很多逻辑漏洞,比如说我没有为输入的前缀进行判空处理等,但是谁会这么无聊,故意用空前缀浪费时间呢(笑)
最后,完整代码和Release可以到相关链接处的GitHub地址里下载,Have fun!XD

相关链接

开源项目地址:
控制台版:https://github.com/chemicalfiber/file-name-prefix-processor
https://gitee.com/chemicalfiber/file-name-prefix-processor
GUI版:https://github.com/chemicalfiber/file-name-prefix-processor-GUI
https://gitee.com/chemicalfiber/file-name-prefix-processor-GUI
编写+测试视频地址(B站):
控制台版:https://www.bilibili.com/video/av177451812
GUI版:https://www.bilibili.com/video/av251059542
编写+测试视频地址(YouTube):
控制台版:https://youtu.be/OEVjdbnTywk
GUI版:https://youtu.be/vF-x71zqqh8