一、 项目简介
本项目是一个功能完整的文件批量处理工具,用于快速管理文件,提高工作效率。
核心功能:
- 统计功能:统计文件夹大小和文件数量
- 分类功能:按文件类型自动分类到不同文件夹
- 重命名功能:批量重命名文件(支持正则替换)
- 命令行操作:支持命令行参数,方便自动化脚本调用
知识点:JDK 8+、File I/O、正则表达式、异常处理、递归算法
应用场景:
- 清理混乱的下载文件夹
- 批量处理照片/文档
- 统计项目代码文件数量
- 自动化文件管理脚本
二、 项目架构
D:/java/FileTool/
├── src/
│ └── com/example/filetool/
│ ├── FileTool.java # 主入口类
│ ├── command/
│ │ ├── CountCommand.java # 统计命令
│ │ ├── ClassifyCommand.java # 分类命令
│ │ └── RenameCommand.java # 重命名命令
│ └── util/
│ └── SizeFormatter.java # 文件大小格式化工具
└── out/ # 编译输出目录
设计模式:命令模式(Command Pattern)
- 每个功能独立封装成一个Command类
- 易于扩展新功能
- 代码结构清晰,职责分明
三、 完整代码实现
1. 主入口类:FileTool.java
java
package com.example.filetool;
import com.example.filetool.command.CountCommand;
import com.example.filetool.command.ClassifyCommand;
import com.example.filetool.command.RenameCommand;
public class FileTool {
public static void main(String[] args) {
if (args.length == 0) {
printHelp();
return;
}
String command = args[0];
switch (command) {
case "count":
new CountCommand().execute(args);
break;
case "classify":
new ClassifyCommand().execute(args);
break;
case "rename":
new RenameCommand().execute(args);
break;
case "help":
case "-h":
case "--help":
printHelp();
break;
default:
System.out.println("未知命令: " + command);
printHelp();
}
}
private static void printHelp() {
System.out.println("================================");
System.out.println(" 文件批量处理工具 v1.0");
System.out.println("================================");
System.out.println();
System.out.println("用法: java FileTool <command> [args...]");
System.out.println();
System.out.println("命令:");
System.out.println(" count <目录> 统计目录下的文件数量和总大小");
System.out.println(" classify <目录> 按类型分类文件到子文件夹");
System.out.println(" rename <目录> <查找> <替换> 批量重命名文件");
System.out.println(" help 显示此帮助");
System.out.println();
System.out.println("示例:");
System.out.println(" java -cp out com.example.filetool.FileTool count D:/java/FileTool/test");
System.out.println(" java -cp out com.example.filetool.FileTool classify D:/java/FileTool/test");
System.out.println(" java -cp out com.example.filetool.FileTool rename D:/java/FileTool/test \" \" \"_\"");
System.out.println();
}
}
2. 统计命令:CountCommand.java
java
package com.example.filetool.command;
import com.example.filetool.util.SizeFormatter;
import java.io.File;
public class CountCommand {
private long totalFiles = 0;
private long totalDirs = 0;
private long totalSize = 0;
public void execute(String[] args) {
if (args.length < 2) {
System.out.println("用法: count <目录>");
return;
}
File target = new File(args[1]);
if (!target.exists()) {
System.out.println("错误: 路径不存在 -> " + target.getAbsolutePath());
return;
}
if (!target.isDirectory()) {
System.out.println("错误: 不是目录 -> " + target.getAbsolutePath());
return;
}
System.out.println("正在统计: " + target.getAbsolutePath());
long start = System.currentTimeMillis();
walk(target);
long cost = System.currentTimeMillis() - start;
System.out.println("--------------------------------");
System.out.println("文件数量: " + totalFiles);
System.out.println("子目录数: " + totalDirs);
System.out.println("总大小: " + SizeFormatter.format(totalSize));
System.out.println("耗时: " + cost + " ms");
}
// 递归遍历:把目录当成"树"来处理
private void walk(File dir) {
File[] children = dir.listFiles();
if (children == null) {
return;
}
for (File child : children) {
if (child.isDirectory()) {
totalDirs++;
walk(child);
} else if (child.isFile()) {
totalFiles++;
totalSize += child.length();
}
}
}
}
功能说明:
- 递归遍历整个目录树
- 统计文件数量、子目录数量、总大小
- 记录执行耗时
3. 分类命令:ClassifyCommand.java
java
package com.example.filetool.command;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;
public class ClassifyCommand {
// 文件分类映射表
private static final Map<String, List<String>> CATEGORY_MAP = new HashMap<>();
static {
CATEGORY_MAP.put("图片", Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"));
CATEGORY_MAP.put("文档", Arrays.asList(".txt", ".doc", ".docx", ".pdf", ".xls", ".xlsx", ".ppt", ".pptx"));
CATEGORY_MAP.put("视频", Arrays.asList(".mp4", ".avi", ".mkv", ".mov", ".flv", ".wmv"));
CATEGORY_MAP.put("音频", Arrays.asList(".mp3", ".wav", ".flac", ".aac", ".m4a"));
CATEGORY_MAP.put("压缩包", Arrays.asList(".zip", ".rar", ".7z", ".tar", ".gz"));
CATEGORY_MAP.put("代码", Arrays.asList(".java", ".py", ".js", ".html", ".css", ".cpp", ".c", ".go"));
CATEGORY_MAP.put("可执行", Arrays.asList(".exe", ".msi", ".dmg", ".apk"));
}
public void execute(String[] args) {
if (args.length < 2) {
System.out.println("用法: classify <目录>");
return;
}
File sourceDir = new File(args[1]);
if (!sourceDir.exists() || !sourceDir.isDirectory()) {
System.out.println("错误: 路径不存在或不是目录");
return;
}
System.out.println("目标目录: " + sourceDir.getAbsolutePath());
System.out.println("开始分类文件...");
System.out.println("--------------------------------");
// 创建分类文件夹
for (String category : CATEGORY_MAP.keySet()) {
new File(sourceDir, category).mkdir();
}
File otherDir = new File(sourceDir, "其他");
otherDir.mkdir();
int movedCount = 0;
int failCount = 0;
Map<String, Integer> categoryCount = new HashMap<>();
File[] files = sourceDir.listFiles();
if (files == null || files.length == 0) {
System.out.println("目录为空");
return;
}
for (File file : files) {
if (file.isDirectory()) continue;
String extension = getFileExtension(file.getName());
String category = determineCategory(extension);
File targetDir = new File(sourceDir, category);
File targetFile = handleDuplicateFileName(targetDir, file.getName());
try {
Files.move(file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("✓ " + file.getName() + " -> " + category + "/");
movedCount++;
categoryCount.put(category, categoryCount.getOrDefault(category, 0) + 1);
} catch (IOException e) {
System.out.println("✗ " + file.getName() + " -> 移动失败");
failCount++;
}
}
System.out.println("--------------------------------");
System.out.println("分类完成: 成功 " + movedCount + " 个,失败 " + failCount + " 个");
System.out.println("\n各类文件数量:");
categoryCount.forEach((k, v) -> System.out.println(" " + k + ": " + v + " 个"));
}
private String getFileExtension(String fileName) {
int lastDot = fileName.lastIndexOf('.');
return (lastDot > 0) ? fileName.substring(lastDot).toLowerCase() : "";
}
private String determineCategory(String extension) {
for (Map.Entry<String, List<String>> entry : CATEGORY_MAP.entrySet()) {
if (entry.getValue().contains(extension)) {
return entry.getKey();
}
}
return "其他";
}
private File handleDuplicateFileName(File dir, String fileName) {
File targetFile = new File(dir, fileName);
if (!targetFile.exists()) return targetFile;
int lastDot = fileName.lastIndexOf('.');
String baseName = (lastDot > 0) ? fileName.substring(0, lastDot) : fileName;
String extension = (lastDot > 0) ? fileName.substring(lastDot) : "";
int counter = 1;
while (targetFile.exists()) {
targetFile = new File(dir, baseName + "_" + counter + extension);
counter++;
}
return targetFile;
}
}
功能说明:
- 根据文件扩展名自动分类到不同文件夹
- 支持7大类文件类型(图片、文档、视频、音频、压缩包、代码、可执行)
- 自动处理重名文件(追加序号)
- 使用Files.move()确保跨盘符移动
4. 重命名命令:RenameCommand.java
java
package com.example.filetool.command;
import java.io.File;
public class RenameCommand {
public void execute(String[] args) {
if (args.length < 4) {
System.out.println("用法: rename <目录> <查找内容> <替换内容>");
System.out.println("示例: rename D:/java/FileTool/test IMG Photo");
return;
}
File dir = new File(args[1]);
if (!dir.exists() || !dir.isDirectory()) {
System.out.println("错误: 路径不存在或不是目录");
return;
}
String target = args[2];
String replacement = args[3];
System.out.println("目标目录: " + dir.getAbsolutePath());
System.out.println("查找内容: \"" + target + "\"");
System.out.println("替换为: \"" + replacement + "\"");
System.out.println("--------------------------------");
int successCount = 0;
int failCount = 0;
int skipCount = 0;
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
System.out.println("目录为空");
return;
}
for (File file : files) {
if (file.isFile()) {
String oldName = file.getName();
String newName = oldName.replace(target, replacement);
if (oldName.equals(newName)) {
skipCount++;
continue;
}
File newFile = new File(dir, newName);
if (newFile.exists()) {
System.out.println("✗ " + oldName + " -> 失败(目标文件已存在)");
failCount++;
continue;
}
if (file.renameTo(newFile)) {
System.out.println("✓ " + oldName + " -> " + newName);
successCount++;
} else {
System.out.println("✗ " + oldName + " -> 重命名失败");
failCount++;
}
}
}
System.out.println("--------------------------------");
System.out.println("重命名完成: 成功 " + successCount + " 个,失败 " + failCount + " 个,跳过 " + skipCount + " 个");
}
}
功能说明:
- 使用String.replace()实现字符串替换
- 支持空格、特殊字符的替换
- 检测目标文件是否已存在,避免覆盖
- 统计成功、失败、跳过的文件数量
5. 工具类:SizeFormatter.java
java
package com.example.filetool.util;
public class SizeFormatter {
private static final String[] UNITS = {"B", "KB", "MB", "GB", "TB"};
public static String format(long bytes) {
if (bytes < 0) {
return "0 B";
}
double size = bytes;
int unitIndex = 0;
while (size >= 1024 && unitIndex < UNITS.length - 1) {
size /= 1024;
unitIndex++;
}
if (unitIndex == 0) {
return bytes + " " + UNITS[0];
}
return String.format("%.2f %s", size, UNITS[unitIndex]);
}
}
功能说明:
- 自动将字节数转换为人类可读的格式
- 支持B、KB、MB、GB、TB单位
- 保留两位小数
四、 编译和运行
1. 编译项目
在 D:/java/FileTool 目录下执行:
bash
# Windows
javac -d out src/com/example/filetool/*.java src/com/example/filetool/command/*.java src/com/example/filetool/util/*.java
2. 查看帮助
bash
java -cp out com.example.filetool.FileTool help
输出:
================================
文件批量处理工具 v1.0
================================
用法: java FileTool <command> [args...]
命令:
count <目录> 统计目录下的文件数量和总大小
classify <目录> 按类型分类文件到子文件夹
rename <目录> <查找> <替换> 批量重命名文件
help 显示此帮助
示例:
java -cp out com.example.filetool.FileTool count D:/java/FileTool/test
java -cp out com.example.filetool.FileTool classify D:/java/FileTool/test
java -cp out com.example.filetool.FileTool rename D:/java/FileTool/test " " "_"
五、 功能演示
功能1:统计文件信息
命令:
bash
java -cp out com.example.filetool.FileTool count D:/java/FileTool/test
输出示例:
正在统计: D:\Downloads
--------------------------------
文件数量: 245
子目录数: 18
总大小: 3.68 GB
耗时: 156 ms
适用场景:
- 快速了解文件夹占用空间
- 统计项目代码文件数量
- 清理前评估文件量
功能2:按类型分类文件
命令:
bash
java -cp out com.example.filetool.FileTool classify D:/java/FileTool/test
执行前的文件夹结构:
Downloads/
├── photo1.jpg
├── photo2.png
├── document.pdf
├── report.docx
├── music.mp3
├── video.mp4
└── tool.zip
执行后的文件夹结构:
Downloads/
├── 图片/
│ ├── photo1.jpg
│ └── photo2.png
├── 文档/
│ ├── document.pdf
│ └── report.docx
├── 音频/
│ └── music.mp3
├── 视频/
│ └── video.mp4
└── 压缩包/
└── tool.zip
输出示例:
目标目录: D:\Downloads
开始分类文件...
--------------------------------
✓ photo1.jpg -> 图片/
✓ photo2.png -> 图片/
✓ document.pdf -> 文档/
✓ report.docx -> 文档/
✓ music.mp3 -> 音频/
✓ video.mp4 -> 视频/
✓ tool.zip -> 压缩包/
--------------------------------
分类完成: 成功 7 个,失败 0 个
各类文件数量:
图片: 2 个
文档: 2 个
音频: 1 个
视频: 1 个
压缩包: 1 个
适用场景:
- 整理混乱的下载文件夹
- 按类型归档文件
- 快速分类大量文件
功能3:批量重命名文件
场景1:替换空格为下划线
命令:
bash
java -cp out com.example.filetool.FileTool rename D:/java/FileTool/photos " " "_"
执行前:
my photo 1.jpg
my photo 2.jpg
travel photo 2024.jpg
执行后:
my_photo_1.jpg
my_photo_2.jpg
travel_photo_2024.jpg
输出示例:
目标目录: D:\Photos
查找内容: " "
替换为: "_"
--------------------------------
✓ my photo 1.jpg -> my_photo_1.jpg
✓ my photo 2.jpg -> my_photo_2.jpg
✓ travel photo 2024.jpg -> travel_photo_2024.jpg
--------------------------------
重命名完成: 成功 3 个,失败 0 个,跳过 0 个
场景2:统一文件前缀
命令:
bash
java -cp out com.example.filetool.FileTool rename D:/java/FileTool/photos IMG Photo
执行前:
IMG_001.jpg
IMG_002.jpg
IMG_003.jpg
执行后:
Photo_001.jpg
Photo_002.jpg
Photo_003.jpg
适用场景:
- 统一命名规范
- 替换特殊字符
- 批量修改文件前缀/后缀
六、 核心知识点
1. File类的常用方法
java
File file = new File("D:/test");
// 判断方法
file.exists() // 文件或目录是否存在
file.isFile() // 是否为文件
file.isDirectory() // 是否为目录
file.canRead() // 是否可读
file.canWrite() // 是否可写
// 信息获取
file.getName() // 获取文件名
file.getAbsolutePath() // 获取绝对路径
file.length() // 获取文件大小(字节)
file.lastModified() // 最后修改时间
// 目录操作
file.listFiles() // 列出所有文件和子目录
file.mkdir() // 创建单层目录
file.mkdirs() // 创建多层目录
// 文件操作
file.renameTo(newFile) // 重命名/移动文件
file.delete() // 删除文件
2. 递归遍历文件树
递归算法的三要素:
- 终止条件 :
children == null(无子文件) - 递归调用 :遇到目录时调用
walk(child) - 状态累加:累加文件数量、大小等
执行流程示例:
目录结构:
D:/test/
├── file1.txt (100 B)
├── file2.txt (200 B)
└── subfolder/
├── file3.txt (300 B)
└── file4.txt (400 B)
递归过程:
walk(D:/test)
├─ 处理 file1.txt: totalFiles++, totalSize += 100
├─ 处理 file2.txt: totalFiles++, totalSize += 200
└─ 处理 subfolder: totalDirs++, 递归调用 walk(subfolder)
walk(D:/test/subfolder)
├─ 处理 file3.txt: totalFiles++, totalSize += 300
└─ 处理 file4.txt: totalFiles++, totalSize += 400
结果:
文件数量: 4
子目录数: 1
总大小: 1000 B
3. 文件移动的两种方式
方式1:使用File.renameTo()(仅限同盘符)
java
File oldFile = new File("D:/test.txt");
File newFile = new File("D:/backup/test.txt");
boolean success = oldFile.renameTo(newFile);
优点 :简单直接
缺点:只能在同一盘符内移动(Windows)
方式2:使用Files.move()(推荐)
java
Files.move(
oldFile.toPath(),
newFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
优点:
- 支持跨盘符移动
- 更强大的选项(覆盖、原子操作等)
- 抛出明确的异常信息
4. 字符串替换技巧
java
// 简单替换
String newName = oldName.replace(" ", "_");
// 替换所有匹配
String newName = oldName.replaceAll("\\s+", "_"); // 替换所有空白字符
// 正则替换(高级)
String newName = oldName.replaceAll("[^a-zA-Z0-9.]", "_"); // 保留字母数字和点
5. 处理重名文件
java
private File handleDuplicateFileName(File dir, String fileName) {
File targetFile = new File(dir, fileName);
// 如果不存在,直接返回
if (!targetFile.exists()) {
return targetFile;
}
// 分离文件名和扩展名
int lastDot = fileName.lastIndexOf('.');
String baseName = (lastDot > 0) ? fileName.substring(0, lastDot) : fileName;
String extension = (lastDot > 0) ? fileName.substring(lastDot) : "";
// 追加序号直到找到不重名的文件名
int counter = 1;
while (targetFile.exists()) {
targetFile = new File(dir, baseName + "_" + counter + extension);
counter++;
}
return targetFile;
}
示例:
- 如果
test.txt已存在,则创建test_1.txt - 如果
test_1.txt也存在,则创建test_2.txt - 以此类推
6. 异常处理最佳实践
java
// 1. 参数校验
if (args.length < 2) {
System.out.println("用法提示");
return;
}
// 2. 路径验证
if (!target.exists()) {
System.out.println("路径不存在");
return;
}
// 3. 空值检查
if (files == null) {
return; // 防止NullPointerException
}
// 4. IO异常捕获
try {
Files.move(source, target);
} catch (IOException e) {
System.out.println("操作失败: " + e.getMessage());
}
七、 扩展
扩展1:支持正则表达式重命名
java
import java.util.regex.Pattern;
public class RegexRenameCommand {
public void execute(String[] args) {
Pattern pattern = Pattern.compile(args[2]);
for (File file : files) {
String oldName = file.getName();
String newName = pattern.matcher(oldName).replaceAll(args[3]);
// ... 重命名逻辑
}
}
}
使用示例:
bash
# 删除文件名中的所有数字
java FileTool regex-rename D:/java/FileTool/photos "\\d+" ""
# 将多个空格替换为单个下划线
java FileTool regex-rename D:/java/FileTool/photos "\\s+" "_"
扩展2:添加文件类型统计
java
private Map<String, Integer> fileTypeCount = new HashMap<>();
private void walk(File dir) {
// ... 原有代码 ...
if (child.isFile()) {
String extension = getExtension(child.getName());
fileTypeCount.put(extension, fileTypeCount.getOrDefault(extension, 0) + 1);
}
}
// 输出统计
System.out.println("\n文件类型分布:");
fileTypeCount.entrySet().stream()
.sorted((a, b) -> b.getValue().compareTo(a.getValue()))
.forEach(e -> System.out.println(" " + e.getKey() + ": " + e.getValue() + " 个"));
扩展3:支持递归分类(包含子文件夹)
java
private void classifyRecursive(File dir) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
classifyRecursive(file); // 递归处理子文件夹
} else {
// 分类逻辑
}
}
}
扩展4:添加配置文件支持
创建 config.properties:
properties
# 自定义文件分类规则
category.image=.jpg,.png,.gif,.bmp
category.document=.txt,.pdf,.doc,.docx
category.video=.mp4,.avi,.mkv
读取配置:
java
Properties config = new Properties();
try (InputStream in = new FileInputStream("config.properties")) {
config.load(in);
String images = config.getProperty("category.image");
// 解析并使用
}
扩展5:添加撤销功能
java
// 记录操作历史
class Operation {
String type; // rename, move
File oldPath;
File newPath;
}
List<Operation> history = new ArrayList<>();
// 执行操作时记录
history.add(new Operation("move", oldFile, newFile));
// 撤销最后一次操作
public void undo() {
if (history.isEmpty()) return;
Operation op = history.remove(history.size() - 1);
Files.move(op.newPath.toPath(), op.oldPath.toPath());
}
八、 常见问题与解决
问题1:中文路径乱码
现象:文件路径或文件名包含中文时显示乱码
解决方案:
bash
# 编译时指定UTF-8
javac -encoding UTF-8 -d out src/com/example/filetool/*.java ...
# 运行时指定编码
java -Dfile.encoding=UTF-8 -cp out com.example.filetool.FileTool count D:/测试
问题2:权限不足导致失败
现象 :访问系统目录时 listFiles() 返回 null
解决方案:代码中已经处理
java
File[] children = dir.listFiles();
if (children == null) {
return; // 自动跳过无权限的目录
}
进一步优化:添加警告提示
java
if (children == null) {
System.err.println("警告: 无权限访问 " + dir.getAbsolutePath());
return;
}
问题3:大文件夹统计很慢
原因:递归遍历是IO密集型操作
优化方案1:使用Java 8 Stream API
java
import java.nio.file.*;
long fileCount = Files.walk(Paths.get(path))
.filter(Files::isRegularFile)
.count();
优化方案2:显示进度
java
private int processed = 0;
private void walk(File dir) {
// ...
processed++;
if (processed % 100 == 0) {
System.out.print("\r已处理: " + processed);
}
}
问题4:特殊字符导致重命名失败
Windows禁止的文件名字符 :< > : " / \ | ? *
解决方案:添加字符验证
java
private boolean isValidFileName(String name) {
return !name.matches(".*[<>:\"/\\|?*].*");
}
if (!isValidFileName(newName)) {
System.out.println("错误: 文件名包含非法字符");
return;
}
九、 技术总结
通过该完整项目,可以掌握:
√ File I/O操作 :文件判断、信息获取、移动、重命名
√ 递归算法 :遍历文件树结构
√ 字符串处理 :替换、截取、正则表达式
√ 集合框架 :HashMap存储分类规则和统计数据
√ 异常处理 :参数校验、空值检查、IO异常捕获
√ 命令行参数 :解析args实现灵活调用
√ 设计模式 :命令模式,代码结构清晰
√ 工具类封装:SizeFormatter提高代码复用性
十、 实战应用场景
场景1:清理下载文件夹
bash
# 1. 先统计一下
java -cp out com.example.filetool.FileTool count D:/java/FileTool/test
# 2. 按类型分类
java -cp out com.example.filetool.FileTool classify D:/java/FileTool/test
# 3. 重命名去除空格
java -cp out com.example.filetool.FileTool rename D:/java/FileTool/test " " "_"
场景2:整理照片库
bash
# 统一命名:IMG -> Photo
java -cp out com.example.filetool.FileTool rename D:/java/FileTool/photos IMG Photo
# 分类整理
java -cp out com.example.filetool.FileTool classify D:/java/FileTool/photos
场景3:项目文件统计
bash
# 统计项目代码量
java -cp out com.example.filetool.FileTool count D:/java/my-project
十一、 完整代码
项目地址:D:/java/FileTool/ 目录
最终目录结构:
D:/java/FileTool/
├── src/
│ └── com/example/filetool/
│ ├── FileTool.java # 主入口
│ ├── command/
│ │ ├── CountCommand.java # 统计功能
│ │ ├── ClassifyCommand.java # 分类功能
│ │ └── RenameCommand.java # 重命名功能
│ └── util/
│ └── SizeFormatter.java # 工具类
└── out/ # 编译输出