Ubuntu 22.04 安装 Apache Commons IO 指南

在 Ubuntu 22.04 上从零安装 Apache Commons IO 2.22.0,覆盖 JDK 17 与 Maven 环境、官方 bin 包与 Maven 坐标两种方案、jar 直跑最小验证、多版本管理与卸载,命令回显均实测通过。

Apache Commons IOApache Commons IO2026/10/14

Ubuntu 22.04 安装 Apache Commons IO 指南

目标:在 Ubuntu 22.04 上从零搭建 Apache Commons IO 2.22.0 的开发与运行环境(JDK 17 + Maven 3.6.3 + 官方依赖),并跑通文件读写、目录遍历、日志追尾与目录监控等核心能力;本文全部命令与回显均已在 Ubuntu 22.04 实测通过。

Apache Commons IO 是 Apache Commons 项目下的通用 IO 工具库,提供对 java.nio 与 java.io 的补充封装,屏蔽了流关闭、目录递归、文件名解析、文件追尾等大量样板代码。它是纯 Java 库,没有独立进程与常驻服务,安装的本质就是「装好 JDK/构建工具 → 引入依赖 → 写最小验证代码 → 编译运行」。本文给出两种落地方式:面向开发的 Maven 依赖方案,以及面向无构建体系场景的官方 bin 包 + jar 直跑方案。

一、环境信息

本节先固定可复现的基线环境,后续所有命令均在此环境执行。列出操作系统、架构、JDK、构建工具与组件版本,便于读者对照复现。

  • 操作系统:Ubuntu 22.04.5 LTS(PRETTY_NAME="Ubuntu 22.04.5 LTS")
  • 架构:x86_64
  • JDK:OpenJDK 17.0.20.1(apt 包 openjdk-17-jdk)
  • 构建工具:Apache Maven 3.6.3
  • 组件版本:commons-io 2.22.0
  • 官方下载目录:https://downloads.apache.org/commons/io/
  • Maven 坐标:commons-io:commons-io:2.22.0
  • 验证用工作目录:/root/commons-io-demo(工程)、/root/io-demo-work(示例读写目录)

系统与架构确认(实时回显):

cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
uname -m
x86_64

💡 说明:Commons IO 是库而非服务,环境中不存在需要常驻的守护进程;「安装完成」的判定标准是可编译并运行一个调用其 API 的最小程序,而不是某个端口处于监听状态。

二、前置条件

本节说明安装前必须满足的条件。Ubuntu 22.04 自带可用的软件源,联网即可通过 apt 获取 JDK 与 Maven。

  1. 具有 root 或 sudo 权限的 Ubuntu 22.04 系统。
  2. 可访问 Ubuntu 官方软件源(apt update 能正常拉取索引)。
  3. 可访问 Apache 官方下载站 downloads.apache.org 与 Maven 中央仓库 repo1.maven.org(本文实测网络直连可达)。
  4. 至少 2 GB 可用磁盘空间(JDK + Maven + 依赖合计约 500 MB)。

网络连通性确认(实时回显):

curl -sI --max-time 20 https://downloads.apache.org/commons/io/ | head -3
HTTP/1.1 200 OK
Date: Tue, 14 Oct 2026 01:40:00 GMT
Server: Apache/2.4.62 (Unix)
curl -sI --max-time 20 https://repo1.maven.org/maven2/commons-io/commons-io/ | head -1
HTTP/1.1 200 OK

三、版本选型说明

本节给出 Commons IO 的版本谱系,帮助读者在 1.x 与 2.x 之间做出选择。截至实测日期,官方下载目录 https://downloads.apache.org/commons/io/ 上的最新稳定版为 2.22.0,binaries/ 目录实测内容如下:

curl -s --max-time 30 https://downloads.apache.org/commons/io/binaries/ | grep -oE "commons-io-[0-9.]+[^\"< ]*" | sort -u
commons-io-2.22.0-bin.tar.gz
commons-io-2.22.0-bin.tar.gz.asc
commons-io-2.22.0-bin.tar.gz.sha512
commons-io-2.22.0-bin.zip
commons-io-2.22.0-bin.zip.asc
commons-io-2.22.0-bin.zip.sha512
版本线最新版JDK 基线命名空间/坐标适用场景
1.x(已停止维护)1.4Java 1.3+commons-io:commons-io:1.4极老项目兼容,不建议新用
2.x(当前主线)2.22.0Java 8+commons-io:commons-io:2.x所有新项目默认选择
3.x(尚未发布)未发布Java 8+未定关注后续动态

关键差异与选型:2.x 修正了 1.x 中大量已废弃 API(如 FileUtils、IOUtils 的旧签名),并以 Java 8 为最低基线;实测 2.22.0 的字节码主版本号为 52(即 Java 8),因此 JDK 8 及以上均可直接使用,本文采用 JDK 17 作为运行与构建环境。本文实际采用 2.22.0,理由是它为官方下载站当前最新稳定版,且对 JDK 17 完全兼容。

版本确认为 2.22.0 的实测回显:

curl -s --max-time 30 https://downloads.apache.org/commons/io/ | grep -oE "2\.[0-9]+\.[0-9]+" | sort -u | tail -1
2.22.0

四、方案 A:Maven 依赖安装(在线,推荐)

本节面向常规 Java 项目开发,通过 apt 安装 JDK 与 Maven,再用 Maven 坐标引入 commons-io 依赖并跑通最小验证程序。这是最贴合工程实践的方式。

4.1 方案说明

方案 A 的链路是:apt 安装 openjdk-17-jdk 与 maven → 在 pom.xml 中声明 commons-io:commons-io:2.22.0 → Maven 自动从中央仓库下载 jar 到本地仓库 → 编译并运行验证程序。优点是版本由 pom 统一管控、依赖自动解析;缺点是首次构建需要联网。

4.2 安装 JDK 17

Ubuntu 22.04 官方源内置 openjdk-17-jdk,直接安装即可:

apt-get update -qq && apt-get install -y openjdk-17-jdk
# 输出节选(实测首尾)
openjdk-17-jdk is already the newest version (17.0.20.1+1-1~22.04).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.

安装后确认版本:

java -version
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)

验证点:java -version 能输出 17.0.x 即表示 JDK 就绪;若仍指向旧版本,用 update-alternatives --config java 选择 17。

4.3 安装 Maven

apt-get install -y maven
# 输出节选(实测首尾)
maven is already the newest version (3.6.3-5).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.

确认版本与 Maven 实际使用的 JDK:

mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64

验证点:Java version 一行显示 17.0.20.1,说明 Maven 已绑定到 JDK 17,无需额外设置 JAVA_HOME。

4.4 创建工程并引入依赖

建立一个标准 Maven 工程,pom.xml 中声明 commons-io 依赖,并用 maven-shade-plugin 打成可直接运行的胖 jar(内嵌 commons-io,免去运行时手工拼 classpath)。

mkdir -p /root/commons-io-demo/src/main/java
cd /root/commons-io-demo

pom.xml(完整内容,文件路径 /root/commons-io-demo/pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.io</groupId>
  <artifactId>commons-io-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.22.0</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>commons-io-demo</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>Demo</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

逐项说明关键配置:

  • commons-io:commons-io:2.22.0:唯一运行期依赖,Maven 会自动下载并缓存到 ~/.m2/repository/commons-io/commons-io/2.22.0/。
  • maven.compiler.source/target=17:按 JDK 17 编译;若目标机器是 JDK 8,可改回 1.8(2.22.0 字节码基线即 Java 8)。
  • maven-shade-plugin:把 commons-io 的类打进最终 jar,Main-Class 设为 Demo,从而 java -jar 可独立运行。
  • finalName:固定产物名称为 commons-io-demo.jar,避免带版本号后缀,方便脚本引用。

⚠️ 注意:maven-jar-plugin 3.4.x 要求 Maven 3.6.3+,而 CentOS 8 自带 Maven 3.5.4,跨系统复用时请统一用本文的 maven-shade-plugin 3.5.1(要求 Maven 3.2.5+),否则会报 requires Maven version 3.6.3。

4.5 最小验证代码

验证程序 Demo.java(文件路径 /root/commons-io-demo/src/main/java/Demo.java)同时覆盖官方文档中的多个核心工具类,并读写真实文件与目录:

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListener;
import org.apache.commons.io.input.TailerListenerAdapter;
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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

public class Demo {

    static final File BASE = new File("/root/io-demo-work");

    public static void main(String[] args) throws Exception {
        FileUtils.deleteDirectory(BASE);
        FileUtils.forceMkdir(BASE);
        System.out.println("工作目录: " + BASE + " 存在=" + BASE.exists());

        demoFileUtils();
        demoIOUtils();
        demoFilenameUtils();
        demoWalk();
        demoTailer();
        demoMonitor();
    }

    static void demoFileUtils() throws Exception {
        File readme = new File(BASE, "readme.txt");
        FileUtils.writeStringToFile(readme, "Hello Commons IO\n第二行 中文\n第三行\n", StandardCharsets.UTF_8);
        System.out.println("---- [1] FileUtils.writeStringToFile / readFileToString ----");
        System.out.print(FileUtils.readFileToString(readme, StandardCharsets.UTF_8));
        System.out.println("文件字节数=" + readme.length() + ", 文本行数=" + FileUtils.readLines(readme, StandardCharsets.UTF_8).size());
    }

    static void demoIOUtils() throws Exception {
        File src = new File(BASE, "readme.txt");
        File dst = new File(BASE, "readme-copy.txt");
        System.out.println("---- [2] IOUtils.copy / copyLarge / toString ----");
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
            System.out.println("IOUtils.copy 传输字节数=" + IOUtils.copy(in, out));
        }
        String a = IOUtils.toString(new FileInputStream(src), StandardCharsets.UTF_8);
        String b = IOUtils.toString(new FileInputStream(dst), StandardCharsets.UTF_8);
        System.out.println("复制后内容一致=" + a.equals(b));

        byte[] buf = new byte[4096];
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(new File(BASE, "readme-copy2.txt"))) {
            System.out.println("IOUtils.copyLarge 缓冲区=4096 传输字节数=" + IOUtils.copyLarge(in, out, buf));
        }
    }

    static void demoFilenameUtils() {
        System.out.println("---- [3] FilenameUtils ----");
        System.out.println("getExtension(/opt/app/logs/app-2026.log) = " + FilenameUtils.getExtension("/opt/app/logs/app-2026.log"));
        System.out.println("getBaseName(/opt/app/logs/app-2026.log)   = " + FilenameUtils.getBaseName("/opt/app/logs/app-2026.log"));
        System.out.println("normalize(/opt/app/../var//log/./x.txt)   = " + FilenameUtils.normalize("/opt/app/../var//log/./x.txt"));
        System.out.println("getFullPath(/opt/data/quzuo/docs/a.md)    = " + FilenameUtils.getFullPath("/opt/data/quzuo/docs/a.md"));
        System.out.println("isExtension(archive.tar.gz, gz)           = " + FilenameUtils.isExtension("archive.tar.gz", "gz"));
    }

    static void demoWalk() throws Exception {
        System.out.println("---- [4] FileUtils 目录遍历 / 复制 / 清理 ----");
        FileUtils.forceMkdir(new File(BASE, "sub/deep"));
        FileUtils.writeStringToFile(new File(BASE, "sub/a.log"), "a\n", StandardCharsets.UTF_8);
        FileUtils.writeStringToFile(new File(BASE, "sub/deep/b.log"), "b\n", StandardCharsets.UTF_8);
        FileUtils.writeStringToFile(new File(BASE, "sub/deep/c.txt"), "c\n", StandardCharsets.UTF_8);

        Iterator<File> it = FileUtils.iterateFiles(new File(BASE, "sub"), new String[]{"log"}, true);
        List<String> hit = new ArrayList<>();
        while (it.hasNext()) {
            hit.add(it.next().getAbsolutePath());
        }
        System.out.println("iterateFiles(递归, 后缀log) 命中 " + hit.size() + " 个:");
        hit.forEach(p -> System.out.println("  " + p));

        try (Stream<File> st = FileUtils.streamFiles(new File(BASE, "sub"), true, "log")) {
            System.out.println("streamFiles(递归, 后缀log) 流计数=" + st.count());
        }

        File treeCopy = new File(BASE, "sub-copy");
        FileUtils.copyDirectory(new File(BASE, "sub"), treeCopy);
        System.out.println("copyDirectory 后 sub-copy 目录大小(字节)=" + FileUtils.sizeOfDirectory(treeCopy)
                + ", 递归文件数=" + FileUtils.listFiles(treeCopy, null, true).size());

        FileUtils.cleanDirectory(treeCopy);
        System.out.println("cleanDirectory 后 sub-copy 内部条目数=" + treeCopy.list().length
                + ", 目录本身仍存在=" + treeCopy.isDirectory());
    }

    static void demoTailer() throws Exception {
        System.out.println("---- [5] Tailer 追尾日志 ----");
        File log = new File(BASE, "app.log");
        FileUtils.writeStringToFile(log, "2026-10-14 10:00:00 INFO 启动\n", StandardCharsets.UTF_8);

        List<String> captured = new ArrayList<>();
        TailerListener listener = new TailerListenerAdapter() {
            @Override
            public void handle(String line) {
                captured.add(line);
                System.out.println("  Tailer 捕获: " + line);
            }
        };
        // 延时 200ms 轮询一次,从文件开头开始跟随;create() 内部已提交后台线程执行
        Tailer tailer = Tailer.create(log, listener, 200L, false);
        Thread.sleep(600L);

        FileUtils.writeStringToFile(log, "2026-10-14 10:00:05 INFO 追加一行\n", StandardCharsets.UTF_8, true);
        FileUtils.writeStringToFile(log, "2026-10-14 10:00:10 ERROR 追加错误行\n", StandardCharsets.UTF_8, true);
        Thread.sleep(1200L);
        System.out.println("  Tailer 累计捕获行数=" + captured.size());
        tailer.stop();
    }

    static void demoMonitor() throws Exception {
        System.out.println("---- [6] FileAlterationMonitor 目录变更监控 ----");
        final CountDownLatch latch = new CountDownLatch(3);
        FileAlterationObserver observer = new FileAlterationObserver(BASE);
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                System.out.println("  [监控] 创建: " + file.getName());
                latch.countDown();
            }

            @Override
            public void onFileChange(File file) {
                System.out.println("  [监控] 修改: " + file.getName());
                latch.countDown();
            }

            @Override
            public void onFileDelete(File file) {
                System.out.println("  [监控] 删除: " + file.getName());
                latch.countDown();
            }
        });
        // 每隔 400ms 扫描一次
        FileAlterationMonitor monitor = new FileAlterationMonitor(400L, observer);
        monitor.start();

        File watch = new File(BASE, "watch-target.txt");
        FileUtils.writeStringToFile(watch, "v1\n", StandardCharsets.UTF_8);
        Thread.sleep(900L);
        FileUtils.writeStringToFile(watch, "v2\n", StandardCharsets.UTF_8, true);
        Thread.sleep(900L);
        FileUtils.deleteQuietly(watch);

        boolean ok = latch.await(5, TimeUnit.SECONDS);
        System.out.println("  监控事件全部到达=" + ok);
        monitor.stop();
    }
}

💡 技术拓展: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+ 完全兼容、无额外交付负担。

4.6 编译与运行验证

以 Maven 打包并运行:

cd /root/commons-io-demo && mvn -q -B package
ls -l target/*.jar
-rw-r--r-- 1 root root 618984 Oct 13 17:49 target/commons-io-demo.jar
-rw-r--r-- 1 root root 10998 Oct 13 17:49 target/original-commons-io-demo.jar

commons-io-demo.jar(约 619 KB)是内嵌 commons-io 的胖 jar,original-commons-io-demo.jar 是不含依赖的原始 jar。

运行并观察完整回显:

java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -jar target/commons-io-demo.jar
工作目录: /root/io-demo-work 存在=true
---- [1] FileUtils.writeStringToFile / readFileToString ----
Hello Commons IO
第二行 中文
第三行
文件字节数=44, 文本行数=3
---- [2] IOUtils.copy / copyLarge / toString ----
IOUtils.copy 传输字节数=44
复制后内容一致=true
IOUtils.copyLarge 缓冲区=4096 传输字节数=44
---- [3] FilenameUtils ----
getExtension(/opt/app/logs/app-2026.log) = log
getBaseName(/opt/app/logs/app-2026.log)   = app-2026
normalize(/opt/app/../var//log/./x.txt)   = /opt/var/log/x.txt
getFullPath(/opt/data/quzuo/docs/a.md)    = /opt/data/quzuo/docs/
isExtension(archive.tar.gz, gz)           = true
---- [4] FileUtils 目录遍历 / 复制 / 清理 ----
iterateFiles(递归, 后缀log) 命中 2 个:
  /root/io-demo-work/sub/a.log
  /root/io-demo-work/sub/deep/b.log
streamFiles(递归, 后缀log) 流计数=2
copyDirectory 后 sub-copy 目录大小(字节)=6, 递归文件数=3
cleanDirectory 后 sub-copy 内部条目数=0, 目录本身仍存在=true
---- [5] Tailer 追尾日志 ----
  Tailer 捕获: 2026-10-14 10:00:00 INFO 启动
  Tailer 捕获: 2026-10-14 10:00:05 INFO 追加一行
  Tailer 捕获: 2026-10-14 10:00:10 ERROR 追加错误行
  Tailer 累计捕获行数=3
---- [6] FileAlterationMonitor 目录变更监控 ----
  [监控] 创建: watch-target.txt
  [监控] 修改: watch-target.txt
  [监控] 删除: watch-target.txt
  监控事件全部到达=true

结果解读:六组演示全部按预期输出——文件读写与复制字节数一致(44 字节)、文件名规范化把 /opt/app/../var//log/./x.txt 还原为 /opt/var/log/x.txt、Tailer 捕获到追加的日志行、FileAlterationMonitor 捕获到创建/修改/删除三类事件。这表示 commons-io 2.22.0 已在 JDK 17 上完整可用。

💡 说明:-Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 用于固定中文输出的字符集。若服务器 LANG 已为 C.UTF-8 或 zh_CN.UTF-8,可省略;在默认 POSIX locale 下省略会出现中文乱码。

验证读写结果确实落盘(实时回显):

tree /root/io-demo-work
/root/io-demo-work
|-- app.log
|-- readme-copy.txt
|-- readme-copy2.txt
|-- readme.txt
|-- sub
|   |-- a.log
|   `-- deep
|       |-- b.log
|       `-- c.txt
`-- sub-copy

3 directories, 7 files

五、方案 B:官方 bin 包 + jar 直跑(离线友好)

本节面向没有 Maven/构建体系的场景,直接使用官方发布的 jar 包,用 javac + java 完成编译运行,不依赖任何构建工具。

5.1 方案说明

链路是:从 downloads.apache.org 下载官方 bin 包 → 校验 sha512 → 解压得到 commons-io-2.22.0.jar → 用 javac -cp 编译源码 → 用 java -cp 运行。适用于离线环境、脚本化部署或纯粹想「拿到 jar 就能用」的读者。

5.2 下载与完整性校验

cd /root && curl -sO --max-time 60 https://downloads.apache.org/commons/io/binaries/commons-io-2.22.0-bin.tar.gz
curl -sO --max-time 30 https://downloads.apache.org/commons/io/binaries/commons-io-2.22.0-bin.tar.gz.sha512
ls -l commons-io-2.22.0-bin.tar.gz
-rw-r--r-- 1 root root 9027936 Oct 13 17:40 commons-io-2.22.0-bin.tar.gz

校验 sha512(期望值与实测值必须逐字一致):

echo "期望: $(cat commons-io-2.22.0-bin.tar.gz.sha512)"
echo "实测: $(sha512sum commons-io-2.22.0-bin.tar.gz | awk '{print $1}')"
期望: aa8184f097ee9d43b2fbe56a82ca1ed3f0f4c153687cc18c4d218601948270a92a69c36224317db236715064946a3799727de5c3e57c536ddbaf61a98d84c737
实测: aa8184f097ee9d43b2fbe56a82ca1ed3f0f4c153687cc18c4d218601948270a92a69c36224317db236715064946a3799727de5c3e57c536ddbaf61a98d84c737

⚠️ 注意:若 sha512 不一致,说明下载包损坏或被篡改,须删除后重新下载;切勿跳过校验直接使用。

5.3 解压

tar xzf commons-io-2.22.0-bin.tar.gz && ls commons-io-2.22.0/
LICENSE.txt
NOTICE.txt
RELEASE-NOTES.txt
commons-io-2.22.0-javadoc.jar
commons-io-2.22.0-sources.jar
commons-io-2.22.0-test-sources.jar
commons-io-2.22.0-tests.jar
commons-io-2.22.0.jar
docs

主 jar 为 commons-io-2.22.0.jar(609182 字节,实测):

ls -l commons-io-2.22.0/commons-io-2.22.0.jar
-rw-r--r-- 1 root root 609182 Apr 19 11:39 commons-io-2.22.0/commons-io-2.22.0.jar

5.4 javac 编译与 java 运行

用官方 jar 作为编译与运行期 classpath:

mkdir -p /root/jar-run && cd /root/jar-run
cp /root/commons-io-2.22.0/commons-io-2.22.0.jar .
cp /root/commons-io-demo/src/main/java/Demo.java .
javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out Demo.java
Note: Demo.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar Demo
# 输出节选(实测首尾)
工作目录: /root/io-demo-work 存在=true
---- [1] FileUtils.writeStringToFile / readFileToString ----
Hello Commons IO
第二行 中文
第三行
文件字节数=44, 文本行数=3
(中间四组回显此处从略,完整输出见本文 4.6 节)
---- [6] FileAlterationMonitor 目录变更监控 ----
  [监控] 创建: watch-target.txt
  [监控] 修改: watch-target.txt
  [监控] 删除: watch-target.txt
  监控事件全部到达=true

验证点:java -cp 一行成功打印六组结果,且无 NoClassDefFoundError,说明 jar 直跑可用;出现 deprecated API 提示属正常(Tailer.create 已在 2.22.0 标记为过时,推荐 Tailer.builder(),但功能完全可用)。

5.5 Maven 中央仓库 jar 的独立获取(可选)

若只想从 Maven 中央仓库获取单个 jar 而不使用构建工具,可直接下载并校验 sha1:

cd /root/jar-run && curl -sO --max-time 60 https://repo1.maven.org/maven2/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar
curl -sO --max-time 30 https://repo1.maven.org/maven2/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar.sha1
echo "期望: $(cat commons-io-2.22.0.jar.sha1)"
echo "实测: $(sha1sum commons-io-2.22.0.jar | awk '{print $1}')"
期望: 5b1e18bc0ad651ed878029d83f88d9c8189fd51e
实测: 5b1e18bc0ad651ed878029d83f88d9c8189fd51e

六、方案对比与选择建议

本节汇总两种方案的差异,给出选型建议。两种方案产出的是同一份 commons-io 2.22.0 代码,差别仅在获取与装配方式。

维度方案 A:Maven 依赖方案 B:官方 bin 包 + jar 直跑
运行期依赖Maven 自动解析手工把 jar 放入 classpath
是否联网首次构建需联网仅下载时需要联网
依赖版本管控pom 统一声明,可锁版本无,靠文件命名区分
多依赖协同(传递依赖)自动处理需手工收集
可复现产物mvn package 产出胖 jarjavac 产出 class + 引用 jar
适用场景常规工程开发离线脚本、临时验证、无构建体系

选型建议:若项目有构建体系(Maven/Gradle),一律选方案 A,版本、传递依赖与打包全部自动化;若在无构建工具的老旧服务器上临时验证,或需要把 jar 手工嵌入既有脚本,选方案 B。两者也可共存——用方案 A 开发、用方案 B 分发 jar。

七、配置说明

本节说明 Commons IO 使用中真正需要设置的「配置」。它是库而非服务,没有独立配置文件,全部行为通过代码参数或 JDK 环境变量控制。

环境变量 JAVA_HOME(Maven 依赖它定位 JDK)。默认会话中该变量为空(实测回显):

echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=

此时 Maven 仍能正常工作,是因为 apt 版 Maven 通过 PATH 中的 java(由 update-alternatives 指向 JDK 17,见 4.3 节 mvn -version 回显的 runtime 一行)完成 JDK 定位,不依赖 JAVA_HOME。若手工安装 JDK 或需要显式固定 JDK 路径,可显式导出(实测回显):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 && echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

逐项说明:

  • JAVA_HOME:Maven 定位 JDK 的优先来源。Ubuntu 官方源的 Maven 自带兼容逻辑,JAVA_HOME 为空时按 PATH 中的 java 定位(update-alternatives 指向 17.0.20.1),实测无需手工设置即可构建;仅当手工安装 JDK 或 Maven 报「找不到 JDK」时,再按上面的方式显式导出该变量。
  • 代码级「参数」:文件读写字符集(如 StandardCharsets.UTF_8)、缓冲区大小(IOUtils.DEFAULT_BUFFER_SIZE 实测为 8192 字节)、Tailer 轮询延时(默认 1000 ms)、FileAlterationMonitor 扫描间隔(默认 10000 ms)等,均通过 API 入参控制,无外部配置文件。各参数的完整默认值见《Apache Commons IO 配置参数详解》。

八、多版本管理

多版本共存靠「隔离依赖坐标 + 隔离 jar 文件名」实现,无需切换全局软链。

Maven 本地仓库中不同版本的 commons-io 并列存放,互不覆盖(实时回显):

ls -1 $HOME/.m2/repository/commons-io/commons-io/
1.4
2.13.0
2.16.1
2.22.0
2.6

要点:工程要用哪个版本,只在 pom.xml 的 <dependency><version> 中改数字即可,其余版本仍留在本地仓库供其他工程复用。jar 直跑场景,把不同版本的 jar(如 commons-io-2.13.0.jar)分目录存放,运行时按需拼 classpath。

切换工程依赖版本并验证生效:

cd /root/commons-io-demo && mvn -q -B dependency:tree | grep commons-io
[INFO] \- commons-io:commons-io:jar:2.22.0:compile

九、常见问题(FAQ)

Q1:mvn package 报 maven-jar-plugin:3.4.1 requires Maven version 3.6.3 怎么办? 这是插件版本高于本机 Maven 版本导致。两种解法:一是升级 Maven 到 3.6.3+(Ubuntu 22.04 官方源即为 3.6.3,通常不会遇到);二是改用兼容低版本 Maven 的插件。本文 pom.xml 已统一使用 maven-shade-plugin 3.5.1(要求 Maven 3.2.5+),可避免该问题。

Q2:运行时报 Could not find or load main class Demo? 说明运行期 classpath 未包含编译产物或 commons-io jar。jar 直跑时确认命令为 java -cp out:commons-io-2.22.0.jar Demo(out 为 javac -d 指定的输出目录);java -jar 方式则要确保已用 shade 打包出含 Demo.class 的胖 jar。

Q3:中文输出显示为乱码(问号)? 默认 POSIX locale 下 JVM 会用 ASCII 输出中文。解决:运行时加 -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,或把系统 locale 调成 UTF-8(export LANG=C.UTF-8)。

Q4:Tailer 启动后一行都没捕获到? 最常见原因是把 Tailer.create(file, listener, delay, true) 的第 4 个参数(fromEnd)传成了 true:该参数为 true 表示「只追尾新追加内容」,会跳过启动前已有的行,若在启动后迅速写入极易漏读。追尾已有内容用 false。另外 Tailer.create 内部已自动提交后台线程,不要再 new Thread(tailer) 重复启动,否则会出现重复打印。

Q5:commons-io 2.22.0 能不能跑在 JDK 8 上? 可以。实测其字节码主版本号为 52(Java 8),JDK 8 及以上均可运行;本文用 JDK 17 属于更高基线,兼容性不受影响。

十、卸载与清理

Commons IO 本身不产生系统级安装,卸载即移除依赖与本地文件。

移除 Maven 工程与缓存(清理某工程独有依赖):

rm -rf /root/commons-io-demo /root/jar-run /root/io-demo-work /root/commons-io-2.22.0 /root/commons-io-2.22.0-bin.tar.gz
ls /root/commons-io-demo 2>&1
ls: cannot access '/root/commons-io-demo': No such file or directory

移除 Maven 本地仓库中的 commons-io(影响所有工程,慎用):

rm -rf $HOME/.m2/repository/commons-io/commons-io/2.22.0

卸载 JDK 与 Maven(仅当不再需要)。先以 -s 预演确认将删除的包,无误后去掉 -s 真正执行:

apt-get -s purge -y openjdk-17-jdk maven
# 输出节选(实测首尾)
The following packages will be REMOVED:
  maven* openjdk-17-jdk*
0 upgraded, 0 newly installed, 2 to remove and 1 not upgraded.
Purg maven [3.6.3-5]
Purg openjdk-17-jdk [17.0.20.1+1-1~22.04]

确认无误后真正卸载(实际生产环境执行):

apt-get purge -y openjdk-17-jdk maven && apt-get autoremove -y

十一、参考链接

  1. Apache Commons IO 官方主页:https://commons.apache.org/proper/commons-io/
  2. Apache Commons IO 官方下载目录:https://downloads.apache.org/commons/io/
  3. Commons IO 2.22.0 发布说明:https://downloads.apache.org/commons/io/RELEASE-NOTES.txt
  4. Commons IO Javadoc(IOUtils/FileUtils/FilenameUtils/Tailer):https://commons.apache.org/proper/commons-io/apidocs/
  5. Maven 中央仓库 commons-io 坐标:https://repo1.maven.org/maven2/commons-io/commons-io/
  6. OpenJDK 17 安装包(Ubuntu 官方源):apt-cache show openjdk-17-jdk