1. 概述

本文将介绍在 Java 中如何高效地读取文件中指定行号的某一行内容。这在处理日志、配置文件或大数据文件时非常实用。我们会对比几种常见方式,帮你避开踩坑,选择最合适的方法。

2. 测试文件准备

我们先准备一个名为 inputLines.txt 的测试文件,内容如下,供后续所有示例使用:

Line 1
Line 2
Line 3
Line 4
Line 5

文件路径假设为:/tmp/inputLines.txt(实际使用时请替换为真实路径)。

3. 使用 BufferedReader

BufferedReader 是传统但高效的选择,✅ 适合大文件,因为它不会一次性把整个文件加载进内存。

核心思路:逐行读取,跳过前 N-1 行,读取第 N 行。

@Test
public void givenFile_whenUsingBufferedReader_thenExtractedLineIsCorrect() {
    String filePath = "/tmp/inputLines.txt";
    try (BufferedReader br = Files.newBufferedReader(Paths.get(filePath))) {
        // 跳过前3行(读取但不使用)
        for (int i = 0; i < 3; i++) {
            br.readLine();
        }

        // 读取第4行(行号从1开始)
        String extractedLine = br.readLine();
        assertEquals("Line 4", extractedLine);
    } catch (IOException e) {
        fail("文件读取失败: " + e.getMessage());
    }
}

⚠️ 注意:readLine() 返回 null 表示已到文件末尾,使用前需判空。

4. 使用 Scanner

Scanner 也能实现类似功能,API 更简洁,但 ⚠️ 性能不如 BufferedReader,尤其是在大文件场景。

@Test
public void givenFile_whenUsingScanner_thenExtractedLineIsCorrect() {
    String filePath = "/tmp/inputLines.txt";
    try (Scanner scanner = new Scanner(new File(filePath))) {
        // 跳过前3行
        for (int i = 0; i < 3; i++) {
            scanner.nextLine();
        }

        // 读取第4行
        String extractedLine = scanner.nextLine();
        assertEquals("Line 4", extractedLine);
    } catch (FileNotFoundException e) {
        fail("文件未找到: " + e.getMessage());
    }
}

✅ 优点:语法直观,适合简单场景。
❌ 缺点:内部做了额外的解析工作(如类型匹配),缓冲区较小,大文件下性能较差。

5. 使用 Files 工具类(NIO.2)

Java 7 引入的 java.nio.file.Files 提供了更现代的 API,分为两种使用方式:

5.1 小文件:readAllLines()

适用于小文件,简单粗暴,直接把所有行读入 List<String>

@Test
public void givenSmallFile_whenUsingFilesAPI_thenExtractedLineIsCorrect() {
    String filePath = "/tmp/inputLines.txt";
    try {
        // 读取所有行,索引从0开始
        List<String> lines = Files.readAllLines(Paths.get(filePath));
        String extractedLine = lines.get(4); // 第5行
        assertEquals("Line 5", extractedLine);
    } catch (IOException e) {
        fail("读取失败: " + e.getMessage());
    }
}

⚠️ 警告:大文件慎用!会直接 OOM。

5.2 大文件:Files.lines() + Stream

推荐用于大文件,返回 Stream<String>,支持函数式编程,且是惰性加载。

@Test
public void givenLargeFile_whenUsingFilesAPI_thenExtractedLineIsCorrect() {
    String filePath = "/tmp/inputLines.txt";
    try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
        String extractedLine = lines
            .skip(4)           // 跳过前4行
            .findFirst()       // 获取下一行(第5行)
            .orElse(null);     // 防止空指针

        assertEquals("Line 5", extractedLine);
    } catch (IOException e) {
        fail("流处理失败: " + e.getMessage());
    }
}

✅ 优势:内存友好,支持链式操作,可结合 filter、map 等操作。
⚠️ 注意:Stream 必须用 try-with-resources 关闭,否则会资源泄漏。

6. 使用 Apache Commons IO

如果你的项目已经引入了 commons-io,那 FileUtilsIOUtils 也能快速实现。

6.1 FileUtils.readLines()

将文件全部读入 List<String>,和 Files.readAllLines() 类似。

@Test
public void givenFile_whenUsingFileUtils_thenExtractedLineIsCorrect() {
    File file = new File("/tmp/inputLines.txt");
    try {
        List<String> lines = FileUtils.readLines(file, "UTF-8");
        String extractedLine = lines.get(0); // 第1行
        assertEquals("Line 1", extractedLine);
    } catch (IOException e) {
        fail("读取失败: " + e.getMessage());
    }
}

⚠️ 同样不适用于大文件。

6.2 IOUtils.toString()

读取整个文件为 String,然后手动按换行符分割。

@Test
public void givenFile_whenUsingIOUtils_thenExtractedLineIsCorrect() {
    String filePath = "/tmp/inputLines.txt";
    try {
        String fileContent = IOUtils.toString(new FileInputStream(filePath), StandardCharsets.UTF_8);
        String[] lines = fileContent.split(System.lineSeparator());
        String extractedLine = lines[0]; // 第1行
        assertEquals("Line 1", extractedLine);
    } catch (IOException e) {
        fail("转换失败: " + e.getMessage());
    }
}

❌ 不推荐:不仅内存占用高,split(System.lineSeparator()) 在跨平台时可能出问题(Windows \r\n vs Unix \n)。

7. 总结

方法 适用场景 内存占用 推荐指数
BufferedReader 大文件、性能敏感 ✅ 低 ⭐⭐⭐⭐
Scanner 小文件、简单脚本 ❌ 较高 ⭐⭐
Files.readAllLines() 小文件、代码简洁 ❌ 高 ⭐⭐⭐
Files.lines() 大文件、函数式风格 ✅ 低 ⭐⭐⭐⭐⭐
FileUtils.readLines() 小文件、已有 commons-io ❌ 高 ⭐⭐⭐
IOUtils.toString() 不推荐 ❌ 极高

最终建议

  • 小文件:用 Files.readAllLines(),简单直接。
  • 大文件:首选 Files.lines() + Stream,兼顾性能与可读性。
  • 老项目:BufferedReader 依然可靠。

所有示例代码已上传至 GitHub:https://github.com/java-dev-tips/file-line-reader-examples


原始标题:Reading a Line at a Given Line Number From a File in Java