1. 概述

本文将介绍如何使用Java标准库及Apache Commons IO库,实现文件中特定单词的替换功能。我们将通过多种方案对比,帮助读者选择最适合实际场景的实现方式。

2. 示例数据

所有示例将基于以下统一数据:

首先创建测试文件 data.txt,内容如下:

This is a sample file.
This is a sample file.
This is a sample file.

关键参数定义如下:

private static final String FILE_PATH = "src/test/resources/data.txt";
private static final String FILE_OUTPUT_PATH = "src/test/resources/data_output.txt";
private static final String OUTPUT_TO_VERIFY 
  = "This is a test file."+System.lineSeparator()+"This is a test file."+System.lineSeparator()+"This is a test file.";

3. 使用 BufferedReader 方案

BufferedReader 提供逐行读取文件的能力,我们采用以下处理流程:

  1. 逐行读取文件内容
  2. 使用 StringBuilder 拼接完整文本
  3. 调用 String.replace() 替换目标单词
  4. 通过 FileWriter 写入新文件
@Test
void givenFile_whenUsingBufferedReader_thenReplacedWordCorrect() throws IOException {
    StringBuilder fileContent = new StringBuilder();
    try (BufferedReader br = Files.newBufferedReader(Paths.get(FILE_PATH))) {
        String line;
        while ((line = br.readLine()) != null) {
            fileContent.append(line).append(System.lineSeparator());
        }
        String replacedContent = fileContent.toString().replace("sample", "test").trim();
        try (FileWriter fw = new FileWriter(FILE_OUTPUT_PATH)) {
            fw.write(replacedContent);
        }

        assertEquals(OUTPUT_TO_VERIFY, replacedContent);
    }
}

✅ 优点:实现简单,适合中小文件
⚠️ 缺陷:大文件处理时内存占用较高

4. 使用 Scanner 方案

Scanner 类也能实现类似功能,核心逻辑与 BufferedReader 方案一致:

@Test
void givenFile_whenUsingScanner_thenReplacedWordCorrect() throws IOException {
    StringBuilder fileContent = new StringBuilder();
    try (Scanner scanner = new Scanner(new File(FILE_PATH))) {
        while (scanner.hasNextLine()) {
            fileContent.append(scanner.nextLine()).append(System.lineSeparator());
        }
        String replacedContent = fileContent.toString().replace("sample", "test").trim();
        try (FileWriter fw = new FileWriter(FILE_OUTPUT_PATH)) {
            fw.write(replacedContent);
        }

        assertEquals(OUTPUT_TO_VERIFY, replacedContent);
    }
}

❌ 性能提示:Scanner 在处理大文件时性能略逊于 BufferedReader,更适合结构化数据解析场景

5. 使用 NIO2 Files API

Java NIO2 的 Files.lines() 方法提供流式处理能力,结合 Lambda 表达式可实现优雅的逐行替换:

@Test
void givenFile_whenUsingFilesAPI_thenReplacedWordCorrect() throws IOException{
    try (Stream<String> lines = Files.lines(Paths.get(FILE_PATH))) {
        List<String> list = lines.map(line -> line.replace("sample", "test"))
          .collect(Collectors.toList());
        Files.write(Paths.get(FILE_OUTPUT_PATH), list, StandardCharsets.UTF_8);

        assertEquals(OUTPUT_TO_VERIFY, String.join(System.lineSeparator(), list));
    }
}

✅ 优势:现代API设计,代码简洁
⚠️ 注意:Files.write() 会覆盖目标文件,需谨慎处理文件权限

6. 使用 Apache Commons IO

通过第三方库 FileUtils 简化文件操作:

@Test
void givenFile_whenUsingFileUtils_thenReplacedWordCorrect() throws IOException{
    StringBuilder fileContent = new StringBuilder();
    List<String> lines = FileUtils.readLines(new File(FILE_PATH), "UTF-8");
    lines.forEach(line -> fileContent.append(line).append(System.lineSeparator()));
    String replacedContent = fileContent.toString().replace("sample", "test").trim();
    try (FileWriter fw = new FileWriter(FILE_OUTPUT_PATH)) {
        fw.write(replacedContent);
    }

    assertEquals(OUTPUT_TO_VERIFY, replacedContent);
}

❌ 内存陷阱:此方案会同时保存原始文件和修改后文件在内存中
⚠️ 适用性:仅推荐处理小文件,大文件场景存在OOM风险

7. 内存高效方案

针对大文件场景,采用流式处理避免内存堆积:

@Test
void givenLargeFile_whenUsingFilesAPI_thenReplacedWordCorrect() throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(FILE_PATH))) {
        Files.writeString(Paths.get(FILE_OUTPUT_PATH), "",
          StandardCharsets.UTF_8, StandardOpenOption.CREATE,StandardOpenOption.TRUNCATE_EXISTING);
        lines.forEach(line -> {
            line = line.replace("sample", "test") + System.lineSeparator();
            try {
                Files.writeString(Paths.get(FILE_OUTPUT_PATH),line,StandardCharsets.UTF_8, 
                  StandardOpenOption.APPEND);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        assertEquals(OUTPUT_TO_VERIFY, Files.readString(Paths.get(FILE_OUTPUT_PATH)).trim());
    }
}

✅ 核心优势:内存占用恒定,适合GB级文件处理
⚠️ 性能权衡:频繁IO操作会增加CPU负载
🔧 修复技巧:文件末尾会多出换行符,读取时需用 trim() 处理

8. 总结

各方案对比如下:

方案 内存占用 CPU负载 适用场景
BufferedReader 中小文件简单处理
Scanner 需要格式解析的场景
NIO2 Files API 现代Java项目首选
Apache Commons IO 极高 已引入依赖的小文件场景
内存高效方案 极低 大文件处理必备

选择建议:

  • 常规场景优先使用 NIO2 Files API,代码简洁且性能均衡
  • 超大文件处理选择 内存高效方案,但需监控CPU使用率
  • 已使用Apache Commons的项目可沿用 FileUtils,但需警惕内存问题

所有示例代码已上传至 GitHub仓库,建议读者实际运行测试用例加深理解。