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

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

我只有 8 GB 的内存。

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

【问题讨论】:

  • “只有 8 GB 的内存”。哇,我们已经走了很长一段路。

标签: c++ windows linux matlab


【解决方案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 个文件。所以如果你只需要这样做一次,你可以这样做:

    1. 浏览文件,构建所有值的列表。对这个列表进行排序。 (如果您事先知道值是什么,则不需要此步骤。)
    2. 将 StartIndex 设置为 0。
    3. 打开,比如说,100 个文件(无论您的操作系统适合什么)。这些对应于列表中的下 100 个值,从 list[StartIndex]list[StartIndex+99]
    4. 浏览文件,用list[StartIndex] <= value <= list[StartIndex+99] 输出这些记录。
    5. 关闭所有文件。
    6. 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 模式?我不知道。如果您的意思是从用户的角度来看,那么按行
      猜你喜欢
      • 2014-02-05
      • 2010-09-14
      • 2014-04-10
      • 1970-01-01
      • 2011-05-01
      • 1970-01-01
      • 2021-06-14
      • 1970-01-01
      • 2011-12-31
      相关资源
      最近更新 更多