将文件读入缓冲区并避免在读取之间分割线

时间:2017-11-24 19:19:16

标签: c++ performance

我正在阅读sehe's answer,以便在C ++中快速阅读文本,看起来像这样。

static uintmax_t wc(char const *fname)
{
    static const auto BUFFER_SIZE = 16*1024;
    int fd = open(fname, O_RDONLY);
    if(fd == -1)
        handle_error("open");

    /* Advise the kernel of our access pattern.  */
    posix_fadvise(fd, 0, 0, 1);  // FDADVICE_SEQUENTIAL

    char buf[BUFFER_SIZE + 1];
    uintmax_t lines = 0;

    while(size_t bytes_read = read(fd, buf, BUFFER_SIZE))
    {
        if(bytes_read == (size_t)-1)
            handle_error("read failed");
        if (!bytes_read)
            break;

        for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p)
            ++lines;
    }

    return lines;
}

这很酷,但我想知道当我们不处理像计算换行符这样的字符操作但是想要对每行数据进行操作时是否可以采用类似的方法。比方说,我有一个双打文件,并且已经在每一行上使用了一些函数parse_line_to_double

12.44243
4242.910
...

也就是说,如何将BUFFER_SIZE字节读入我的缓冲区但是避免拆分最后一行读取?实际上,我是否可以询问"给我BUFFER_SIZE个或更少的字节,同时确保读取的最后一个字节是换行符(或EOF)"?

对此类低级别IO非常了解,想到的想法是

  • 我可以"备份" fd到迭代之间的最新换行符?
  • 我是否必须保留第二个缓冲区,保持当前行的副本一直被读取?

1 个答案:

答案 0 :(得分:0)

这是一个比较测试。首先,让我们尝试简单的方法。只需使用标准C ++函数读取文件:

#include <iostream>
#include <string>  
#include <fstream> //std::ifstream
#include <sstream> //std::stringstream

uintmax_t test1(char const *fname)
{
    std::ifstream fin(fname);
    if(!fin) return 0;
    uintmax_t lines = 0;
    std::string str;
    double value;
    while(fin >> value)
    {
        //std::cout << value << "\n";
        lines++;
    }
    return lines;
}

接下来,使用std::stringstream,这大约快了2.5倍:

uintmax_t test2(char const *fname)
{
    std::ifstream fin(fname);
    if(!fin) return 0;
    uintmax_t lines = 0;
    std::string str;
    double value;
    std::stringstream ss;
    ss << fin.rdbuf();
    while(ss >> value)
        lines++;
    return lines;
}

接下来,让我们将整个文件读入内存。只要文件小于1 GiB左右,这就没问题了。假设每行都有double值,让我们提取该值。 test3更复杂,更不灵活,并且不比test2更快:

uintmax_t test3(char const *fname)
{
    std::ifstream fin(fname, std::ios::binary);
    if(!fin) return 0;

    fin.seekg(0, std::ios::end);
    size_t filesize = (size_t)fin.tellg();
    fin.seekg(0);
    std::string str(filesize, 0);
    fin.read(&str[0], filesize);

    double value;
    uintmax_t lines = 0;
    size_t beg = 0;
    size_t i;
    size_t len = str.size();
    for(i = 0; i < len; i++)
    {
        if(str[i] == '\n' || i == len - 1)
        {
            try
            {
                value = std::stod(str.substr(beg, i - beg));
                //std::cout << value << "\n";
                beg = i + 1;
                lines++;
            }
            catch(...)
            {
            }
        }
    }
    return lines;
}

为了与问题中的wc函数进行比较,让我们将整个文件读入内存并仅计算行数。这比wc(正如预期的那样)运行得快一点,表明不需要额外的优化

uintmax_t test_countlines(char const *fname)
{
    std::ifstream fin(fname, std::ios::binary);
    if(!fin) return 0;
    fin.seekg(0, std::ios::end);
    size_t filesize = (size_t)fin.tellg();
    fin.seekg(0);
    std::string str(filesize, 0);
    fin.read(&str[0], filesize);
    uintmax_t lines = 0;
    for(auto &c : str)
        if(c == '\n')
            lines++;
    return lines;
}