索引巨大的文本文件
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
拆分为Foo1
到FooN
,其中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 模式?我不知道。如果您的意思是从用户的角度来看,那么按行以上是关于索引巨大的文本文件的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 javascript 或 jquery 读取巨大的文本文件?
java 读取一个巨大的文本文件,该如何实现 既能保证内存不溢出 又能保证性能 ?