如何处理非常大的文本文件?

2022-09-04 23:24:19

我目前正在编写需要处理非常大的文本文件(至少几GiB)的东西。这里需要的(这是固定的)是:

  • 基于 CSV,遵循 RFC 4180,嵌入换行符除外
  • 随机读取对行的访问,尽管主要是逐行和接近尾声
  • 在末尾追加行
  • (更改行)。显然,这需要重写文件的其余部分,这种情况也很少见,所以目前不是特别重要。

文件大小禁止将其完全保留在内存中(这也是不希望的,因为在追加时应尽快保留更改)。

我曾考虑过使用内存映射区域作为文件的窗口,如果请求超出其范围的行,则会四处移动。当然,在那个阶段,我仍然没有高于字节级别的抽象。要实际使用内容,我有一个给我一个.现在的问题是,我可以在 中处理文本行,但我还需要知道文件中该行的字节偏移量(保留行索引和偏移量的缓存,这样我就不必从头开始再次扫描文件以查找特定行)。CharsetDecoderCharBufferCharBuffer

有没有办法将 a 中的偏移量映射到匹配项中的偏移量?对于ASCII或ISO-8859-*来说,这显然是微不足道的,对于UTF-8来说,对于ISO 2022或BOCU-1来说,事情会变得非常丑陋(并不是说我实际上期望后两者,但UTF-8应该是这里的默认值 - 并且仍然会带来问题)。CharBufferByteBuffer

我想我可以再次将一部分转换为字节并使用长度。要么它有效,要么我遇到变音符号的问题,在这种情况下,我可能会强制使用NFC或NFD来确保文本始终被明确编码。CharBuffer

不过,我想知道这是否是要走这条路。有更好的选择吗?

伊塔:以下是对常见问题和建议的一些回复:

这是一个用于模拟运行的数据存储,旨在成为完整数据库的小型本地替代方案。我们也有数据库后端,并且使用它们,但是对于它们不可用或不适用的情况,我们确实需要这样做。

我也只支持CSV的一个子集(没有嵌入的换行符),但现在没关系。这里的问题点几乎是我无法预测行的长度,因此需要创建文件的粗略映射。

至于我上面概述的内容:我思考的问题是,我可以很容易地确定字符级别(U + 000D + U + 000A)上行的结尾,但我不想假设这看起来像在字节级别上(例如,对于UTF-16来说,它已经失败了,因为它是or)。我的想法是,我可以通过不硬编码我当前使用的编码细节来使字符编码可更改。但我想我可以坚持使用UTF-8并消化其他所有内容。不过,不知何故,感觉不对劲。0A 0D0D 00 0A 0000 0D 00 0A


答案 1

很难在一系列Java字符(实际上是UTF-16)和字节之间保持1:1的映射,这些字节可能是任何取决于文件编码的。即使使用 UTF-8,1 字节到 1 个字符的“明显”映射也仅适用于 ASCII。UTF-16 和 UTF-8 都不能保证 Unicode 字符可以存储在单台计算机或 .charbyte

我会将我的窗口作为字节缓冲区维护到文件中,而不是char缓冲区。然后,为了在字节缓冲区中找到行尾,我将Java字符串(或可能只是)编码为字节序列,使用与文件相同的编码。然后,我将使用该字节序列在字节缓冲区中搜索行尾。在缓冲区中结束的行的位置 + 缓冲区从文件开头开始的偏移量正好映射到行结束的文件中的字节位置。"\r\n""\n"

追加行只是查找到文件末尾并添加新行的一种情况。更改线条更棘手。我想我会维护一个列表或地图,列出更改行的字节位置以及更改是什么。准备好写入更改时:

  1. 按字节位置对更改列表进行排序
  2. 读取原始文件直到下一次更改,并将其写入临时文件。
  3. 将更改的行写入临时文件。
  4. 跳过原始文件中已更改的行。
  5. 返回到步骤 2,除非您已到达原始文件的末尾
  6. 将临时文件移到原始文件上。

答案 2

是否可以将文件拆分为“子文件”(当然,您不得将其拆分为一个 Utf-8 字符)?然后,您需要为每个子文件提供一些元数据(字符总数和总行数)。

如果你有这个,并且“子文件”相对较小,所以你总是可以完全加载一个,那么处理变得容易。

即使编辑也变得容易,因为您只需要更新“子文件”及其元数据。

如果您将其放在边缘:则可以使用数据库并为每个数据库行存储一行。- 如果这是一个好主意,很大程度上取决于您的用例


推荐