CodeGym /课程 /JAVA 25 SELF /读取文本文件:逐行与整文件

读取文本文件:逐行与整文件

JAVA 25 SELF
第 36 级 , 课程 1
可用

1. 逐行读取文件:BufferedReader 等

之前我们学习了按字节读取:这对处理二进制格式很方便,但对文本而言并不友好。文件由与编码相关的字符组成。因此 Java 提供了方便的“抽象层”——FileReaderBufferedReader 等类,它们把字节流转换为字符流和行。

想象一个文本文件——可能是程序日志、用户列表,或者甚至是巨著《战争与和平》。有时需要快速把整个文件读完,有时需要逐行遍历,有时则要提取某一特定的行。

在 Java 中有多种方式可用,选择取决于文件大小和任务。如果需要按行处理文件(例如统计行数或查找记录),就使用逐行读取。而如果文件较小——可以将其整体载入内存,并把它当作字符串列表来处理。

为什么逐行读取更好?

对于大文件,逐行读取能避免内存问题。把一个数 GB 的日志整读进内存是个坏主意:很容易触发 OutOfMemoryError。而按行读取则可以以很低的开销处理,即便文件有数百 MB。

在 Java 中如何实现?

最经典的方法——使用 BufferedReader(或其同类)与方法 readLine()

import java.io.*;

public class ReadLinesDemo {
    public static void main(String[] args) {
        String fileName = "example.txt";

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            int lineNumber = 1;
            while ((line = reader.readLine()) != null) {
                System.out.printf("%3d: %s%n", lineNumber, line);
                lineNumber++;
            }
        } catch (IOException e) {
            System.out.println("读取文件时发生错误:" + e.getMessage());
        }
    }
}

这里我们打开文件进行读取,并通过 readLine() 逐行读取,直到返回 null(表示到达文件末尾)。之后将每一行连同行号输出。

简述 try-with-resources

看到 try...){ ... } 这种结构了吗?这就是 try-with-resources。它保证即使在读取过程中发生错误,文件也会被关闭。你无需手动调用 close():即便有 catch/finally,关闭也会自动执行。

为什么需要 BufferedReader?

BufferedReader 并非逐字符读取,而是按块读取(通常为 8192 字节),这能加快文件读取。此外,它还提供了方便的 readLine() 方法,直接返回直到换行符之前的一整行。

应选择多大的缓冲区?

通常 BufferedReader 使用 8192 字节(8 KB)的缓冲区——对大多数任务已足够。如果你要读取非常长的行(例如每行 100_000 个字符),可以增大缓冲区:

BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // 64 KB 缓冲区

但对于一般任务,标准大小就非常合适。

2. 整文件读取:Files.readAllLines 和 Files.readString

如果文件较小(例如不超过 1020 MB),将其一次性读入很方便。例如,当你需要快速获取所有行列表、分析文本或通过网络发送它时。

现代方式:Files.readAllLines

自 Java 7 起引入了便捷的 Files 类,包含很多实用方法。

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class ReadAllLinesDemo {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");

        try {
            List<String> lines = Files.readAllLines(path);
            for (int i = 0; i < lines.size(); i++) {
                System.out.printf("%3d: %s%n", i + 1, lines.get(i));
            }
        } catch (IOException e) {
            System.out.println("读取文件时发生错误:" + e.getMessage());
        }
    }
}

这里方法 Files.readAllLines(path) 返回一个字符串列表(List<String>)。可以像普通集合那样对它进行查找、排序、过滤。

更现代:Files.readString(Java 11+)

如果你需要将整个文件读取为一个字符串(例如用于查找子串或传给 JSON),请使用 Files.readString

import java.nio.file.*;
import java.io.IOException;

public class ReadStringDemo {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");

        try {
            String content = Files.readString(path);
            System.out.println("文件内容:");
            System.out.println(content);
        } catch (IOException e) {
            System.out.println("读取文件时发生错误:" + e.getMessage());
        }
    }
}

那编码怎么办?

默认情况下,Files.readAllLinesFiles.readString 使用系统的默认编码。如果文件使用了其他编码(例如 Windows-1251),请显式指定:

List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);

3. 方案对比:什么时候用哪种方式

方式 适用场景 优点 缺点
BufferedReader.readLine()
大文件,逐行处理 节省内存,灵活 只能按行读取
Files.readAllLines()
小型或中等大小的文件 直接得到字符串列表,简单 大文件可能导致 OutOfMemory
Files.readString()
小文件,需要整段文本 一次性得到完整字符串 没有按行拆分

建议:
— 如果文件较小——使用 Files.readAllLinesFiles.readString
— 如果文件很大或你不确定其大小——使用 BufferedReader.readLine()

4. 实战:示例与解析

示例 1:统计大文件中的行数

假设你需要知道一个巨大文件(例如服务器日志)有多少行。

import java.io.*;

public class LineCount {
    public static void main(String[] args) {
        String fileName = "biglog.txt";
        int lineCount = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            while (reader.readLine() != null) {
                lineCount++;
            }
            System.out.println("文件总行数:" + lineCount);
        } catch (IOException e) {
            System.out.println("读取文件时发生错误:" + e.getMessage());
        }
    }
}

示例 2:按内容查找行

查找包含“错误”一词的所有行(不区分大小写):

import java.io.*;

public class FindErrorLines {
    public static void main(String[] args) {
        String fileName = "biglog.txt";
        String keyword = "错误";

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            int lineNumber = 1;
            while ((line = reader.readLine()) != null) {
                if (line.toLowerCase().contains(keyword)) {
                    System.out.printf("%3d: %s%n", lineNumber, line);
                }
                lineNumber++;
            }
        } catch (IOException e) {
            System.out.println("读取文件时发生错误:" + e.getMessage());
        }
    }
}

示例 3:从小型文件加载配置

文件 config.txt

host=localhost
port=8080
mode=dev

把它全部读入并解析为键-值对:

import java.nio.file.*;
import java.util.*;

public class ConfigLoader {
    public static void main(String[] args) throws Exception {
        Path path = Path.of("config.txt");
        List<String> lines = Files.readAllLines(path);

        Map<String, String> config = new HashMap<>();
        for (String line : lines) {
            if (line.trim().isEmpty() || line.startsWith("#")) continue; // 跳过空行和注释
            String[] parts = line.split("=", 2);
            if (parts.length == 2) {
                config.put(parts[0].trim(), parts[1].trim());
            }
        }

        System.out.println("已加载的配置:" + config);
    }
}

5. 读取文本文件的常见错误

错误 1:尝试将二进制文件当作文本读取。 如果你使用 BufferedReaderFiles.readAllLines 打开图像或压缩包,会得到莫名其妙的字符,并且有触发 OutOfMemoryError 的风险。对二进制文件请使用 InputStream

错误 2:未处理异常。 文件可能被删除、移动、或被锁定。始终用 try-catch 包裹读取,并告知用户出现的问题。

错误 3:忽略编码。 如果你的文本是俄文,而你在读取文件时未指定编码,可能会得到“?????”。请使用 StandardCharsets.UTF_8 或所需的编码。

错误 4:忘记关闭流。 如果不关闭文件,它可能会一直被锁定直到程序结束。务必使用 try-with-resources。

错误 5:在文本文件中使用 read() 而不是 readLine()。 方法 read() 按字符读取——这对按行处理既慢又不方便。处理文本请使用 readLine()Files 类的方法。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION