C:通过mmap读取时解析文件以获得列数

C: Parse a file to obtain number of columns while reading by mmap

我有如下文件:

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

我想return这个文件的列数。我正在用 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   

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

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

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

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

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

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

    但是,strtok 会覆盖它找到的列分隔符,因此如果您修复了 strtok 调用以将 \n 作为分隔符包含在内,您将无法再告诉线在哪里结束。这可能对您的代码很重要,这也是 strtok 在这种情况下不是合适工具的关键原因。 Gnu strtok 联机帮助页(man strtok,已强调)提供了关于这个问题的警告(在最后的 BUGS 部分):

    Be cautious when using these functions. If you do use them, note that:

    • These functions modify their first argument.

    • These functions cannot be used on constant strings.

    • The identity of the delimiting byte is lost.

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

我的评论实际上是不正确的,因为你使用 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;
}