读取字符行并获取文件位置

2022-09-01 21:13:09

我正在从文本文件中读取连续的字符行。文件中字符的编码可能不是单字节。

在某些时候,我想获取下一行开始的文件位置,以便我以后可以重新打开文件并快速返回到该位置。

问题

有没有一种简单的方法可以做到这两点,最好使用标准的Java库?

如果没有,什么是合理的解决方法?

理想解决方案的属性

理想的解决方案是处理多字符编码。这包括 UTF-8,其中不同的字符可能由不同数量的字节表示。理想的解决方案将主要依赖于受信任的、得到良好支持的库。最理想的是标准的Java库。第二好的是Apache或Google库。解决方案必须是可扩展的。将整个文件读入内存不是解决方案。返回到位置不应要求在线性时间中读取所有先前的字符。

对于第一个要求,是有吸引力的。但是,缓冲显然会干扰获取有意义的文件位置。BufferedReader.readLine()

不太明显,也可以提前阅读,干扰获取文件位置。从 InputStreamReader 文档中:InputStreamReader

为了实现字节到字符的高效转换,从基础流中提前读取的字节数可能多于满足当前读取操作所需的字节数。

该方法读取每个字符的单个字节RandomAccessFile.readLine()

每个字节都转换为一个字符,方法是将字符的下八位的字节值设置为零。因此,此方法不支持完整的 Unicode 字符集。


答案 1

如果从 a 构造 a 并保持代码可访问的实例,则应该能够通过调用以下命令来获取下一行的位置:BufferedReaderFileReaderFileReader

fileReader.getChannel().position();

在调用 .bufferedReader.readLine()

如果您愿意用性能增益换取位置精度,则可以使用大小为 1 的输入缓冲区来构造。BufferedReader

替代解决方案自己跟踪字节会有什么问题:

long startingPoint = 0; // or starting position if this file has been previously processed

while (readingLines) {
    String line = bufferedReader.readLine();
    startingPoint += line.getBytes().length;
}

这将为您提供与已处理内容准确的字节数,而不管底层标记或缓冲如何。您必须在计数中考虑行尾,因为它们被剥离了。


答案 2

此部分解决方法仅解决使用 7 位 ASCII 或 UTF-8 编码的文件。具有一般解决方案的答案仍然是可取的(就像对此解决方法的批评一样)。

在 UTF-8 中:

  • 所有单字节字符都可以与多字节字符中的所有字节区分开来。多字节字符中的所有字节在高阶位置都有一个“1”。特别是,表示 LF 和 CR 的字节不能是多字节字符的一部分。
  • 所有单字节字符均采用 7 位 ASCII 格式。因此,我们可以使用 UTF-8 解码器解码仅包含 7 位 ASCII 字符的文件。

总而言之,这两点意味着我们可以读取一行,其中包含读取字节而不是字符的东西,然后解码该行。

为了避免缓冲问题,我们可以使用。该类提供读取行并获取/设置文件位置的方法。RandomAccessFile

下面是一个代码草图,用于使用 RandomAccessFile 将下一行读取为 UTF-8。

protected static String 
readNextLineAsUTF8( RandomAccessFile in ) throws IOException {
    String rv = null;
    String lineBytes = in.readLine();
    if ( null != lineBytes ) {
        rv = new String( lineBytes.getBytes(),
            StandardCharsets.UTF_8 );
    }
    return rv;
 } 

然后,可以在调用该方法之前立即从 RandomAccessFile 获取文件位置。给定一个由 引用的 RandomAccessFile:in

    long startPos = in.getFilePointer();
    String line = readNextLineAsUTF8( in );

推荐