索引巨大的文本文件

Posted

技术标签:

【中文标题】索引巨大的文本文件【英文标题】:Indexing Huge text file 【发布时间】:2011-04-11 10:21:02 【问题描述】:

我有一个包含 6 列数据(制表符作为分隔符)的巨大文本文件(超过 100 个演出)。在第一列中,我有整数值(集合中有 2500 个不同的值)。我需要根据第一列中的值将此文件拆分为多个较小的文件(请注意,行未排序)。这些较小的文件中的每一个都将用于在 matlab 中准备绘图。

我只有 8 GB 的内存。

问题是如何有效地做到这一点?有什么想法吗?

【问题讨论】:

“只有 8 GB 的内存”。哇,我们已经走了很长一段路。 【参考方案1】:

使用 bash:

cat 100gigfile | while read line; do
  intval="$( echo "$line" | cut -f 1)"
  chunkfile="$( printf '%010u.txt' "$intval" )"
  echo "$line" >> "$chunkfile"
done

这会将您的 100 gig 文件拆分为(如您所说)根据第一个字段的值命名的 2500 个单独的文件。您可能需要根据自己的喜好调整 printf 的格式参数。

【讨论】:

这将产生一个cut 和一个printf 进程,并将为每一行打开和关闭"$chunkfile"。对于 100gig 文件来说似乎效率不高。 @Marcelo - 您可以在设计高效解决方案时尝试在后台运行它。想打赌哪个程序先完成? @Bo Persson 我希望用你最喜欢的 RAD 语言(perl、python 等)破解一个稍微更有效的解决方案,可以在更短的时间内解决问题。一个 100Gb 的文件可能有超过 10 亿行。【参考方案2】:

使用 bash+awk 的单行代码:

awk 'print $0 >> $1".dat" ' 100gigfile 

这会将大文件的每一行附加到一个名为第一列的值+“.dat”扩展名的文件中,例如12 aa bb cc dd ee ff 行将转到 12.dat 文件。

【讨论】:

我刚刚开始测试这个解决方案 - 看起来很简单。 @gozwei:确实。我对awk知之甚少,但在某些情况下它是必不可少的【参考方案3】:

对于 linux 64 位(我不确定它是否适用于 windows),您可以映射文件,并将块复制到新文件。我认为这是最有效的方法。

【讨论】:

【参考方案4】:

最有效的方法是逐块,一次打开所有文件,并重新使用读取缓冲区进行写入。由于提供了信息,因此数据中没有其他模式可用于加速。

您将在不同的文件描述符中打开每个文件,以避免打开和关闭每一行。在开始时或在你去的时候懒洋洋地打开它们。在完成之前关闭它们。大多数 linux 发行版默认只允许打开 1024 个文件,所以你必须提高限制,比如使用ulimit -n 2600,前提是你有权这样做(另请参阅/etc/security/limits.conf)。

分配一个缓冲区,比如几个 kb,然后从源文件中原始读取到其中。迭代并保留控制变量。每当您到达结束行或缓冲区末尾时,就会从缓冲区写入正确的文件描述符。您必须考虑一些边缘情况,例如当读取获得换行符但不足以确定应该进入哪个文件时。

如果您计算出最小行大小,您可以反向迭代以避免处理缓冲区的前几个字节。这将被证明有点棘手,但仍然会加快速度。

我想知道非阻塞 I/O 是否可以解决此类问题。

【讨论】:

嗯,题上的标签是c++,题目是效率。【参考方案5】:

显而易见的解决方案是每次遇到新值时打开一个新文件,并保持打开状态直到结束。但是您的操作系统可能不允许您一次打开 2500 个文件。所以如果你只需要这样做一次,你可以这样做:

    浏览文件,构建所有值的列表。对这个列表进行排序。 (如果您事先知道值是什么,则不需要此步骤。) 将 StartIndex 设置为 0。 打开,比如说,100 个文件(无论您的操作系统适合什么)。这些对应于列表中的下 100 个值,从 list[StartIndex]list[StartIndex+99]。 浏览文件,用list[StartIndex] <= value <= list[StartIndex+99] 输出这些记录。 关闭所有文件。 在StartIndex上加100,如果还没有完成,请转到第3步。

所以你需要 26 次遍历文件。

【讨论】:

【参考方案6】:

在你的外壳中...

$ split -d -l <some number of lines> Foo Foo

这会将一个大文件Foo 拆分为Foo1FooN,其中n 由原始文件中的行数除以您提供给-l 的值确定。循环遍历各个部分...

编辑...评论中的要点...此脚本(如下)将逐行读取,根据第一个字段分类并分配给文件...

#!/usr/bin/env python
import csv

prefix = 'filename'
reader = csv.reader(open('%s.csv' % prefix, 'r'))
suffix = 0
files = 
# read one row at a time, classify on first field, and send to a file
# row[0] assumes csv reader does *not* split the line... if you make it do so,
# remove the [0] indexing (and strip()s) below
for row in reader:
    tmp = row[0].split('\t')
    fh = files.get(tmp[0].strip(), False)
    if not fh:
        fh = open('%s%05i.csv' % (prefix, suffix), 'a')
        files[tmp[0].strip()] = fh
        suffix += 1
    fh.write(row[0])

for key in files.keys():
    files[key].close()

【讨论】:

-1 因为它不回答问题。您必须决定将一行转储到哪个文件中,而 split 不会这样做。 啊哈-第三次阅读问题时,我发现它不完整:) 我更喜欢 bash 而不是 python 的少数情况之一:使用 awk 的单行代码 @davka,oneliner 会逐行读取文件,还是作为连续字节流读取文件? 你的意思是什么是底层 i/o 模式?我不知道。如果您的意思是从用户的角度来看,那么按行

以上是关于索引巨大的文本文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在python中拆分一个巨大的文本文件

如何通过 javascript 或 jquery 读取巨大的文本文件?

java 读取一个巨大的文本文件,该如何实现 既能保证内存不溢出 又能保证性能 ?

如何跳转到巨大文本文件中的特定行?

如何根据日期列在不同的文本/csv文件中转储一个巨大的mysql表?

在 Perl 中读取带有巨大文本节点的 xml 的实用方法