ApacheCommons——commons-io(文件与流操作)

1、概述

commons-io(Apache Commons IO)是 Java 处理 IO 操作最强力的工具库之一,全面增强了原生的 java.io 与 java.nio 包。它消除了繁重冗长的流读写、字节缓冲区操作以及手动关闭流的代码,彻底规避了内存溢出和资源泄漏问题。

核心 API 分类列表全景图

分类模块常用类 (Class)核心功能与解决问题
流操作 (Stream)IOUtils输入/输出流的读写、复制、转换与安全关闭(最常用)
文件与目录 (File)FileUtils文件/目录的创建、删除、复制、移动、读取与写入
文件名与路径 (Path)FilenameUtils跨平台的路径提取、扩展名获取、路径规范化
文件监控 (Monitor)FileAlterationObserver, FileAlterationListenerAdaptor监控本地文件或目录的创建、修改、删除等动态
文件过滤 (Filter)IOFileFilter, FileFilterUtils, NameFileFilter组合式文件过滤器,配合目录遍历或搜寻
比较与校验 (Compare)ContentEqualsFactory, CRC64比较文件内容是否一致,计算文件 checksum 等
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.16.1</version>
</dependency>

2、流操作

2.1、IOUtils(流操作与安全转换)

commons-io 中的 org.apache.commons.io.IOUtils 是 Java 开发中最强大的 I/O 流处理工具类之一。它针对输入/输出流(InputStream、OutputStream、Reader、Writer)提供了大量静态方法,特点在于高性能、代码简洁、天然防范 NPE,且底层自动处理了缓冲(Buffer)逻辑。

2.1.1、核心API
  • 流转数据(Read)
    • toString(InputStream input, Charset encoding) / toString(URI uri, Charset encoding):将流或资源一次性转为字符串。
    • toByteArray(InputStream input):将输入流转换为 byte[]。
    • readLines(InputStream input, Charset encoding):按行读取流内容,返回 List<String>。
  • 数据写入流(Write)
    • write(String data, OutputStream output, Charset encoding):将字符串写入输出流。
    • writeLines(Collection<?> lines, String lineEnding, OutputStream output, Charset encoding):将集合逐行写入输出流。
  • 流数据对复制(Copy - 核心功能)
    • copy(InputStream input, OutputStream output):高效将输入流复制到输出流(内部使用默认 4KB 缓冲区,支持超过 2GB 的数据,返回 int,超大流会自动转为使用 copyLarge)。
    • copyLarge(InputStream input, OutputStream output):专门用于处理超大流复制,返回类型为 long。
  • 流比较与控制
    • contentEquals(InputStream input1, InputStream input2):比对两个流的内容是否完全一致。
    • consume(InputStream input):读取并丢弃流中的所有剩余数据。
    • skip(InputStream input, long toSkip):精确跳过指定字节数。

注意:较早版本中的 IOUtils.closeQuietly() 已在现代版本中被标注为废弃,推荐优先使用 Java 7+ 的 try-with-resources 语法进行资源的自动关闭。

2.1.2、使用示例

示例 1:流转字符串与行读取(读取配置文件/响应流)

import org.apache.commons.io.IOUtils;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class IOUtilsReadDemo {
    public static void main(String[] args) {
        // 从 Classpath 读取文件流
        try (InputStream input = IOUtilsReadDemo.class.getResourceAsStream("/config.json")) {
            if (input != null) {
                // 1. 将 InputStream 直接转换为 UTF-8 字符串
                String jsonStr = IOUtils.toString(input, StandardCharsets.UTF_8);
                System.out.println("文本内容:\n" + jsonStr);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 按行读取
        try (InputStream logStream = IOUtilsReadDemo.class.getResourceAsStream("/app.log")) {
            if (logStream != null) {
                // 2. 按行读取返回 List<String>
                List<String> lines = IOUtils.readLines(logStream, StandardCharsets.UTF_8);
                System.out.println("日志总行数: " + lines.size());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

示例 2:高效流复制(网络文件下载 / 响应导出)

IOUtils.copy 内部使用了高性能的缓冲池,无需手动循环写 byte[4096]。

import org.apache.commons.io.IOUtils;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

public class IOUtilsCopyDemo {
    public static void main(String[] args) {
        String fileUrl = "https://example.com/files/sample.pdf";

        // 从网络 URL 打开输入流,并复制保存到本地文件
        try (InputStream in = new URL(fileUrl).openStream();
             OutputStream out = new FileOutputStream("downloaded_sample.pdf")) {

            // 高效复制流(内部自动处理 Buffer)
            long bytesCopied = IOUtils.copyLarge(in, out);
            System.out.println("文件下载成功,传输字节数: " + bytesCopied);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

示例 3:集合数据逐行写入流

适合用于导出 CSV、日志文件或构造 HTTP 输出流。

import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

public class IOUtilsWriteDemo {
    public static void main(String[] args) throws Exception {
        List<String> records = Arrays.asList(
            "ID,Name,Role",
            "101,Alice,Admin",
            "102,Bob,User"
        );

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        // 1. 将集合按行写入输出流(自动处理换行符)
        IOUtils.writeLines(records, System.lineSeparator(), out, StandardCharsets.UTF_8);

        // 2. 验证写入的内容
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        String resultStr = IOUtils.toString(in, StandardCharsets.UTF_8);
        System.out.println("生成的内容:\n" + resultStr);
    }
}
生成的内容:
ID,Name,Role
101,Alice,Admin
102,Bob,User

示例 4:流内容相同性校验

比较两个流的字节内容是否完全等价(常用于校验文件内容或数据传输完整性)。

import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class IOUtilsCompareDemo {
    public static void main(String[] args) throws Exception {
        InputStream stream1 = new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8));
        InputStream stream2 = new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8));
        InputStream stream3 = new ByteArrayInputStream("Hello Java".getBytes(StandardCharsets.UTF_8));

        // 比对内容是否一致
        boolean isEqual12 = IOUtils.contentEquals(stream1, stream2); // true
        boolean isEqual13 = IOUtils.contentEquals(stream1, stream3); // false

        System.out.println("Stream 1 & 2 是否相同: " + isEqual12);
        System.out.println("Stream 1 & 3 是否相同: " + isEqual13);
    }
}
Stream 1 & 2 是否相同: true
Stream 1 & 3 是否相同: false

3、文件与目录

apache commons-io 在处理本地文件与目录时,主要通过 FileUtils(文件与目录高级工具)和 FilenameUtils(文件名与路径解析工具)这两个核心类来实现。它们极大地补充了原生 java.io.File 功能的不足,简化了读写、复制、移动、递归删除以及路径规范化等操作。

3.1、FileUtils(文件与目录高级操作)

FileUtils 提供了针对 File 对象的全套读写、复制、删除与遍历 API。其最大特点是自动管理底层文件流的开启与关闭,无需编写繁琐的 FileInputStream 或 FileOutputStream 代码。

3.1.1、核心API
  • 文件读写操作
    • readFileToString(File file, Charset encoding) / readFileToByteArray(File file):将文件内容一次性读取为字符串或字节数组。
    • readLines(File file, Charset encoding):按行读取文件,返回 List<String>。
    • writeStringToFile(File file, String data, Charset encoding, boolean append):将字符串写入文件(append 为 true 表示追加模式)。
    • writeLines(File file, Collection<?> lines, boolean append):将集合逐行写入文件。
  • 文件与目录的复制 / 移动
    • copyFile(File srcFile, File destFile):复制文件,可保留源文件修改时间。
    • copyDirectory(File srcDir, File destDir):递归复制整个目录及其所有子内容。
    • copyFileToDirectory(File srcFile, File destDir):将文件复制到指定目录下。
    • moveFile(File srcFile, File destFile) / moveDirectory(File srcDir, File destDir):剪切/移动文件或目录。
  • 删除与清理
    • deleteDirectory(File directory):强力递归删除非空目录(原生 File.delete() 无法直接删除带内容的目录)。
    • cleanDirectory(File directory):清空目录下的所有内容,但保留目录本身。
    • deleteQuietly(File file):安全删除文件或目录,不抛出任何 Exception(若删除失败仅返回 false)。
  • 目录查找与计算
    • sizeOf(File fileOrDir) / sizeOfDirectory(File directory):精确计算文件或整个目录的占用字节大小。
    • listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter):结合文件过滤器高阶搜索子文件。
3.1.2、使用示例
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;

public class FileUtilsDemo {
    public static void main(String[] args) {
        try {
            File srcFile = new File("app.log");
            File backupDir = new File("backup");

            // 1. 快速写入与追加文本
            FileUtils.writeStringToFile(srcFile, "2026-09-04 [INFO] 系统启动完成\n", StandardCharsets.UTF_8, true);

            // 2. 读取文本与按行读取
            String content = FileUtils.readFileToString(srcFile, StandardCharsets.UTF_8);
            List<String> lines = FileUtils.readLines(srcFile, StandardCharsets.UTF_8);
            System.out.println("日志总行数: " + lines.size());

            // 3. 文件与目录复制/移动
            FileUtils.copyFileToDirectory(srcFile, backupDir); // 复制 app.log 到 backup/ 目录下

            // 4. 计算目录大小(转换为 MB)
            long dirSize = FileUtils.sizeOfDirectory(backupDir);
            System.out.println("备份目录总大小: " + (dirSize / 1024) + " KB");

            // 5. 条件递归搜索:查找 backup 目录下所有 .log 后缀的文件
            Collection<File> logFiles = FileUtils.listFiles(
                    backupDir,
                    new SuffixFileFilter(".log"), // 文件过滤器
                    TrueFileFilter.INSTANCE        // 目录过滤器(允许进入所有子目录搜索)
            );
            System.out.println("找到的 log 文件数量: " + logFiles.size());

            // 6. 安全清理与删除目录
            // FileUtils.deleteDirectory(backupDir); // 递归删除整个目录
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2、FilenameUtils(文件名与路径解析)

跨平台(Windows / Linux)处理文件名与路径时,直接使用 String.substring 或 split 极易出现分隔符不一致的问题。FilenameUtils 提供了与平台解耦的路径与文件名提取工具。

3.2.1、核心API
  • getExtension(String filename):快速提取扩展名(如 txt、png),无扩展名则返回空字符串。
  • getBaseName(String filename):获取无扩展名的主文件名。
  • getName(String filename):提取带扩展名的完整文件名。
  • getFullPath(String filename):获取包含盘符和层级目录的路径。
  • normalize(String filename):规范化路径(自动清理 ., … 以及纠正系统斜杠类型)。
  • isExtension(String filename, String extension):检查文件扩展名是否符合预期。
3.2.2、使用示例
import org.apache.commons.io.FilenameUtils;

public class FilenameUtilsDemo {
    public static void main(String[] args) {
        String rawPath = "D:/data/project/../logs/sys_2026.log";

        // 1. 路径规范化(自动处理 relative 符号 .. 并纠正正反斜杠)
        String normalizedPath = FilenameUtils.normalize(rawPath);
        System.out.println("规范化路径: " + normalizedPath); // D:\data\logs\sys_2026.log

        // 2. 文件名要素提取
        System.out.println("文件扩展名: " + FilenameUtils.getExtension(rawPath));  // log
        System.out.println("主文件名: " + FilenameUtils.getBaseName(rawPath));     // sys_2026
        System.out.println("完整文件名: " + FilenameUtils.getName(rawPath));      // sys_2026.log
        System.out.println("父级路径: " + FilenameUtils.getFullPath(rawPath));     // D:/data/project/../logs/

        // 3. 校验扩展名
        boolean isImage = FilenameUtils.isExtension(rawPath, new String[]{"png", "jpg", "jpeg"});
        System.out.println("是否为图片文件: " + isImage); // false
    }
}

4、文件监控

commons-io 提供了专门用于实时监控文件与目录变更(新建、修改、删除)的事件驱动组件包 org.apache.commons.io.monitor。相比传统的“轮询遍历文件列表”或原生 Java NIO 的 WatchService,commons-io 的文件监控框架 API 设计更加高级且易用,支持针对指定目录的递归监听、防刷抖动处理以及开箱即用的事件回调。

4.1、核心设计架构与四大组件

文件监控机制由以下四个关键类协同工作:

  • FileAlterationObserver(观察者)
    • 职责:绑定一个监控根目录。负责扫描该目录树,并在内部保存文件系统的状态快照。
    • 工作逻辑:每次触发检查时,对当前目录状态与上一次快照进行比对,若发现差异则触发对应的监听事件。支持传入 IOFileFilter 进行文件过滤。
  • FileAlterationListener / FileAlterationListenerAdaptor(事件监听器)
    • 职责:定义具体的事件回调逻辑。
    • 常见方法:onFileCreate(文件创建)、onFileChange(文件修改)、onFileDelete(文件删除)、onDirectoryCreate(目录创建)等。
    • 最佳实践:推荐继承抽象类 FileAlterationListenerAdaptor,只需重写业务感兴趣的方法,无需实现全量接口。
  • FileAlterationMonitor(监控定时器)
    • 职责:后台异步线程管理器。按照设定的时间间隔(如 1 秒/5 秒)定时调用关联的 FileAlterationObserver.checkAndNotify() 方法。
  • IOFileFilter(文件过滤器 - 可选)
    • 职责:限定观察者只关注特定类型或后缀的文件(如只监听 .json 或 .conf 文件的变动)。

4.2、核心API

  • FileAlterationObserver 构建
    • FileAlterationObserver.builder().setFile(File directory).setFileFilter(IOFileFilter filter).get():推荐使用 Builder 模式创建观察者。
    • addListener(FileAlterationListener listener):为观察者注册事件监听器。
  • FileAlterationMonitor 控制
    • new FileAlterationMonitor(long intervalMillis, FileAlterationObserver... observers):创建监控线程,指定轮询间隔(毫秒)。
    • start():启动后台监控线程。
    • stop() / stop(long stopInterval):停止监控线程并释放资源。

4.3、使用示例

以下示例展示了如何实现对指定配置目录(./config)下 .properties 文件变化的实时监听,常用于本地配置热加载场景。

import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
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.util.concurrent.TimeUnit;

public class FileMonitorDemo {

    public static void main(String[] args) throws Exception {
        // 监控的目标目录
        File monitorDir = new File("./config");
        if (!monitorDir.exists()) {
            monitorDir.mkdirs();
        }

        // 1. 定义文件过滤器:过滤掉隐藏文件,且只监听以 .properties 结尾的文件
        IOFileFilter visibleFiles = HiddenFileFilter.VISIBLE;
        IOFileFilter propertiesFiles = FileFilterUtils.suffixFileFilter(".properties");
        IOFileFilter combinedFilter = FileFilterUtils.and(visibleFiles, propertiesFiles);

        // 2. 创建观察者(Observer),绑定目录与过滤器
        FileAlterationObserver observer = FileAlterationObserver.builder()
                .setFile(monitorDir)
                .setFileFilter(combinedFilter)
                .get();

        // 3. 注册事件监听器(Listener),重写对应的回调逻辑
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                System.out.println("【文件新建】: " + file.getAbsolutePath());
                // TODO: 触发配置初始化加载
            }

            @Override
            public void onFileChange(File file) {
                System.out.println("【文件修改】: " + file.getAbsolutePath());
                // TODO: 触发配置热刷新逻辑
            }

            @Override
            public void onFileDelete(File file) {
                System.out.println("【文件删除】: " + file.getAbsolutePath());
                // TODO: 触发清理缓存逻辑
            }

            @Override
            public void onDirectoryCreate(File directory) {
                System.out.println("【目录新建】: " + directory.getAbsolutePath());
            }

            @Override
            public void onDirectoryDelete(File directory) {
                System.out.println("【目录删除】: " + directory.getAbsolutePath());
            }
        });

        // 4. 创建监控后台线程(Monitor),设置轮询时间间隔为 3 秒
        long interval = TimeUnit.SECONDS.toMillis(3);
        FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);

        // 5. 启动监控线程
        System.out.println(">>> 开始监听目录: " + monitorDir.getAbsolutePath() + " (轮询间隔 3 秒)...");
        monitor.start();

        // 模拟主线程持续运行一段时间以展示效果
        Thread.sleep(30000);

        // 6. 优雅关闭监控线程
        System.out.println(">>> 停止文件监控器...");
        monitor.stop();
    }
}

5、文件过滤

apache commons-io 在 org.apache.commons.io.filefilter 包下提供了一套强大且易用的文件过滤器(File Filter)体系。

相比原生 java.io.FileFilter,commons-io 的过滤器不仅内置了按后缀、文件名模式、文件大小、修改时间、隐蔽性等高频筛选逻辑,还支持布尔逻辑组合(与、或、非),并能够同时作用于文件与目录的筛选。

5.1、核心接口与常用过滤器分类

所有过滤器均实现了 IOFileFilter 接口(继承自原生的 FileFilter 和 FilenameFilter)。

常用基础过滤器

  • SuffixFileFilter / PrefixFileFilter:基于文件后缀(如 .log)或前缀(如 sys_)进行匹配。

  • WildcardFileFilter:支持通配符(如 “*.txt”、“data_2026_??.csv”)。

  • RegexFileFilter:使用正则表达式匹配文件名。

  • SizeFileFilter:基于文件大小筛选(例如大于 10MB 或小于 1KB)。

  • AgeFileFilter:基于修改时间筛选(例如 7 天前修改的文件,可搭配 CutoffFilter)。

  • HiddenFileFilter:过滤隐藏文件或非隐藏文件(HiddenFileFilter.HIDDEN / HIDDEN.VISIBLE)。

  • DirectoryFileFilter / FileFileFilter:专门匹配“仅目录”或“仅文件”(DirectoryFileFilter.DIRECTORY / FileFileFilter.FILE)。

  • TrueFileFilter / FalseFileFilter:恒真/恒假匹配(用于递归搜索时放行所有子目录)。

  • 逻辑组合过滤器(组合拳)

  • AndFileFilterFileFilterUtils.and(...):逻辑与(同时满足多个条件)。

  • OrFileFilterFileFilterUtils.or(...):逻辑或(满足任意条件即可)。

  • NotFileFilterFileFilterUtils.not(...):逻辑非(取反操作)。

5.2、使用示例

示例 1:基础过滤器与逻辑组合

通过 FileFilterUtils 工厂类,可以极其优雅地以链式或静态方法构建复杂组合过滤器。

import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;

import java.io.File;

public class BasicFilterDemo {
    public static void main(String[] args) {
        File targetDir = new File("./logs");

        // 构建组合条件:
        // 1. 必须是文件(排除目录)
        // 2. 必须以 "app_" 开头 且 以 ".log" 结尾
        // 3. 文件大小不能大于 5MB
        IOFileFilter isFile = FileFilterUtils.fileFileFilter();
        IOFileFilter namePattern = FileFilterUtils.and(
                FileFilterUtils.prefixFileFilter("app_"),
                FileFilterUtils.suffixFileFilter(".log")
        );
        IOFileFilter sizeFilter = FileFilterUtils.not(
                FileFilterUtils.sizeFileFilter(5 * 1024 * 1024L, true) // acceptLarger = true
        );

        // 使用 and 链接所有条件
        IOFileFilter combinedFilter = FileFilterUtils.and(isFile, namePattern, sizeFilter);

        // 使用原生 File.listFiles 配合过滤器进行单层扫描
        File[] matchedFiles = targetDir.listFiles(combinedFilter);

        if (matchedFiles != null) {
            for (File f : matchedFiles) {
                System.out.println("符合条件的文件: " + f.getName());
            }
        }
    }
}

示例 2:搭配 FileUtils.listFiles 递归检索与清理日志

FileUtils.listFiles 接收两个 IOFileFilter 参数:

  • fileFilter:作用于文件粒度的筛选。
  • dirFilter:决定是否递归进入某个子目录(传入 TrueFileFilter.INSTANCE 表示深入所有子目录)。
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;

import java.io.File;
import java.util.Collection;

public class FileUtilsListDemo {
    public static void main(String[] args) {
        File rootDir = new File("./data");

        // 查找规则:
        // 1. 使用通配符匹配 '*.csv' 或 '*.xlsx'
        // 2. 忽略隐藏目录/隐藏文件
        IOFileFilter wildcardFilter = new WildcardFileFilter("*.csv", "*.xlsx");
        IOFileFilter visibleFilter = FileFilterUtils.and(
                wildcardFilter,
                FileFilterUtils.not(FileFilterUtils.prefixFileFilter("."))
        );

        // 执行递归搜寻
        Collection<File> files = FileUtils.listFiles(
                rootDir,
                visibleFilter,          // 文件过滤条件
                TrueFileFilter.INSTANCE  // 目录过滤条件:递归搜寻所有子目录
        );

        System.out.println("成功找到相符文件数: " + files.size());
        files.forEach(file -> System.out.println("Path: " + file.getAbsolutePath()));
    }
}

示例 3:时间过滤器(按修改时间删除过期历史文件)

按修改时间筛选在清理历史日志、过期临时文件时属于刚需逻辑。

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AgeFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;

import java.io.File;
import java.util.Collection;
import java.util.Date;

public class AgeFilterDemo {
    public static void main(String[] args) {
        File logDir = new File("./logs");

        // 计算 7 天前的毫秒时间戳
        long sevenDaysAgoMillis = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000);
        Date cutoffDate = new Date(sevenDaysAgoMillis);

        // 创建 AgeFileFilter:acceptOld = true 表示匹配修改时间【早于/老于】指定时间的文件
        AgeFileFilter oldFileFilter = new AgeFileFilter(cutoffDate, true);

        // 搜寻 logs 目录下所有 7 天前的历史文件
        Collection<File> expiredLogs = FileUtils.listFiles(logDir, oldFileFilter, TrueFileFilter.INSTANCE);

        for (File logFile : expiredLogs) {
            System.out.println("清理过期日志: " + logFile.getName());
            // FileUtils.deleteQuietly(logFile); // 安全删除过期文件
        }
    }
}

6、比较与校验

apache commons-io 在处理文件和流的内容比对、校验与比对测试时,提供了极具针对性的 API,主要分布在 IOUtils、FileUtils 以及专门用于校验计算的 org.apache.commons.io.comparator 和 org.apache.commons.io.input 包中。

6.1、内容流与文件的相等性比对(Content Comparison)

在判断两个文件或数据流内容是否相同时,直接对比文件名或文件大小并不准确。commons-io 提供了逐字节高效比对内容的方法,底层会自动使用缓冲处理,避免将大文件一次性加载到内存中。

6.1.1、核心 API
  • IOUtils.contentEquals(InputStream input1, InputStream input2):比对两个输入流中的字节内容是否完全一致。
  • IOUtils.contentEqualsIgnoreEOL(Reader input1, Reader input2):按字符比对两个 Reader,忽略换行符(EOL, End-of-Line)的差异(例如 Windows 的 \r\n 与 Linux 的 \n 在内容一致时会被判定为相等)。
  • FileUtils.contentEquals(File file1, File file2):比对两个文件的字节内容是否一致。
  • FileUtils.contentEqualsIgnoreEOL(File file1, File file2, String encoding):忽略换行符差异比对两个文本文件。
6.1.2、使用示例
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class ContentCompareDemo {
    public static void main(String[] args) throws Exception {
        // 1. 比对字节流内容
        InputStream stream1 = new ByteArrayInputStream("Data123".getBytes(StandardCharsets.UTF_8));
        InputStream stream2 = new ByteArrayInputStream("Data123".getBytes(StandardCharsets.UTF_8));
        boolean streamEqual = IOUtils.contentEquals(stream1, stream2);
        System.out.println("流内容是否一致: " + streamEqual); // true

        // 2. 文本文件跨平台比对(忽略 Windows \r\n 与 Linux \n 差异)
        File fileWindows = new File("config_win.txt"); // 内容: line1\r\nline2
        File fileLinux = new File("config_linux.txt"); // 内容: line1\nline2

        // 普通字节比对(结果为 false)
        boolean byteEqual = FileUtils.contentEquals(fileWindows, fileLinux);
        // 忽略换行符比对(结果为 true)
        boolean ignoreEolEqual = FileUtils.contentEqualsIgnoreEOL(fileWindows, fileLinux, "UTF-8");

        System.out.println("严格字节比对: " + byteEqual);
        System.out.println("忽略换行符比对: " + ignoreEolEqual);
    }
}

6.2、流校验码计算(ChecksumInputStream & CRC32)

在网络传输或文件写盘时,需要计算数据的 CRC32 或其它校验码以确保数据的准确性(防止数据损坏)。commons-io 提供了 ChecksumInputStream,允许你在流读取数据的同时顺便计算出 CRC32 校验和,无需二次读取文件。

6.2.1、核心API
  • ChecksumInputStream:装饰器流,继承自 ProxyInputStream。
  • ChecksumInputStream.builder():推荐使用链式 Builder 创建,传入 InputStream 以及 java.util.zip.Checksum(如 CRC32 或 Adler32)。
  • getChecksum().getValue():获取实时计算出的校验码数值。
6.2.2、使用示例
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ChecksumInputStream;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;

public class ChecksumDemo {
    public static void main(String[] args) throws Exception {
        byte[] data = "Important Financial Record".getBytes(StandardCharsets.UTF_8);
        InputStream sourceStream = new ByteArrayInputStream(data);

        // 绑定 CRC32 校验算法至输入流
        CRC32 crc32 = new CRC32();
        try (ChecksumInputStream checksumIn = ChecksumInputStream.builder()
                .setInputStream(sourceStream)
                .setChecksum(crc32)
                .get()) {

            // 在读取/消费流的过程中,底层会自动累加计算 CRC32
            String content = IOUtils.toString(checksumIn, StandardCharsets.UTF_8);

            // 获取计算出的 CRC32 校验和
            long calculatedChecksum = checksumIn.getChecksum().getValue();
            System.out.println("读取到的内容: " + content);
            System.out.println("实时计算出的 CRC32 校验和: " + calculatedChecksum);
        }
    }
}

6.3、文件列表排序与比较器(File Comparators)

在对目录中的文件进行排序时(例如:按修改时间清理旧日志、按文件大小排序、按文件名升序/降序),直接手写 Comparator 容易忽视 null 值或跨平台路径问题。commons-io 在 org.apache.commons.io.comparator 包中内置了丰富的预设比较器。

6.3.1、常用比较器类
  • NameFileComparator:按文件名排序(支持敏感/不敏感大小写 NAME_INSENSITIVE_COMPARATOR)。
  • SizeFileComparator:按文件/目录占用空间大小排序(SIZE_COMPARATOR / SIZE_REVERSE)。
  • LastModifiedFileComparator:按最后修改时间排序(LASTMODIFIED_COMPARATOR / LASTMODIFIED_REVERSE)。
  • ExtensionFileComparator:按文件扩展名排序。
  • CompositeFileComparator:组合多个比较器(例如:先按修改时间排序,若相同再按文件名排序)。
6.3.2、使用示例
import org.apache.commons.io.comparator.CompositeFileComparator;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.apache.commons.io.comparator.NameFileComparator;
import org.apache.commons.io.comparator.SizeFileComparator;

import java.io.File;
import java.util.Arrays;
import java.util.List;

public class FileComparatorDemo {
    public static void main(String[] args) {
        File logDir = new File("./logs");
        File[] files = logDir.listFiles();

        if (files != null) {
            // 1. 按文件大小降序排序(大文件在前)
            Arrays.sort(files, SizeFileComparator.SIZE_REVERSE);
            System.out.println("--- 按文件大小降序排序 ---");
            for (File f : files) {
                System.out.println(f.getName() + " (" + f.length() + " bytes)");
            }

            // 2. 按修改时间升序排序(最旧的文件在前,适合日志清理)
            Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);

            // 3. 组合比较器:优先按修改时间排序,若修改时间相同则按文件名排序
            CompositeFileComparator compositeComparator = new CompositeFileComparator(
                    LastModifiedFileComparator.LASTMODIFIED_COMPARATOR,
                    NameFileComparator.NAME_INSENSITIVE_COMPARATOR
            );
            List<File> fileList = Arrays.asList(files);
            compositeComparator.sort(fileList);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值