将文件读取为缓冲区,并避免在读取之间分裂线

Reading file into buffer and avoiding splitting lines between reads

本文关键字:读取 之间 分裂 文件 缓冲区      更新时间:2023-10-16

我正在阅读Sehe在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或更少的字节,同时确保最后一个字节读取为newline字符(或eof)"?

对低水平IO的了解很少,想到的想法是

  • 我可以将fd"备份"到迭代之间的最新新线?
  • 我是否必须保留第二个缓冲区,持有当前行的副本?

这是一个比较测试。首先,让我们尝试简单的方法。只需使用标准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;
}