Apache Commons IO 快速入门与集成示例
Apache Commons IO 2.22.0 快速入门:pom 依赖引入、核心 API 一览、文件读写复制、文件名解析、目录遍历、Tailer 追尾与目录变更监控等集成示例,全部代码回显实测通过。
Apache Commons IO 快速入门与集成示例
目标:用最快路径把 Apache Commons IO 2.22.0 集成进 Java 工程并跑通 6 个常用场景——文件读写、流复制、文件名解析、目录遍历、日志实时追尾、目录变更监控,全部代码与回显均在 JDK 17 实测通过。
Apache Commons IO 是 Java 生态最常用的 IO 工具库之一,核心价值是「把样板代码变一行调用」:读写文件不用再手工管理流与字符集,遍历目录不用再写递归,追尾日志不用再自己实现轮询。本文按「引入依赖 → 认识核心 API → 逐个集成示例 → 注意事项」组织,读者照着抄即可用。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3(也可直接用 javac + 官方 jar,见本文 3.2 节)
- 组件版本:commons-io 2.22.0
- Maven 坐标:
commons-io:commons-io:2.22.0 - 示例工作目录:
/root/qs-work(快速入门)、/root/monitor-work(监控演示)、/root/io-demo-work(双进程追尾)
二、前置条件
- 已安装 JDK 8 及以上(本文实测 JDK 17),
java/javac可用。 - 已引入 commons-io 2.22.0 依赖(Maven 坐标或官方 jar,方式见第三章)。
- 终端 locale 为 UTF-8,或运行 java 时带
-Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,否则中文输出乱码。
环境确认(实时回显):
java -version 2>&1 | head -1
openjdk version "17.0.20.1" 2026-08-18
三、依赖引入
本节给出 Maven 与 jar 直跑两种引入方式。
3.1 Maven 工程(推荐)
在 pom.xml 的 <dependencies> 中加入:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.22.0</version>
</dependency>
首次构建时 Maven 自动从中央仓库下载并缓存,验证依赖生效:
mvn -q -B dependency:tree | grep commons-io
[INFO] \- commons-io:commons-io:jar:2.22.0:compile
3.2 无 Maven 场景(jar 直跑)
下载官方 bin 包校验后,用 javac/java 直跑(下载校验命令详见《Apache Commons IO 常用 CLI 命令与工具详解》4.1–4.2 节):
javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out QuickStart.java
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar QuickStart
💡 技术拓展:Java 生态里做「文件/流工具」的库不止 Commons IO 一家,常见方案对比如下——
方案 定位 特点 适用场景 java.nio(JDK 原生) 基础 API 无第三方依赖,Files/Paths 覆盖大部分场景 简单读写、追求零依赖 Commons IO 通用 IO 工具集 IOUtils/FileUtils/FilenameUtils/Tailer/Monitor 齐全 日志追尾、目录监控、批量文件操作 Guava IO Google 工具库子模块 与 Guava 全家桶耦合,API 偏轻 已用 Guava 的工程 Okio Kotlin/流式 IO 面向现代流与 Buffer,Square 系 网络/序列化底层流处理 本文采用 Commons IO,理由是它对「文件树遍历、目录变更监控、日志追尾」这类运维/工程常见需求提供了开箱即用的封装(
FileUtils、FileAlterationMonitor、Tailer),且与 JDK 8+ 完全兼容、无额外交付负担。
四、核心 API 一览
本节列出本文示例涉及的 6 个核心类,后续小节逐一演示。
| 类 | 包 | 核心方法 | 用途 |
|---|---|---|---|
FileUtils | org.apache.commons.io | writeStringToFile / readFileToString / copyFile / sizeOfDirectory / listFiles | 文件与目录操作 |
IOUtils | org.apache.commons.io | copy / copyLarge / toString | 流复制与读取 |
FilenameUtils | org.apache.commons.io | getBaseName / getExtension / normalize | 文件名解析 |
Tailer | org.apache.commons.io.input | builder() / create() | 日志实时追尾 |
FileAlterationMonitor | org.apache.commons.io.monitor | start() / stop() | 目录变更监控 |
FileAlterationObserver | org.apache.commons.io.monitor | addListener(FileAlterationListener) | 监控事件分发 |
五、集成示例
本节用完整可运行的代码 + 实测回显演示 6 个场景。工程根目录建议设为 /root/qs-work(示例硬编码路径,可按需替换)。
5.1 文件读写与复制
代码(QuickStart.java,文件路径 /root/qs-work/QuickStart.java)演示 FileUtils 建目录、写文件、读文件、复制文件,以及 IOUtils.copy 流复制:
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
public class QuickStart {
public static void main(String[] args) throws Exception {
File base = new File("/root/qs-work");
FileUtils.deleteDirectory(base);
FileUtils.forceMkdir(base);
System.out.println("1) FileUtils.forceMkdir 创建 " + base + " 存在=" + base.exists());
File f = new File(base, "note.txt");
FileUtils.writeStringToFile(f, "Apache Commons IO 快速入门\n第二行\n", StandardCharsets.UTF_8);
System.out.println("2) writeStringToFile 后 大小=" + f.length() + " 内容行数=" + FileUtils.readLines(f, StandardCharsets.UTF_8).size());
System.out.print("3) readFileToString 内容: ");
System.out.print(FileUtils.readFileToString(f, StandardCharsets.UTF_8));
File f2 = new File(base, "note-copy.txt");
FileUtils.copyFile(f, f2);
System.out.println("4) copyFile 后 note-copy.txt 存在=" + f2.exists() + " 大小=" + f2.length());
System.out.println("5) IOUtils.copy 字节数=" + IOUtils.copy(new FileInputStream(f), new FileOutputStream(new File(base, "note-copy2.txt"))));
}
}
编译运行(实测回显):
cd /root/qs-work && javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out QuickStart.java
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar QuickStart
1) FileUtils.forceMkdir 创建 /root/qs-work 存在=true
2) writeStringToFile 后 大小=41 内容行数=2
3) readFileToString 内容: Apache Commons IO 快速入门
第二行
4) copyFile 后 note-copy.txt 存在=true 大小=41
5) IOUtils.copy 字节数=41
要点:写入 41 字节(UTF-8 中文每字 3 字节)、2 行内容;copyFile 与 IOUtils.copy 都完整复制 41 字节。
5.2 文件名解析
FilenameUtils 无需访问文件系统即可解析路径字符串。在 QuickStart.java 主类中追加:
System.out.println("6) FilenameUtils.getBaseName=" + FilenameUtils.getBaseName("/var/log/app-2026-10-14.log")
+ " getExtension=" + FilenameUtils.getExtension("/var/log/app-2026-10-14.log"));
运行回显(实测):
6) FilenameUtils.getBaseName=app-2026-10-14 getExtension=log
要点:getBaseName 取文件名主体、getExtension 取扩展名,按 . 分隔(EXTENSION_SEPARATOR 实测值为 .)。批量处理日志滚动文件(app-2026-10-14.log、app-2026-10-15.log)时,用它解析日期后缀非常顺手。
5.3 目录统计与遍历
继续在 QuickStart.java 中追加:
System.out.println("7) FileUtils.sizeOfDirectory 目录大小=" + FileUtils.sizeOfDirectory(base) + " 字节");
System.out.println("8) 目录内文件清单:");
for (File x : FileUtils.listFiles(base, null, false)) {
System.out.println(" - " + x.getName());
}
运行回显(实测):
7) FileUtils.sizeOfDirectory 目录大小=123 字节
8) 目录内文件清单:
- note.txt
- note-copy2.txt
- note-copy.txt
要点:sizeOfDirectory 递归统计整个目录的字节总数(实测 3 个 41 字节文件 + 尾部换行等合计 123 字节);listFiles(dir, 扩展名过滤, 是否递归) 第三个参数 false 表示只列顶层文件。想按扩展名过滤并递归可传 new String[]{"log"}, true。
5.4 日志实时追尾:Tailer.builder()
Tailer 实时追尾日志文件,是运维场景最常用的能力。2.22.0 推荐用 Tailer.builder() 链式构建(实测确认:此版本 Builder 用 setDelayDuration 设置延时、末端用 .get() 取实例,没有 setDelayMillis(int) 与 build())。在 QuickStart.java 中追加:
System.out.println("9) Tailer.builder() 追尾(推荐 API)");
File log = new File(base, "app.log");
FileUtils.writeStringToFile(log, "2026-10-14 12:00:00 INFO start\n", StandardCharsets.UTF_8);
List<String> got = new ArrayList<>();
Tailer tailer = Tailer.builder()
.setFile(log)
.setDelayDuration(java.time.Duration.ofMillis(100))
.setTailFromEnd(false)
.setTailerListener(new TailerListenerAdapter() {
@Override public void handle(String line) { got.add(line); System.out.println(" [builder-Tailer] " + line); }
})
.get();
Thread.sleep(400);
FileUtils.writeStringToFile(log, "2026-10-14 12:00:05 INFO append\n", StandardCharsets.UTF_8, true);
Thread.sleep(600);
tailer.stop();
System.out.println(" builder-Tailer 累计捕获=" + got.size() + " 行");
运行回显(实测):
9) Tailer.builder() 追尾(推荐 API)
[builder-Tailer] 2026-10-14 12:00:00 INFO start
[builder-Tailer] 2026-10-14 12:00:05 INFO append
builder-Tailer 累计捕获=2 行
要点:延时 100 ms(实时性高)、setTailFromEnd(false) 表示从文件开头追尾(含启动前已有内容),追尾到追加写入的日志行。
5.5 目录变更监控:FileAlterationMonitor
FileAlterationMonitor + FileAlterationObserver 定时扫描目录并分发创建/修改/删除事件。完整演示代码 MonitorDemo.java(文件路径 /root/monitor-work/MonitorDemo.java):
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MonitorDemo {
public static void main(String[] args) throws Exception {
File dir = new File("/root/monitor-work");
FileUtils.deleteDirectory(dir);
FileUtils.forceMkdir(dir);
final CountDownLatch latch = new CountDownLatch(3);
FileAlterationObserver observer = new FileAlterationObserver(dir);
observer.addListener(new FileAlterationListenerAdaptor() {
@Override public void onFileCreate(File f) { System.out.println(" 创建: " + f.getName()); latch.countDown(); }
@Override public void onFileChange(File f) { System.out.println(" 修改: " + f.getName()); latch.countDown(); }
@Override public void onFileDelete(File f) { System.out.println(" 删除: " + f.getName()); latch.countDown(); }
});
FileAlterationMonitor monitor = new FileAlterationMonitor(300L, observer);
monitor.start();
System.out.println("监控已启动, 扫描间隔=300ms");
File a = new File(dir, "a.txt");
FileUtils.writeStringToFile(a, "v1", StandardCharsets.UTF_8);
Thread.sleep(700);
FileUtils.writeStringToFile(a, "v2", StandardCharsets.UTF_8, true);
Thread.sleep(700);
FileUtils.deleteQuietly(a);
System.out.println("3 类事件全部到达=" + latch.await(5, TimeUnit.SECONDS));
monitor.stop();
System.out.println("监控已停止");
}
}
编译运行(实测回显):
cd /root/monitor-work && javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out MonitorDemo.java
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar MonitorDemo
监控已启动, 扫描间隔=300ms
创建: a.txt
修改: a.txt
删除: a.txt
3 类事件全部到达=true
监控已停止
要点:new FileAlterationMonitor(300L, observer) 把扫描间隔设为 300 ms(默认 10000 ms,秒级监控必须显式缩短);监听器继承 FileAlterationListenerAdaptor 只覆写关心的三个方法,其余事件(目录创建/删除等)留空即可。
5.6 多进程实时追尾(双进程演示)
真实场景中日志由「写进程」产生、「追尾进程」消费。下面用两个独立进程演示:LogWriter.java 每 500 ms 追加一条日志共 5 条,LogTailer.java 用 Tailer.create 实时追尾(文件路径均在 /root/io-demo-work/)。
LogWriter.java(写日志进程):
import java.io.*;
import java.nio.charset.StandardCharsets;
public class LogWriter {
public static void main(String[] args) throws Exception {
File f = new File("/root/io-demo-work/live.log");
FileWriter w = new FileWriter(f, true);
for (int i = 1; i <= 5; i++) {
w.write("2026-10-14 11:0" + i + ":00 INFO 第" + i + "条写入 进程1\n");
w.flush();
System.out.println("写入了第" + i + "条");
Thread.sleep(500);
}
w.close();
System.out.println("写日志进程结束");
}
}
LogTailer.java(追尾进程,fromEnd=false 从文件开头追尾):
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListener;
import org.apache.commons.io.input.TailerListenerAdapter;
import java.io.File;
public class LogTailer {
public static void main(String[] args) throws Exception {
File log = new File("/root/io-demo-work/live.log");
TailerListener listener = new TailerListenerAdapter() {
@Override public void handle(String line) {
System.out.println(" [Tailer实时捕获] " + line);
}
};
System.out.println("Tailer 开始追尾 " + log.getAbsolutePath());
Tailer tailer = Tailer.create(log, listener, 100L, false);
Thread.sleep(4000);
tailer.stop();
System.out.println("Tailer 停止(追尾进程结束)");
}
}
编译两个进程,先启动追尾再启动写日志(实测回显):
cd /root/io-demo-work && javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out LogWriter.java LogTailer.java
rm -f live.log
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar LogTailer > tailer.out 2>&1 &
sleep 0.5
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar LogWriter
sleep 1.5
cat tailer.out
写入了第1条
写入了第2条
写入了第3条
写入了第4条
写入了第5条
写日志进程结束
追尾进程输出(实测,5 条全部实时捕获):
Tailer 开始追尾 /root/io-demo-work/live.log
[Tailer实时捕获] 2026-10-14 11:01:00 INFO 第1条写入 进程1
[Tailer实时捕获] 2026-10-14 11:02:00 INFO 第2条写入 进程1
[Tailer实时捕获] 2026-10-14 11:03:00 INFO 第3条写入 进程1
[Tailer实时捕获] 2026-10-14 11:04:00 INFO 第4条写入 进程1
[Tailer实时捕获] 2026-10-14 11:05:00 INFO 第5条写入 进程1
Tailer 停止(追尾进程结束)
fromEnd 语义对比:把 Tailer.create(log, listener, 100L, false) 的第 4 个参数改为 true(只从文件末尾追尾),实测第 1 条(写进程启动瞬间写入、早于追尾就绪)被跳过,只捕获第 2–5 条:
[Tailer(fromEnd=true)捕获] 2026-10-14 11:02:00 INFO 第2条写入 进程1
[Tailer(fromEnd=true)捕获] 2026-10-14 11:03:00 INFO 第3条写入 进程1
[Tailer(fromEnd=true)捕获] 2026-10-14 11:04:00 INFO 第4条写入 进程1
[Tailer(fromEnd=true)捕获] 2026-10-14 11:05:00 INFO 第5条写入 进程1
要点:fromEnd=false 用于「补追存量日志」场景(进程重启后要把启动前的历史行也补上);fromEnd=true 用于「只关心新日志」场景(如实时告警),两者差异实测如上。
六、注意事项
本节汇总集成过程中的高频注意点。
- Tailer 用 builder 新 API:2.22.0 中
Tailer.create已标记过时(编译出现deprecated API提示属正常),推荐Tailer.builder(),且延时用setDelayDuration(Duration.ofMillis(long))、取实例用.get()(没有setDelayMillis(int)/build())。 - 不要重复启动 Tailer 线程:
Tailer.create内部已提交后台线程(线程名commons-io-tailer),若再手动new Thread(tailer).start()会出现每条日志被重复打印两次。 - 编码统一:编译带
-encoding UTF-8,运行带-Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,读写文件显式传StandardCharsets.UTF_8。 - 堆内存建议 256–512 MB:示例工程用
-Xmx256m即可(java -Xmx256m -jar commons-io-demo.jar);需在内存中一次性处理超大文件时放宽到-Xmx512m。 - 监控与追尾的延时取舍:
Tailer轮询延时与FileAlterationMonitor扫描间隔越小实时性越高、CPU 与 IO 开销越大,按业务敏感度平衡(秒级用 100–300 ms,分钟级用默认值)。
七、常见问题(FAQ)
Q1:Tailer.builder() 编译报错:找不到 setDelayMillis(int) 或 build() 方法?
2.22.0 的 Builder 接口与旧版不同:延时用 setDelayDuration(java.time.Duration.ofMillis(100)),取实例用末端的 .get()。照本文 5.4 节写法即可。
Q2:用 Tailer.create 后每条日志打印了两遍?
Tailer.create 内部已自动启动后台线程(线程名 commons-io-tailer),不要再手动 new Thread(tailer).start()。只调一次 create(或 builder().get())即可。
Q3:追尾启动前的历史日志没捕获到?
检查 fromEnd 参数:true 表示只追尾新追加内容,会跳过启动前的存量行;需要补追历史用 false(见 5.6 节双进程演示与语义对比)。
Q4:FileAlterationMonitor 事件延迟 10 秒才到?
默认扫描间隔 10000 ms。秒级监控用 new FileAlterationMonitor(300L, observer) 显式缩短(见 5.5 节)。
Q5:中文读写乱码?
读写方法显式传 StandardCharsets.UTF_8,运行加 -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,编译加 -encoding UTF-8,三处齐了即正常(见第六章)。
Q6:报 NoClassDefFoundError: org/apache/commons/io/FileUtils?
运行期 classpath 缺 commons-io jar。-cp out:commons-io-2.22.0.jar 方式确认 jar 路径正确;-jar 方式确认是含依赖的 shade 胖 jar。
八、参考链接
- Apache Commons IO 官方主页:https://commons.apache.org/proper/commons-io/
- Commons IO 快速上手官方指南:https://commons.apache.org/proper/commons-io/
- Commons IO Javadoc(FileUtils):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/FileUtils.html
- Commons IO Javadoc(Tailer):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/Tailer.html
- Commons IO Javadoc(FileAlterationMonitor):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/FileAlterationMonitor.html
- Maven 中央仓库 commons-io 坐标:https://repo1.maven.org/maven2/commons-io/commons-io/