【问题标题】:Parsing tab separated data解析制表符分隔的数据
【发布时间】:2011-05-10 12:36:41
【问题描述】:

我有一个文本文件 (~10GB),格式如下:

data1<TAB>data2<TAB>data3<TAB>data4<NEWLINE>

我想扫描它并仅在data2 上进行处理。在 C++ 中提取data2 的最佳(最快)方法是什么。

编辑:添加新行

【问题讨论】:

  • 有新行吗?最简单的方法是一次一行。
  • @Jonathan Wood 是的,有新行。
  • @Ajay:每个dataN 部分有多长?是固定的还是可变的?
  • @Xeo 这是一个词,因此是一个变量
  • 我很困惑。为什么不打开文件,读取一个字符串,忽略结果,读取空格,然后读取下一个字符串?

标签: c++


【解决方案1】:

逐行读取文件。对于每一行,在选项卡上拆分。这将为您留下一个包含字段的数组,允许您使用第二个字段(data2)。

【讨论】:

  • 这很干净,但是做了很多不必要的工作。这个问题优先考虑速度。
  • 唯一不需要的是将其他字段存储在数组中而不是丢弃它们;然而,这大致是一个恒定的内存开销。什么更快,托尼 D?下面的 cut 建议应该会慢很多(因为 cut 将对数据进行传递,并且您必须 fork/exec 运行 cut,然后对其输出进行传递)
【解决方案2】:

这听起来像是 shell 实用程序等更高级别工具的工作:

cut -f2           # from stdin
cut -f2 <my_file  # from file

不过,你也可以用 C++ 做到这一点:

void parse(std::istream& in)
{
    std::string word;
    while( in ) {
        std::cin >> word;  // throwaway 1
        std::cin >> word;  // data2
        process(word);
        std::cin >> word >> word;  // throwaway 3 and 4
    }
}

// ...
parse(std::cin);
std::ifstream file("my_file");
parse(file);

【讨论】:

  • 哇,我喜欢切割解决方案。如果发布者坚持使用 C/C++ 来执行此操作,并且 cut 保证在系统上,他们可以使用 popen("cut -f2
【解决方案3】:

一次读取一行文件。从那里解析选项卡非常简单。你可以使用strtok() 之类的东西或类似的例程。

【讨论】:

    【解决方案4】:

    好吧,打开一个文件流(它应该能够处理 10gig 文件),然后跳转到第一个选项卡之后,即 '\t',读取您的数据,然后跳到下一个换行符并重复。

    #include <fstream>
    #include <string>
    
    int main(){
      std::fstream fin("your_file.txt");
    
      while(fin){
        std::string data2;
        char sink = '\0';
    
        // skip to first tab
        fin.ignore(1024,'\t');
    
        fin >> data2;
        // do stuff with data2
    
        // skip to next line
        fin.ignore(1024,'\n');
      }
    }
    

    【讨论】:

    • 逐个字符扫描不是更耗时吗。
    • 我的意思是 getline 和 strtok 会更有效
    • @Ajay: getline 也必须遍历所有这些字符。 strtok 也是如此,所以我猜没有。
    • sink 永远不会是 '\t''\n'
    • @wilhelmtell:已编辑,忘记再次跳过空格,谢谢提醒。
    【解决方案5】:

    由于文件相当大,您可以考虑使用一种技术,让您的 I/O 与您的处理重叠。在回复评论时,您提到您正在使用 linux。如果您使用的是内核 2.6 或更高版本,您可能会考虑使用 Linux 异步 I/O (AIO)。具体来说,您将使用aio_read 将一些读取请求排队,然后使用aio_suspend 等待一个(或多个)请求结束。随着请求完成,您将使用普通 char* 扫描缓冲区以定位您感兴趣的数据。对于您找到的每条数据,您可以在此时创建一​​个 std::string (尽管避免复制可能是有益的)和处理它。一旦您扫描了一个块,您将重新排队以从文件中读取另一个块。您继续这样做,直到您处理完文件中的每个块。

    此方法的代码将比逐行读取文件更复杂,但可能要快得多。

    【讨论】:

    • “相当快”......不一定......无论如何,处理速度很容易比 I/O 快几个数量级(完全取决于硬件和精确的处理方法 - std::字符串比就地指针/长度处理慢),在这种情况下,这样做的潜在好处是最小的。事件处理开销甚至可能使其变慢。如果需要最佳性能,值得尝试,但期望不高... ;-P
    • @Tony 所有的优点。天啊,观众中总有一个 ;-)
    • 无论如何,+1 是唯一提出这种方法的人。 :-)
    • @Tony 谢谢。我的经验是逐行使用 C++ I/O 非常慢。我想有很多复制正在进行(或者其他什么,我从来不想调查太多),所以即使你的处理速度非常快(例如校验和),你仍然可以挤出相当多的额外性能这种技术。当然,您的里程可能会有所不同!
    • 好吧,while (getline(std::cin, my_string)) ... 很慢(我也没有调查过 - 很奇怪,my_string 应该只重新分配几次,这是最明显的性能风险)。无论如何,aio_read 读取一个二进制块,更类似于my_stream.read()...不是我拥有或听说过性能投诉的功能。在这些块读取之后,程序员必须做额外的工作来跟踪行/第二个字段,处理第二个字段本身可能不完整的部分行读取等。无论如何这不是火箭科学,但内存映射要容易得多。跨度>
    【解决方案6】:

    您可以按照其他人的建议使用 iostream。另一种方法是简单地使用 fscanf。例如:

    #include <stdio.h>
    
    ...
    
    FILE* fp = fopen(path_to_file, "r");
    char[256] data;
    
    while(fscanf(fp, "%*s<tab>%s<tab>%*s<tab>%*s", data))
    {
       do what you want with your data
    }
    

    【讨论】:

    • 为什么是星星?或者,为什么%s之外的星星?使用 %*s 会禁止分配。缺点是%s 停在第一个空白处,所以如果数据中有空白,这将无法直接工作。您可以使用%100*[^\t] 来抑制data1,并使用%100[^\t] 来捕获data2 等,但长度容易出错。
    • 啊,感谢您了解这一点。我已经更新了代码以将 *s 放在正确的位置。是的,只有当数据没有空格时它才会起作用
    • 如果您要推荐古老的 C 函数而不是 C++ 标准库,您至少应该解释为什么您认为它们有任何好处......并且不要在此过程中泄漏内存。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多