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

时间:2011-01-18 10:02:50

标签: java nio text-files

我目前正在编写需要处理非常大的文本文件的东西(至少有几个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(对于UTF-16已经失败,例如,它是0D 00 0A 0000 0D 00 0A。我的想法是,我可以通过不对当前使用的编码细节进行硬编码来使字符编码变得可变。但我想我可以坚持使用UTF-8并将其他所有东西都加入其中。不过,不知怎的,感觉不对。

7 个答案:

答案 0 :(得分:2)

在一系列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. 将临时文件移动到原始文件上。

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:0)

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

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

如果您希望对未编入索引的文本文件进行随机访问,则不能指望它具有高性能。

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

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

  • 找到行的开头:

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

  • 插入文件中间

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

  • 在末尾附加行

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

文件中有多少定期间隔的偏移表怎么样,所以你可以重新解析你正在寻找的地点附近的地方?

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