Ubuntu 22.04 安装 Apache Commons IO 指南
在 Ubuntu 22.04 上从零安装 Apache Commons IO 2.22.0,覆盖 JDK 17 与 Maven 环境、官方 bin 包与 Maven 坐标两种方案、jar 直跑最小验证、多版本管理与卸载,命令回显均实测通过。
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。
- 具有 root 或 sudo 权限的 Ubuntu 22.04 系统。
- 可访问 Ubuntu 官方软件源(
apt update能正常拉取索引)。 - 可访问 Apache 官方下载站
downloads.apache.org与 Maven 中央仓库repo1.maven.org(本文实测网络直连可达)。 - 至少 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.4 | Java 1.3+ | commons-io:commons-io:1.4 | 极老项目兼容,不建议新用 |
| 2.x(当前主线) | 2.22.0 | Java 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-plugin3.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 IO Google 工具库子模块 与 Guava 全家桶耦合,API 偏轻 已用 Guava 的工程 Okio Kotlin/流式 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,可省略;在默认POSIXlocale 下省略会出现中文乱码。
验证读写结果确实落盘(实时回显):
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 产出胖 jar | javac 产出 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
十一、参考链接
- Apache Commons IO 官方主页:https://commons.apache.org/proper/commons-io/
- Apache Commons IO 官方下载目录:https://downloads.apache.org/commons/io/
- Commons IO 2.22.0 发布说明:https://downloads.apache.org/commons/io/RELEASE-NOTES.txt
- Commons IO Javadoc(IOUtils/FileUtils/FilenameUtils/Tailer):https://commons.apache.org/proper/commons-io/apidocs/
- Maven 中央仓库 commons-io 坐标:https://repo1.maven.org/maven2/commons-io/commons-io/
- OpenJDK 17 安装包(Ubuntu 官方源):
apt-cache show openjdk-17-jdk