一、一句话总结
Commons IO = Java IO操作的瑞士军刀,提供工具类简化文件操作,避免手写样板代码
二、核心模块
1. 主要工具类
commons-io
├── IOUtils - 流操作工具(核心)
├── FileUtils - 文件操作工具(最常用)
├── FilenameUtils - 文件名工具
├── FileSystemUtils - 文件系统工具
└── 其他工具类
三、IOUtils(流操作)
1. 复制流(最常用)
// ✅ 一行代码复制流
try (InputStream input = new FileInputStream("source.txt");
OutputStream output = new FileOutputStream("target.txt")) {
// 传统方式需要循环读取写入
// Commons IO一行搞定
IOUtils.copy(input, output);
// 指定缓冲区大小
IOUtils.copy(input, output, 8192); // 8KB缓冲区
// 复制到字符串
String content = IOUtils.toString(input, StandardCharsets.UTF_8);
// 从字符串复制
IOUtils.write("Hello World", output, StandardCharsets.UTF_8);
}
2. 读写全部内容
// 读取所有字节
byte[] bytes = IOUtils.toByteArray(inputStream);
// 读取所有行
List<String> lines = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
// 读取到字符串
String content = IOUtils.toString(inputStream, "UTF-8");
// 写入全部
IOUtils.write("content", outputStream, "UTF-8");
IOUtils.writeLines(lines, System.lineSeparator(), outputStream, "UTF-8");
3. 关闭流(更安全)
// 静默关闭(不抛异常)
IOUtils.closeQuietly(inputStream); // 已过时,用try-with-resources
IOUtils.closeQuietly(outputStream);
// 批量关闭
IOUtils.closeQuietly(input, output, reader, writer);
// 新方式:closeQuietly已被标记为过时
// 应该使用try-with-resources
四、FileUtils(文件操作)
1. 文件复制
// 复制文件
File srcFile = new File("source.txt");
File destFile = new File("target.txt");
FileUtils.copyFile(srcFile, destFile);
// 复制目录
File srcDir = new File("/source");
File destDir = new File("/backup");
FileUtils.copyDirectory(srcDir, destDir);
// 带过滤器复制
FileFilter filter = file -> file.getName().endsWith(".java");
FileUtils.copyDirectory(srcDir, destDir, filter);
// 复制URL到文件
URL url = new URL("http://example.com/file.txt");
FileUtils.copyURLToFile(url, destFile);
2. 文件读写
// 读取文件内容
String content = FileUtils.readFileToString(file, "UTF-8");
List<String> lines = FileUtils.readLines(file, "UTF-8");
byte[] bytes = FileUtils.readFileToByteArray(file);
// 写入文件
FileUtils.writeStringToFile(file, "content", "UTF-8");
FileUtils.writeLines(file, lines); // 写入集合
FileUtils.writeByteArrayToFile(file, bytes);
// 追加内容
FileUtils.writeStringToFile(file, "追加内容", "UTF-8", true);
3. 文件和目录操作
// 创建目录(包括父目录)
FileUtils.forceMkdir(dir);
// 强制删除(包括只读文件)
FileUtils.forceDelete(file);
FileUtils.deleteDirectory(dir); // 删除目录及内容
// 清理目录(删除内容但不删除目录)
FileUtils.cleanDirectory(dir);
// 移动/重命名
FileUtils.moveFile(srcFile, destFile);
FileUtils.moveDirectory(srcDir, destDir);
FileUtils.moveFileToDirectory(srcFile, destDir, true);
// 获取目录大小
long size = FileUtils.sizeOfDirectory(dir); // 字节数
String sizeDesc = FileUtils.byteCountToDisplaySize(size); // "1.5 MB"
五、FilenameUtils(文件名处理)
1. 路径操作
// 规范化路径
String normalized = FilenameUtils.normalize("/a/b/../c/./d.txt");
// 结果: "/a/c/d.txt"
// 获取各部分
String baseName = FilenameUtils.getBaseName("/path/to/file.txt"); // "file"
String extension = FilenameUtils.getExtension("/path/to/file.txt"); // "txt"
String name = FilenameUtils.getName("/path/to/file.txt"); // "file.txt"
String path = FilenameUtils.getPath("/path/to/file.txt"); // "/path/to/"
// 连接路径
String fullPath = FilenameUtils.concat("/home/user", "docs/file.txt");
// 结果: "/home/user/docs/file.txt"(自动处理分隔符)
2. 文件名检查
// 扩展名检查
boolean isTxt = FilenameUtils.isExtension("file.txt", "txt");
boolean isImage = FilenameUtils.isExtension("photo.jpg",
new String[]{"jpg", "png", "gif"});
// 系统适配
String separatorsToUnix = FilenameUtils.separatorsToUnix("C:\\Windows\\file.txt");
// 结果: "C:/Windows/file.txt"
String separatorsToWindows = FilenameUtils.separatorsToWindows("/usr/local/file");
// 结果: "\\usr\\local\\file"
六、实际应用示例
1. 文件上传处理
public class FileUploadService {
public void saveUploadedFile(InputStream uploadedStream,
String filename,
String uploadDir) throws IOException {
File uploadDirectory = new File(uploadDir);
// 确保目录存在
FileUtils.forceMkdir(uploadDirectory);
// 安全文件名处理
String safeFilename = FilenameUtils.getName(filename); // 防止路径遍历攻击
File targetFile = new File(uploadDirectory, safeFilename);
// 保存文件
try (OutputStream out = new FileOutputStream(targetFile)) {
IOUtils.copy(uploadedStream, out);
}
// 日志记录
long fileSize = FileUtils.sizeOf(targetFile);
log.info("文件已保存: {}, 大小: {}", targetFile.getAbsolutePath(),
FileUtils.byteCountToDisplaySize(fileSize));
}
}
2. 配置文件管理
public class ConfigManager {
public Properties loadConfig(String configPath) throws IOException {
File configFile = new File(configPath);
if (!configFile.exists()) {
// 创建默认配置
Properties defaults = createDefaultConfig();
saveConfig(configFile, defaults);
return defaults;
}
// 读取配置文件
String content = FileUtils.readFileToString(configFile, "UTF-8");
Properties props = new Properties();
try (StringReader reader = new StringReader(content)) {
props.load(reader);
}
return props;
}
public void saveConfig(File configFile, Properties props) throws IOException {
StringWriter writer = new StringWriter();
props.store(writer, "Application Configuration");
FileUtils.writeStringToFile(configFile, writer.toString(), "UTF-8");
}
}
3. 日志文件轮转
public class LogRotator {
public void rotateLogs(File logDir, int maxBackups) throws IOException {
// 获取所有日志文件
File[] logFiles = logDir.listFiles((dir, name) ->
name.startsWith("app.log"));
if (logFiles == null) return;
// 按修改时间排序
Arrays.sort(logFiles, Comparator.comparingLong(File::lastModified).reversed());
// 保留最新maxBackups个文件
for (int i = maxBackups; i < logFiles.length; i++) {
FileUtils.forceDelete(logFiles[i]);
}
// 重命名现有文件
for (int i = Math.min(maxBackups - 1, logFiles.length - 1); i > 0; i--) {
File oldFile = logFiles[i];
File newFile = new File(logDir,
FilenameUtils.getBaseName(oldFile.getName()) + "." + (i + 1) +
"." + FilenameUtils.getExtension(oldFile.getName()));
FileUtils.moveFile(oldFile, newFile);
}
// 清空当前日志
File currentLog = new File(logDir, "app.log");
if (currentLog.exists()) {
FileUtils.write(currentLog, "", "UTF-8");
}
}
}
七、与其他框架集成
1. Spring Boot中使用
@Service
public class DocumentService {
@Value("${app.documents.directory}")
private String docsDir;
public void processDocuments() throws IOException {
File directory = new File(docsDir);
// 确保目录存在
FileUtils.forceMkdir(directory);
// 查找所有PDF文件
File[] pdfFiles = directory.listFiles((dir, name) ->
FilenameUtils.isExtension(name, "pdf"));
if (pdfFiles != null) {
for (File pdfFile : pdfFiles) {
processPdfFile(pdfFile);
}
}
}
private void processPdfFile(File pdfFile) throws IOException {
// 备份原文件
File backupDir = new File(docsDir, "backup");
FileUtils.forceMkdir(backupDir);
String timestamp = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String backupName = FilenameUtils.getBaseName(pdfFile.getName()) +
"_" + timestamp + ".pdf";
File backupFile = new File(backupDir, backupName);
FileUtils.copyFile(pdfFile, backupFile);
// 处理文件...
}
}
2. 与Apache Commons Compress结合
public class ArchiveService {
public void extractZip(File zipFile, File destDir) throws IOException {
try (ZipArchiveInputStream zis = new ZipArchiveInputStream(
new BufferedInputStream(new FileInputStream(zipFile)))) {
ArchiveEntry entry;
while ((entry = zis.getNextEntry()) != null) {
File entryFile = new File(destDir, entry.getName());
if (entry.isDirectory()) {
FileUtils.forceMkdir(entryFile);
} else {
FileUtils.forceMkdir(entryFile.getParentFile());
try (OutputStream os = new BufferedOutputStream(
new FileOutputStream(entryFile))) {
IOUtils.copy(zis, os);
}
}
}
}
}
}
八、性能优化技巧
1. 使用合适的缓冲区大小
public class OptimizedFileProcessor {
// 默认缓冲区大小(可配置)
private static final int BUFFER_SIZE = 8192; // 8KB
public void processLargeFile(File input, File output) throws IOException {
try (InputStream in = new BufferedInputStream(
new FileInputStream(input), BUFFER_SIZE);
OutputStream out = new BufferedOutputStream(
new FileOutputStream(output), BUFFER_SIZE)) {
// 使用自定义缓冲区大小
IOUtils.copy(in, out, BUFFER_SIZE);
}
}
public String readLargeFile(File file) throws IOException {
// 对于大文件,使用StringBuilder避免多次扩容
try (InputStream in = new FileInputStream(file);
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
StringBuilder sb = new StringBuilder((int) file.length());
char[] buffer = new char[BUFFER_SIZE];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
sb.append(buffer, 0, charsRead);
}
return sb.toString();
}
}
}
2. 并行处理多个文件
public class ParallelFileProcessor {
public void processFilesInParallel(List<File> files, File outputDir) {
files.parallelStream().forEach(file -> {
try {
processSingleFile(file, outputDir);
} catch (IOException e) {
log.error("处理文件失败: {}", file.getName(), e);
}
});
}
private void processSingleFile(File inputFile, File outputDir) throws IOException {
String outputFilename = FilenameUtils.getBaseName(inputFile.getName()) +
"_processed.txt";
File outputFile = new File(outputDir, outputFilename);
// 读取内容
String content = FileUtils.readFileToString(inputFile, "UTF-8");
// 处理内容
String processed = content.toUpperCase();
// 写入文件
FileUtils.writeStringToFile(outputFile, processed, "UTF-8");
log.debug("已处理: {}", inputFile.getName());
}
}
九、常见陷阱和解决方案
1. 内存溢出问题
// ❌ 错误:大文件一次性读入内存
byte[] hugeFile = IOUtils.toByteArray(new FileInputStream("10GB文件.bin"));
// ✅ 正确:流式处理大文件
try (InputStream in = new FileInputStream("10GB文件.bin");
OutputStream out = new FileOutputStream("output.bin")) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// 处理每个块
processChunk(buffer, bytesRead);
out.write(buffer, 0, bytesRead);
}
}
// ✅ 正确:使用Apache Commons IO的copyLarge处理大文件
try (InputStream in = new FileInputStream("largefile.bin");
OutputStream out = new FileOutputStream("output.bin")) {
IOUtils.copyLarge(in, out); // 内部使用更大的缓冲区
}
2. 路径遍历安全
// ❌ 不安全:直接使用用户输入的文件名
String userInput = "../../etc/passwd"; // 恶意输入
File file = new File(baseDir, userInput); // 可能访问系统文件
// ✅ 安全:使用FilenameUtils过滤
String safeName = FilenameUtils.getName(userInput); // 只取文件名部分
File safeFile = new File(baseDir, safeName); // 安全
// ✅ 额外检查
if (!safeFile.getCanonicalPath().startsWith(baseDir.getCanonicalPath())) {
throw new SecurityException("路径遍历攻击检测");
}
3. 文件锁和并发访问
public class ConcurrentFileAccess {
private final Lock fileLock = new ReentrantLock();
public void appendToLogFile(String message) throws IOException {
fileLock.lock();
try {
File logFile = new File("app.log");
// 使用FileUtils的追加模式
FileUtils.writeStringToFile(logFile,
LocalDateTime.now() + ": " + message + "\n",
"UTF-8", true); // true表示追加
} finally {
fileLock.unlock();
}
}
public synchronized void safeFileOperation(File file) throws IOException {
// 使用synchronized确保同一时间只有一个线程操作文件
String content = FileUtils.readFileToString(file, "UTF-8");
// 处理内容
FileUtils.writeStringToFile(file, content, "UTF-8");
}
}
十、最佳实践总结
1. 导入和依赖
<!-- Maven依赖 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version> <!-- 使用最新版本 -->
</dependency>
2. 编码规范
// 1. 总是指定字符编码
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);
// 2. 使用try-with-resources管理流
try (InputStream in = new FileInputStream(file)) {
byte[] data = IOUtils.toByteArray(in);
}
// 3. 处理可能为null的情况
FileUtils.deleteQuietly(file); // 如果文件不存在也不抛异常
// 4. 使用常量定义缓冲区大小
private static final int BUFFER_SIZE = 8192;
IOUtils.copy(input, output, BUFFER_SIZE);
3. 错误处理
public void safeFileOperation(File file) {
try {
// 使用Commons IO的方法
if (FileUtils.directoryContains(parentDir, file)) {
String content = FileUtils.readFileToString(file, "UTF-8");
// 处理内容
}
} catch (IOException e) {
// 转换为业务异常
throw new BusinessException("文件操作失败: " + file.getName(), e);
} catch (IllegalArgumentException e) {
// 处理无效参数
log.warn("无效文件参数: {}", file.getPath());
throw new ValidationException("文件路径无效", e);
}
}
十一、替代方案对比
| 场景 | Commons IO | Java NIO.2 | 传统Java IO |
|---|---|---|---|
| 文件复制 | FileUtils.copyFile() | Files.copy() | 需要手写循环 |
| 读取所有行 | FileUtils.readLines() | Files.readAllLines() | 需要BufferReader |
| 目录操作 | FileUtils.cleanDirectory() | 需要手写递归 | 复杂 |
| 文件名处理 | FilenameUtils.normalize() | Path.normalize() | 手动处理 |
| 流操作 | IOUtils.copy() | Files.copy() | 手写缓冲区循环 |
推荐使用:
- 简单操作:直接用
Files类(Java 7+) - 复杂场景:使用 Commons IO 的工具方法
- 极高性能:使用 NIO 的
FileChannel.transferTo()
总结要点
核心价值:
- 减少样板代码:一行代码完成常见IO操作
- 提高安全性:内置路径遍历防护
- 统一错误处理:一致的异常处理方式
- 跨平台兼容:自动处理路径分隔符
必学方法:
// 1. 文件读写
FileUtils.readFileToString()
FileUtils.writeStringToFile()
// 2. 流操作
IOUtils.copy()
IOUtils.toByteArray()
IOUtils.toString()
// 3. 文件操作
FileUtils.copyFile()
FileUtils.deleteDirectory()
FileUtils.forceMkdir()
// 4. 文件名处理
FilenameUtils.normalize()
FilenameUtils.getExtension()
FilenameUtils.concat()
记住:Commons IO 不是要替代 Java IO,而是要让它更好用!