Hutool是一个强大的Java工具库,其文件IO工具类集简化了文件处理的复杂性。本文将全面介绍FileUtil的核心功能,从基础操作到高级应用,帮助开发者轻松实现文件管理、压缩打包和Web下载等功能。
1. Hutool文件IO工具类概览
Hutool提供了一套完整的文件IO处理体系,主要包含三个核心工具类,每个类都有其专门的适用场景。
-
FileUtil:作为基础文件操作工具类,它封装了文件创建、删除、复制、移动和路径处理等常用功能。与Java原生的Files和Path类相比,FileUtil的API更加简洁直观,无需编写冗长的try-catch代码块,大大提高了开发效率。
-
FileReader:专注于文件读取操作,提供了多种读取方式(整体读取、逐行读取、流式读取),并能自动处理字符编码问题。对于大文件处理,FileReader提供了流式读取方式,避免内存溢出的风险。
-
FileWriter:负责文件写入操作,支持覆盖写入、追加写入等多种模式,同时保证线程安全性。其简洁的API让文件写入变得简单而可靠。
上述三个工具类相互配合,构成了Hutool强大的文件处理能力,下面我们将深入探讨它们的具体使用方法。
2. 环境配置与基础文件操作
2.1 引入Hutool依赖
要使用Hutool的文件操作功能,首先需要在项目中添加Hutool依赖。对于Maven项目,请在pom.xml中添加以下配置:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
建议使用最新版本以获得最佳性能和功能支持。如需处理Web相关功能,可额外引入hutool-extra模块。
2.2 基础文件操作示例
FileUtil提供了一系列静态方法,用于处理基础文件操作。以下是常用操作的代码示例:
import cn.hutool.core.io.FileUtil;
import java.io.File;
// 创建文件(自动创建父目录)
File file = FileUtil.touch("/path/to/file.txt");
// 创建目录(支持多级目录创建)
File dir = FileUtil.mkdir("/path/to/directory");
// 检查文件是否存在
boolean exists = FileUtil.exist("/path/to/file");
// 删除文件或目录(递归删除)
FileUtil.del("/path/to/fileOrDir");
// 复制文件或目录
FileUtil.copy("source.txt", "destination.txt", true); // 第三个参数表示是否覆盖
// 移动文件或重命名
FileUtil.move(new File("oldName.txt"), new File("newName.txt"), true);
// 获取文件信息
long size = FileUtil.size(file); // 文件大小
Date lastModified = FileUtil.lastModifiedTime(file); // 最后修改时间
路径处理是文件操作中的关键环节,FileUtil提供了安全的路径构建方法:
// 安全构建文件路径(自动处理路径分隔符和Slip漏洞)
File configFile = FileUtil.file("/config", "app", "settings.properties");
// 路径规范化
String normalizedPath = FileUtil.normalize("/path/../to/./file.txt"); // 结果为"/to/file.txt"
// 获取临时目录和用户主目录
File tempDir = FileUtil.getTmpDir();
File userHome = FileUtil.getUserHomeDir();
2.3 文件查询与遍历
对于目录内容的查询,FileUtil提供了丰富的方法:
// 列出目录下的直接文件
File[] files = FileUtil.ls("/path/to/dir");
// 递归遍历目录中的所有文件
List<File> allFiles = FileUtil.loopFiles("/path/to/dir");
// 带过滤条件的文件遍历(如查找所有Java文件)
List<File> javaFiles = FileUtil.loopFiles("/src",
file -> file.getName().endsWith(".java"));
// 检查目录是否为空
boolean isEmpty = FileUtil.isEmpty(new File("/path/to/dir"));
boolean isDirEmpty = FileUtil.isDirEmpty(new File("/path/to/dir"));
以上基础操作为文件处理奠定了坚实基础,接下来我们将深入探讨文件的读取和写入操作。
3. 文件读取操作详解
3.1 使用FileReader读取文件
FileReader类提供了多种文件读取方式,适应不同场景需求。
基础读取方法:
// 创建FileReader实例(可指定编码,默认UTF-8)
FileReader reader = FileReader.create("file.txt", CharsetUtil.UTF_8);
// 读取整个文件内容为字符串
String content = reader.readString();
// 按行读取文件到列表
List<String> lines = reader.readLines();
// 读取为字节数组
byte[] bytes = reader.readBytes();
// 使用自定义集合接收行数据
Set<String> uniqueLines = reader.readLines(new LinkedHashSet<>());
文件编码处理是文件读取中的常见问题。Hutool通过CharsetUtil自动处理编码问题,避免乱码情况。如果未指定编码,FileReader会自动探测文件编码,大大简化了操作。
3.2 大文件处理策略
对于大文件,一次性加载到内存可能导致OOM错误,Hutool提供了流式处理方式:
// 逐行处理大文件(内存友好)
FileReader.create("largefile.txt").readLines(new LineHandler() {
@Override
public void handle(String line) {
// 处理每一行,无需将整个文件加载到内存
processLine(line);
}
});
// 使用Reader处理器进行自定义流式处理
String result = FileReader.create("largefile.txt").read(new FileReader.ReaderHandler<String>() {
@Override
public String handle(BufferedReader reader) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("ERROR")) { // 过滤错误日志
sb.append(line).append("\n");
}
}
return sb.toString();
}
});
// 使用BufferedReader进行更精细的控制
try (BufferedReader br = FileReader.create("largefile.txt").getReader()) {
String line;
while ((line = br.readLine()) != null) {
// 处理每一行
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
3.3 文件类型特定处理
对于特定格式的文件,Hutool提供了更加便捷的处理方式:
// 读取Properties文件(兼容多种格式)
Props props = new Props("config.properties");
// 读取JSON文件
JSONObject json = JSONUtil.readJSONObject("data.json", CharsetUtil.UTF_8);
// 读取XML文件
String xmlContent = FileReader.create("data.xml").readString();
以上文件读取方式灵活且功能强大,接下来我们将探讨文件写入的相关操作。
4. 文件写入操作详解
4.1 使用FileWriter写入文件
FileWriter提供了完整的文件写入功能,支持多种写入模式和数据类型。
基础写入操作:
// 创建FileWriter实例
FileWriter writer = FileWriter.create("output.txt", CharsetUtil.UTF_8);
// 覆盖写入内容
writer.write("Hello, World!");
// 追加内容到文件末尾
writer.append("Additional content");
// 写入字节数据
byte[] data = "Binary data".getBytes();
writer.write(data, 0, data.length);
批量数据写入是FileWriter的强项,特别适合日志记录、数据导出等场景:
// 写入字符串列表(每行一个元素)
List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
writer.writeLines(lines);
// 追加写入列表
writer.appendLines(lines);
// 写入Map数据(键值对格式)
Map<String, String> config = new HashMap<>();
config.put("host", "localhost");
config.put("port", "8080");
writer.writeMap(config, "=", true); // 第三个参数控制是否同步写入
// 控制换行符(确保跨平台兼容性)
writer.writeLines(lines, LineSeparator.WINDOWS, false, true);
4.2 流式写入与性能优化
对于大批量数据写入,使用流式操作可以显著提升性能:
// 从输入流写入文件
try (InputStream inputStream = new FileInputStream("source.txt")) {
writer.writeFromStream(inputStream);
}
// 使用缓冲输出流提高写入性能
try (BufferedOutputStream outputStream = writer.getOutputStream()) {
for (int i = 0; i < 10000; i++) {
String data = "Data line " + i + "\n";
outputStream.write(data.getBytes());
}
}
// 使用BufferedWriter进行文本写入
try (BufferedWriter bufferedWriter = writer.getWriter(true)) {
for (int i = 0; i < 10000; i++) {
bufferedWriter.write("Data line " + i);
bufferedWriter.newLine(); // 跨平台换行
}
}
4.3 文件追加与内容修改
实际应用中,经常需要向现有文件追加内容或修改部分内容:
// 追加模式写入
FileWriter appendWriter = FileWriter.create("log.txt", CharsetUtil.UTF_8, true);
appendWriter.append("New log entry\n");
// 文件内容替换
String content = FileReader.create("config.txt").readString();
String newContent = content.replace("oldValue", "newValue");
FileWriter.create("config.txt").write(newContent);
掌握了文件读写操作后,我们将进入本教程的核心内容:文件压缩与下载实战。
5. 文件压缩与下载实战
5.1 使用ZipUtil进行文件压缩
Hutool的ZipUtil类提供了简洁而强大的文件压缩功能,支持文件和目录的压缩。
基础压缩操作:
import cn.hutool.core.util.ZipUtil;
import java.io.File;
// 压缩单个文件
File file = new File("test.txt");
File zipFile = ZipUtil.zip("test.zip", file);
// 压缩目录
File dir = new File("/path/to/directory");
File zipDir = ZipUtil.zip("directory.zip", dir);
// 添加文件到已存在的压缩包
ZipUtil.addEntry("existing.zip", "newfile.txt", new File("newfile.txt"));
// 解压缩文件
File unzipDir = ZipUtil.unzip("compressed.zip", "destDir");
多文件压缩与目录结构保持:
在实际项目中,往往需要将多个文件压缩为一个包,并保持原有的目录结构。
public class ZipCompressionService {
/**
* 压缩多个文件并保持目录结构
*/
public static void compressWithStructure(List<File> files, String outputZipPath) {
// 使用ZipOutputStream进行精细控制
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputZipPath))) {
for (File file : files) {
// 添加文件到ZIP,自定义文件在ZIP中的路径
ZipUtil.putEntry(zos, file, "myprefix/" + file.getName(), null);
}
} catch (IOException e) {
throw new IORuntimeException("压缩失败", e);
}
}
/**
* 递归压缩目录
*/
public static void compressFolder(String sourceDir, String zipFilePath) {
File sourceFile = new File(sourceDir);
if (!sourceFile.exists()) {
throw new RuntimeException("源目录不存在");
}
// 使用Hutool的压缩方法
ZipUtil.zip(zipFilePath, sourceFile);
}
}
5.2 Web应用中的文件下载实现
在Web应用中,提供文件下载功能是常见需求。以下是如何结合Hutool和Servlet API实现文件下载的完整示例。
基础下载实现:
@RestController
@RequestMapping("/api/download")
public class FileDownloadController {
@GetMapping("/file")
public void downloadFile(HttpServletResponse response) {
try {
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=\"download.txt\"");
response.setCharacterEncoding("UTF-8");
// 使用Hutool将文件写入响应流
File file = new File("path/to/file.txt");
ServletUtil.write(response, file);
} catch (Exception e) {
throw new RuntimeException("下载失败", e);
}
}
}
压缩包下载功能:
以下是一个完整的打包下载实现,支持目录结构保持:
@RestController
public class ZipDownloadController {
@Autowired
private String fileStoragePath; // 文件存储根路径
@GetMapping("/downloadZip")
public void downloadZip(@RequestParam String[] filePaths,
HttpServletResponse response) {
// 创建临时压缩文件
String tempZipPath = FileUtil.getTmpDirPath() + File.separator +
System.currentTimeMillis() + ".zip";
File tempZipFile = new File(tempZipPath);
try {
// 收集需要下载的文件
List<File> filesToZip = new ArrayList<>();
for (String filePath : filePaths) {
File file = new File(fileStoragePath, filePath);
if (file.exists()) {
filesToZip.add(file);
}
}
// 创建ZIP文件
ZipUtil.zip(tempZipFile, true, filesToZip.toArray(new File[0]));
// 设置下载响应头
String encodedFileName = URLEncoder.encode("download.zip", "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
response.setContentType("application/zip");
response.setHeader("Content-Length", String.valueOf(tempZipFile.length()));
// 写入响应流
FileUtil.writeToStream(tempZipFile, response.getOutputStream());
} catch (Exception e) {
throw new RuntimeException("压缩下载失败", e);
} finally {
// 删除临时压缩文件
if (tempZipFile.exists()) {
FileUtil.del(tempZipFile);
}
}
}
}
5.3 大文件压缩与下载优化
处理大文件时,需要采用特殊策略避免内存溢出:
public class LargeFileCompressor {
/**
* 分块压缩大文件
*/
public static void compressLargeFile(String sourcePath, String zipPath, int chunkSize) {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipPath))) {
File sourceFile = new File(sourcePath);
FileInputStream fis = new FileInputStream(sourceFile);
byte[] buffer = new byte[chunkSize];
int bytesRead;
int partCounter = 0;
while ((bytesRead = fis.read(buffer)) > 0) {
String entryName = sourceFile.getName() + ".part" + partCounter++;
zos.putNextEntry(new ZipEntry(entryName));
zos.write(buffer, 0, bytesRead);
zos.closeEntry();
}
} catch (IOException e) {
throw new IORuntimeException("大文件压缩失败", e);
}
}
/**
* 流式压缩,避免内存溢出
*/
public static void streamCompress(String[] filePaths, OutputStream outputStream) {
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
for (String filePath : filePaths) {
File file = new File(filePath);
ZipEntry zipEntry = new ZipEntry(file.getName());
zos.putNextEntry(zipEntry);
FileUtil.writeToStream(file, zos);
zos.closeEntry();
}
zos.finish();
} catch (IOException e) {
throw new IORuntimeException("流式压缩失败", e);
}
}
}
6. 高级特性与最佳实践
6.1 路径安全与验证
文件操作中的路径安全至关重要,不当的路径处理可能导致安全漏洞。
路径安全实践:
// 使用FileUtil构建安全路径(防止路径遍历攻击)
File safeFile = FileUtil.file(parentDir, "userFile.txt");
// 路径规范化处理
String userInput = "/path/../to/./sensitive/file.txt";
String safePath = FileUtil.normalize(userInput);
// 检查路径是否在允许的根目录下
public boolean isPathAllowed(String userPath, String rootDir) {
File resolvedPath = new File(rootDir, userPath).getAbsoluteFile();
String normalizedPath = FileUtil.normalize(resolvedPath.getPath());
return normalizedPath.startsWith(FileUtil.normalize(rootDir));
}
// 提取安全的文件名
String userFileName = "../../etc/passwd";
String safeFileName = FileUtil.getName(FileUtil.normalize(userFileName));
6.2 性能优化策略
针对不同场景的性能优化建议:
-
根据文件大小选择处理策略:
public void processFile(File file) { long fileSize = FileUtil.size(file); if (fileSize < 1024 * 1024) { // 小于1MB // 小文件直接读取 String content = FileReader.create(file).readString(); processContent(content); } else if (fileSize < 1024 * 1024 * 100) { // 小于100MB // 中等文件使用缓冲读取 try (BufferedReader reader = FileReader.create(file).getReader()) { String line; while ((line = reader.readLine()) != null) { processLine(line); } } } else { // 大文件使用内存映射或分块处理 processLargeFileWithChunks(file, 8192); } } -
批量操作优化:
// 批量文件复制性能优化 public void batchCopyFiles(List<File> sources, File destDir) { // 并行流处理(适用于大量IO操作) sources.parallelStream() .forEach(file -> FileUtil.copy(file, new File(destDir, file.getName()), true)); }
6.3 异常处理与资源管理
正确的异常处理确保文件操作的稳定性:
public class SafeFileOperations {
/**
* 安全的文件读取方法
*/
public static Optional<String> readFileSafely(String path) {
try {
if (!FileUtil.exist(path)) {
return Optional.empty();
}
String content = FileReader.create(path).readString();
return Optional.of(content);
} catch (IORuntimeException e) {
// 记录日志并返回空结果
log.error("文件读取失败: {}", path, e);
return Optional.empty();
}
}
/**
* 带重试机制的文件写入
*/
public static boolean writeWithRetry(String path, String content, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
FileWriter.create(path).write(content);
return true;
} catch (IORuntimeException e) {
if (i == maxRetries - 1) {
log.error("文件写入失败,已达最大重试次数: {}", path, e);
return false;
}
try {
Thread.sleep(100 * (i + 1)); // 指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
}
}
return false;
}
/**
* 使用try-with-resources自动管理资源
*/
public static void processWithAutoClose(String sourcePath, String destPath) {
try (FileReader reader = FileReader.create(sourcePath);
FileWriter writer = FileWriter.create(destPath)) {
// 流式处理,避免内存溢出
reader.readLines(new LineHandler() {
@Override
public void handle(String line) {
String processedLine = processLine(line);
writer.append(processedLine + "\n");
}
});
} // 自动关闭资源
}
}
7. 综合实战案例
7.1 完整项目示例:配置文件管理器
以下是一个综合应用Hutool FileUtil的配置文件管理器示例:
public class ConfigManager {
private static final String CONFIG_DIR = "config";
private static final String APP_CONFIG_FILE = "config/app.properties";
private static final String BACKUP_EXT = ".bak";
/**
* 初始化配置目录和默认配置
*/
public void initConfig() {
// 创建配置目录
FileUtil.mkdir(CONFIG_DIR);
// 如果配置文件不存在,创建默认配置
if (!FileUtil.exist(APP_CONFIG_FILE)) {
Properties defaultProps = new Properties();
defaultProps.setProperty("app.name", "MyApplication");
defaultProps.setProperty("app.version", "1.0.0");
defaultProps.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
saveConfig(defaultProps);
}
}
/**
* 读取配置文件
*/
public Properties loadConfig() {
Properties props = new Properties();
if (FileUtil.exist(APP_CONFIG_FILE)) {
try {
// 使用Hutool读取Properties文件
String content = FileReader.create(APP_CONFIG_FILE).readString();
props.load(new StringReader(content));
} catch (Exception e) {
throw new RuntimeException("配置读取失败", e);
}
}
return props;
}
/**
* 保存配置文件(带备份功能)
*/
public boolean saveConfig(Properties props) {
// 创建备份
if (FileUtil.exist(APP_CONFIG_FILE)) {
FileUtil.copy(APP_CONFIG_FILE, APP_CONFIG_FILE + BACKUP_EXT, true);
}
try {
// 保存新配置
List<String> lines = new ArrayList<>();
for (String key : props.stringPropertyNames()) {
lines.add(key + "=" + props.getProperty(key));
}
FileWriter.create(APP_CONFIG_FILE).writeLines(lines);
// 验证配置并清理备份
if (validateConfig()) {
FileUtil.del(APP_CONFIG_FILE + BACKUP_EXT);
return true;
} else {
// 恢复备份
FileUtil.copy(APP_CONFIG_FILE + BACKUP_EXT, APP_CONFIG_FILE, true);
FileUtil.del(APP_CONFIG_FILE + BACKUP_EXT);
return false;
}
} catch (Exception e) {
// 恢复备份
if (FileUtil.exist(APP_CONFIG_FILE + BACKUP_EXT)) {
FileUtil.copy(APP_CONFIG_FILE + BACKUP_EXT, APP_CONFIG_FILE, true);
FileUtil.del(APP_CONFIG_FILE + BACKUP_EXT);
}
throw new RuntimeException("配置保存失败", e);
}
}
private boolean validateConfig() {
// 配置验证逻辑
return true;
}
}
7.2 日志文件分析器与报告生成
以下示例展示如何结合文件读取和分析生成日志报告:
public class LogAnalyzer {
/**
* 分析日志文件并生成报告
*/
public void analyzeLogAndGenerateReport(String logFile, String reportFile) {
// 统计数据结构
Map<String, Integer> levelCount = new HashMap<>();
Map<String, Integer> hourDistribution = new HashMap<>();
Set<String> errorPatterns = new HashSet<>();
// 使用FileReader逐行分析大日志文件
FileReader.create(logFile).readLines(new LineHandler() {
@Override
public void handle(String line) {
// 统计日志级别
if (line.contains("INFO")) {
levelCount.merge("INFO", 1, Integer::sum);
} else if (line.contains("WARN")) {
levelCount.merge("WARN", 1, Integer::sum);
} else if (line.contains("ERROR")) {
levelCount.merge("ERROR", 1, Integer::sum);
extractErrorPattern(line, errorPatterns);
}
// 分析时间分布
String hour = extractHourFromLog(line);
if (hour != null) {
hourDistribution.merge(hour, 1, Integer::sum);
}
}
});
// 生成报告
generateReport(reportFile, levelCount, hourDistribution, errorPatterns);
}
private String extractHourFromLog(String logLine) {
// 简化实现:从日志行提取时间信息
if (logLine.length() >= 12) {
return logLine.substring(0, 2);
}
return null;
}
private void extractErrorPattern(String errorLine, Set<String> patterns) {
// 提取错误模式
if (errorLine.contains("NullPointer")) {
patterns.add("NullPointerException");
} else if (errorLine.contains("Timeout")) {
patterns.add("TimeoutException");
}
}
private void generateReport(String reportFile, Map<String, Integer> levelCount,
Map<String, Integer> hourDistribution,
Set<String> errorPatterns) {
List<String> reportLines = new ArrayList<>();
reportLines.add("=== 日志分析报告 ===");
reportLines.add("生成时间: " + new Date());
reportLines.add("");
reportLines.add("各级别日志数量:");
levelCount.forEach((level, count) ->
reportLines.add(String.format(" %s: %d", level, count)));
reportLines.add("");
reportLines.add("时间分布:");
hourDistribution.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry ->
reportLines.add(String.format(" %s时: %d", entry.getKey(), entry.getValue())));
reportLines.add("");
reportLines.add("错误模式统计:");
errorPatterns.forEach(pattern ->
reportLines.add(" " + pattern));
// 写入报告文件
FileWriter.create(reportFile).writeLines(reportLines);
}
/**
* 将日志文件压缩归档
*/
public void archiveLogFiles(String logDir, int daysBefore) {
File logDirectory = new File(logDir);
if (!logDirectory.exists() || !logDirectory.isDirectory()) {
return;
}
// 查找需要归档的日志文件
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -daysBefore);
Date archiveDate = calendar.getTime();
List<File> filesToArchive = FileUtil.loopFiles(logDir, file -> {
Date lastModified = FileUtil.lastModifiedTime(file);
return lastModified.before(archiveDate);
});
// 按日期创建归档
String archiveName = String.format("logs_archive_%tY%tm%td.zip",
archiveDate, archiveDate, archiveDate);
File archiveFile = new File(logDir, archiveName);
// 压缩文件
ZipUtil.zip(archiveFile, true, filesToArchive.toArray(new File[0]));
// 删除已归档的原始日志文件
filesToArchive.forEach(FileUtil::del);
}
}
8. 总结与最佳实践
通过本文的详细介绍,相信你已经全面掌握了Hutool FileUtil的各项功能。以下是一些关键的最佳实践总结,帮助你在实际项目中更加得心应手地使用这个强大的工具库。
8.1 工具类选择指南
根据不同的场景需求,选择合适的工具类可以大大提高开发效率:
- 基础文件操作:优先选择
FileUtil,它提供了最全面的文件操作方法,API设计简洁直观。 - 文本文件读取:对于文本内容读取,特别是需要处理编码的场景,
FileReader是最佳选择。 - 文本文件写入:需要频繁写入文本内容时,使用
FileWriter的各种写入方法。 - 大文件处理:避免一次性加载大文件到内存,采用流式处理或分块处理方式。
- 压缩操作:使用
ZipUtil进行文件压缩和解压,特别是Web应用中的批量文件下载。
8.2 性能优化要点
- 小文件处理(<1MB):使用
FileReader.readString()和FileWriter.write()一次性处理。 - 中等文件(1MB-100MB):使用缓冲流和合适的缓冲区大小(通常8KB-32KB)。
- 大文件(>100MB):采用分块处理或流式处理,避免全量加载到内存。
- 批量操作:使用
writeLines()代替多次单行写入,减少IO操作次数。
8.3 安全注意事项
- 始终使用
FileUtil.file()构建路径,防止路径遍历攻击。 - 对用户上传的文件进行严格的类型和大小限制。
- 重要操作前创建备份,确保操作失败时可以恢复。
- 及时关闭文件流,避免资源泄漏。
Hutool的FileUtil工具集通过简洁的API设计、全面的功能覆盖和良好的性能表现,成为Java文件处理的首选工具库。希望本文能够帮助你在实际项目中充分发挥其潜力,提升开发效率和代码质量。

1万+

被折叠的 条评论
为什么被折叠?



