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

我正在写一些需要处理非常大的文本文件的东西(至少有几个GiB)。 这里需要什么(这是固定的)是:

  • 基于CSV,遵循RFC 4180,但嵌入式换行符除外
  • 对线路的随机读取访问,尽管大多数是逐行和接近结束
  • 最后添加行
  • (换行)。 显然,要求重写文件的其余部分,这也很少见,所以目前不是特别重要

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

我曾想过使用内存映射区域作为文件的窗口,如果请求超出其范围的行,它将被移动。 当然,在那个阶段我仍然没有字节级别以上的抽象。 为了实际使用内容我有一个CharsetDecoder给我一个CharBuffer 。 现在问题是,我可以在CharBuffer处理文本行,但我还需要知道文件中该行的字节偏移量(以保持行索引和偏移的缓存,所以我没有再次从头开始扫描文件以查找特定行)。

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

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

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

ETA:有人回答常见问题和建议:

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

我也只支持CSV的一个子集(没有嵌入式换行符),但现在还可以。 这里有问题的几点是我无法预测线条的长度,因此需要创建文件的粗略地图。

至于我上面概述的内容:我正在思考的问题是我可以很容易地确定字符级别上的一行(U + 000D + U + 000A),但我不想假设这看起来像0A 0D字节级别上的0A 0D (例如,UTF-16已经失败,其中它是0D 00 0A 0000 0D 00 0A )。 我的想法是,我可以通过不硬编码我当前使用的编码细节来使字符编码变得可变。 但我想我可以坚持使用UTF-8并将其他所有东西都加入其中。 不过,不知怎的,感觉不对劲。

在一系列Java字符(实际上是UTF-16)和字节之间保持1:1映射非常困难,这些字节可能是任何东西,具体取决于您的文件编码。 即使使用UTF-8,1个字节到1个字符的“明显”映射仅适用于ASCII。 UTF-16和UTF-8都不保证unicode字符可以存储在单个机器charbyte

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

追加行只是寻找文件末尾并添加新行的情况。 换线更棘手。 我想我会维护一个更改行的字节位置列表或映射以及更改的内容。 准备好写入更改时:

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

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

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

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

如果你想把它放到边缘:那么你可以使用数据库并为每个数据库行存储一行。 – 如果这是一个好主意, 很大程度上取决于您的用例

CharBuffer假设所有字符都是UTF-16或UCS-2(也许有人知道区别)

使用正确文本格式的问题是您需要读取每个字节以了解第n个字符的位置或第n行的位置。 我使用多GB文本文件,但假设ASCII-7数据,我只按顺序读/写。

如果要在未编制索引的文本文件上进行随机访问,则不能指望它具有高性能。

如果你愿意购买一台新服务器,你可以获得一台24 GB,大约1,800英镑,64GB大约4,200英镑。 这些允许您甚至将多GB文件加载到内存中。

如果你有固定的宽度线,那么使用RandomAccessFile可能会解决你的很多问题。 我意识到你的线可能不是固定的宽度,但是你可以通过添加行尾指示符然后填充行(例如用空格)来人为地强加它。

如果您的文件当前具有相当均匀的线长分布并且没有非常非常长的线,这显然效果最佳。 缺点是这会人为地增加文件的大小。

  • 找到行的开头:

坚持使用UTF-8和\ n表示该行的结尾应该不是问题。 或者你可以允许UTF-16,并识别数据:它必须被引用(例如),有N个commans(分号)和另一个行尾。 可以读取标题,知道结构有多少列。

  • 插入文件的中间

可以通过在每行的结尾/开头保留一些空间来实现。

  • 最后添加行

只要文件被锁定(与任何其他修改一样),这是微不足道的

在固定列数的情况下,我会将文件逻辑和/或物理地拆分成列,并为IO任务实现一些包装/适配器并整体管理文件。

如何在文件中以一定的间隔定位偏移表,以便您可以重新解析您正在寻找的位置附近的某处?

想法是这些将是字节偏移,其中编码将处于其初始状态(即,如果数据是ISO-2022编码,则该点将处于ASCII兼容模式)。 然后,对数据的任何索引都将包含指向此表的指针以及查找实际行所需的任何内容。 如果放置重新启动点使得每个点都在两个点之间适合mmap窗口,则可以省略解析层中的check / remap / restart代码,并使用假定数据按顺序映射的解析器。