【问题标题】:C: Parse a file to obtain number of columns while reading by mmapC:通过mmap读取时解析文件以获取列数
【发布时间】:2017-10-19 17:24:07
【问题描述】:

我有一个文件,如下所示:

1-3-5  2       1  
2      3-4-1   2
4-1    2-41-2  3-4  

我想返回这个文件的列数。我正在用 C 中的 mmap 读取文件。到目前为止,我一直在尝试使用 strtok(),但失败了。这只是一个测试文件,我的原始文件是 GB 级的。

pmap = mmap(0,mystat.st_size,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
char *start = pmap; 
char *token; 
token = strtok(start, "\t"); 
while (token != NULL){
     printf("%s \n",token);
     token = strtok(NULL, "\t");
     col_len++; 
    }

我一直在尝试这些方法,但显然存在逻辑错误。我得到以下输出:

number of cols = 1   

虽然,列数应该是 3。
如果你们能提供有关如何使用 mmap 解析此类文件的任何想法,那就太好了。 我使用 mmap 是因为对文件的单次传递执行速度更快。

【问题讨论】:

  • strtokmmap'd 文件上可能是个坏主意,它会修改您的文件。您可以自己迭代字符。
  • 是的,我明白了。但是,由于数据规模庞大,我想节省计算时间。 strtok 只是让您解析分隔符,这很有用。因此,在这里寻求一些输入
  • 为什么不在 getc() 上循环并将 '\t 和 '\n' 作为字段/行分隔符处理?

标签: c parsing mmap strtok


【解决方案1】:

如果没有明确的问题,很难提供明确的答案;如所写,该问题不包含完整的代码,不显示精确的输入,也不显示调试输出。

但是可以基于strtok对这个问题的不适用性提供一些建议。

strtok 修改了它的第一个参数,因此将它与mmaped 资源一起使用确实不是一个好主意。但是,这与您遇到的问题没有直接关系。)

  1. 您应该确保文件中的列确实由制表符分隔。在我看来,该文件最有可能包含空格,而不是制表符,这就是程序报告 整个文件 包含一列的原因。如果这是唯一的问题,您可以使用第二个参数" \t" 而不是"\t" 调用strtok。但请记住,strtok 将连续的分隔符组合成一个分隔符,因此如果文件是制表符分隔且有空字段,strtok 将不会报告空字段。

  2. 与上面的短语“整个文件”相关,您不要告诉strtok 将换行符识别为终止标记。因此strtok 循环将尝试分析整个文件,将每行的最后一个字段计算为与下一行的第一个字段相同的标记的一部分。这肯定不是你想要的。

    但是,strtok 会覆盖它找到的列分隔符,因此如果您确实修复了 strtok 调用以将 \n 包含为分隔符,您将无法再判断行结束的位置。这可能对您的代码很重要,这也是为什么strtok 在这种情况下不是合适的工具的一个关键原因。 Gnu strtok 手册页(man strtok,添加了重点)提供了关于这个问题的警告(在最后的错误部分):

    使用这些功能时要小心。如果您确实使用它们,请注意:

    • 这些函数修改它们的第一个参数。

    • 这些函数不能用于常量字符串。

    • 定界字节的标识丢失。

  3. 不能保证文件以 NUL 字符结尾。事实上,该文件不太可能包含 NUL 字符,并且在 mmap 区域中引用不在文件中的字节是未定义的行为,但实际上大多数操作系统将 mmap 整数页,零-填满最后一页。所以 4096 次中的 4095 次,你不会注意到这个问题,而第 4096 次当文件正好是整数页时,你的程序将崩溃并烧毁,以及它所控制的任何敏感设备。这是 strtok 永远不应该用于 mmaped 文件的另一个原因。

【讨论】:

  • 感谢您对我们为什么不应该使用 strtok 的详细评论。我早些时候试图避免strtok。但是,getc 遇到了一些问题。你能提供一种替代/更快的方法吗?
  • @AritraBose:要在 mmap 文件上执行此操作,我只需在输入上逐个字符扫描,注意不要假设它是 NUL 终止的,检查每个字符,例如,isspace。但是您需要在“执行此操作”中指定“此”的含义。是什么分隔列?一个标签?一堆空白?以上的一些组合?可以空字段吗?如果是这样,它们是如何标记的? (例如,如果一行以空格开头,它之前是否有一个空字段?)你想知道第一行有多少列吗?在最长的线?在整个文件中?等等……
【解决方案2】:

我的评论实际上是不正确的,因为您使用MAP_PRIVATE,您不会冒险破坏您的文件。但是,如果您修改内存区域,则会复制所触摸的页面,并且您可能不希望这种开销,否则您可以从头开始将文件复制到 RAM。所以我还是要说:不要在这里使用strtok()

不过,基于<ctype.h> 中的函数的具有自己循环的解决方案非常简单。由于我想自己尝试一下,请参见此处的工作程序来演示它(相关部分是countCols() 函数):

#define _POSIX_C_SOURCE 200112L               
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int countCols(const char *line, size_t maxlen)
{
    int cols = 0;
    int incol = 0;
    const char *c = line;

    while (maxlen && (!isspace(*c) || isblank(*c)))
    {
        if (isblank(*c))
        {
            incol = 0;
        }
        else
        {
            if (!incol)
            {
                incol = 1;
                ++cols;
            }
        }
        ++c;
        --maxlen;
    }

    return cols;
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s [file]\n", argv[0]);
        return EXIT_FAILURE;
    }

    struct stat st;
    if (stat(argv[1], &st) < 0)
    {
        fprintf(stderr, "Could not stat `%s': %s\n", argv[1],
                strerror(errno));
        return EXIT_FAILURE;
    }

    int dataFd = open(argv[1], O_RDONLY);
    if (dataFd < 0)
    {
        fprintf(stderr, "Could not open `%s': %s\n", argv[1],
                strerror(errno));
        return EXIT_FAILURE;
    }

    char *data = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, dataFd, 0);
    if (data == MAP_FAILED)
    {
        close(dataFd);
        fprintf(stderr, "Could not mmap `%s': %s\n", argv[1],
                strerror(errno));
        return EXIT_FAILURE;
    }

    int cols = countCols(data, st.st_size);

    printf("found %d columns.\n", cols);

    munmap(data, st.st_size);

    return EXIT_SUCCESS;
}

【讨论】:

  • 感激不尽!那工作得很好。正如您所说,您是对的, strtok() 也给了我正确的答案。我看到该文件有空格分隔和制表符分隔的列。因此,使用 strtok() 捕获两者都解决了我的问题。但是,很高兴知道在这些情况下反对使用 strtok 的论点。您的解决方案简单而优雅,因为它避免了任何内置函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-17
  • 1970-01-01
相关资源
最近更新 更多