Apache Commons IO 快速入门与集成示例

Apache Commons IO 2.22.0 快速入门:pom 依赖引入、核心 API 一览、文件读写复制、文件名解析、目录遍历、Tailer 追尾与目录变更监控等集成示例,全部代码回显实测通过。

Apache Commons IOApache Commons IO2026/10/14

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(双进程追尾)

二、前置条件

  1. 已安装 JDK 8 及以上(本文实测 JDK 17),java/javac 可用。
  2. 已引入 commons-io 2.22.0 依赖(Maven 坐标或官方 jar,方式见第三章)。
  3. 终端 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 IOGoogle 工具库子模块与 Guava 全家桶耦合,API 偏轻已用 Guava 的工程
OkioKotlin/流式 IO面向现代流与 Buffer,Square 系网络/序列化底层流处理

本文采用 Commons IO,理由是它对「文件树遍历、目录变更监控、日志追尾」这类运维/工程常见需求提供了开箱即用的封装(FileUtils、FileAlterationMonitor、Tailer),且与 JDK 8+ 完全兼容、无额外交付负担。

四、核心 API 一览

本节列出本文示例涉及的 6 个核心类,后续小节逐一演示。

类包核心方法用途
FileUtilsorg.apache.commons.iowriteStringToFile / readFileToString / copyFile / sizeOfDirectory / listFiles文件与目录操作
IOUtilsorg.apache.commons.iocopy / copyLarge / toString流复制与读取
FilenameUtilsorg.apache.commons.iogetBaseName / getExtension / normalize文件名解析
Tailerorg.apache.commons.io.inputbuilder() / create()日志实时追尾
FileAlterationMonitororg.apache.commons.io.monitorstart() / stop()目录变更监控
FileAlterationObserverorg.apache.commons.io.monitoraddListener(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 用于「只关心新日志」场景(如实时告警),两者差异实测如上。

六、注意事项

本节汇总集成过程中的高频注意点。

  1. Tailer 用 builder 新 API:2.22.0 中 Tailer.create 已标记过时(编译出现 deprecated API 提示属正常),推荐 Tailer.builder(),且延时用 setDelayDuration(Duration.ofMillis(long))、取实例用 .get()(没有 setDelayMillis(int)/build())。
  2. 不要重复启动 Tailer 线程:Tailer.create 内部已提交后台线程(线程名 commons-io-tailer),若再手动 new Thread(tailer).start() 会出现每条日志被重复打印两次。
  3. 编码统一:编译带 -encoding UTF-8,运行带 -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,读写文件显式传 StandardCharsets.UTF_8。
  4. 堆内存建议 256–512 MB:示例工程用 -Xmx256m 即可(java -Xmx256m -jar commons-io-demo.jar);需在内存中一次性处理超大文件时放宽到 -Xmx512m。
  5. 监控与追尾的延时取舍: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。

八、参考链接

  1. Apache Commons IO 官方主页:https://commons.apache.org/proper/commons-io/
  2. Commons IO 快速上手官方指南:https://commons.apache.org/proper/commons-io/
  3. Commons IO Javadoc(FileUtils):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/FileUtils.html
  4. Commons IO Javadoc(Tailer):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/Tailer.html
  5. Commons IO Javadoc(FileAlterationMonitor):https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/FileAlterationMonitor.html
  6. Maven 中央仓库 commons-io 坐标:https://repo1.maven.org/maven2/commons-io/commons-io/