从文件中快速读取第n行的方法

Rem*_*i.b 5 c++ performance newline file

介绍

我有一个C++进程调用MyProcess我称之为nbLines时间,其中nbLines是一个大文件的行数,InputDataFile.txt在其中找到输入数据.例如电话

./MyProcess InputDataFile.txt 142
Run Code Online (Sandbox Code Playgroud)

通知MyProcess输入数据将142InputDataFile.txt文件的行中找到.

问题

问题是,InputDataFile.txt如此大(~150 GB),搜索正确行的时间不可忽略.灵感来自这篇文章,这是我的(可能不是最优的)代码

int line = 142;
int N = line - 1;
std::ifstream inputDataFile(filename.c_str());
std::string inputData;
for(int i = 0; i < N; ++i)
    std::getline(inputDataFile, inputData);

std::getline(inputDataFile,inputData);
Run Code Online (Sandbox Code Playgroud)

目标

我的目标是inputData更快地进行搜索MyProcess.

可能解决方案

将每行的第一个字符的索引与行号相匹配会很方便bash.这样,而不是给142MyProcess,我可以直接给感兴趣的第一个字符的索引.MyProcess然后可以直接跳转到此位置,而无需搜索和计算'\n'字符.然后它将读取数据,直到遇到'\n'字符.这样的事情可行吗?怎么能实现呢?

当然,我欢迎任何其他解决方案,这将减少导入这些输入数据的总计算时间.

Ale*_*cki 2

正如其他答案中所建议的,构建文件映射可能是一个好主意。我这样做的方式(以伪代码)是:

let offset be a unsigned 64 bit int =0;

for each line in the file 
    read the line
    write offset to a binary file (as 8 bytes rather as chars)
    offset += length of line in bytes
Run Code Online (Sandbox Code Playgroud)

现在您有一个“Map”文件,它是 64 位整数的列表(文件中的每一行一个)。要读取地图,您只需计算所需线路的条目在地图中的位置:

offset = desired_line_number * 8 // where line number starts at 0
offset2 = (desired_line_number+1) * 8

data_position1 = load bytes [offset through offset + 8] as a 64bit int from map
data_position2 = load bytes [offset2 through offset2 + 8] as a 64bit int from map

data = load bytes[data_position1 through data_position2-1] as a string from data.
Run Code Online (Sandbox Code Playgroud)

这个想法是,您读取一次数据文件,并将字节偏移量记录在文件中每行开始的位置,然后使用固定大小的整数类型将偏移量顺序存储在二进制文件中。地图文件的大小应为number_of_lines * sizeof(integer_type_used). 然后,您只需通过计算存储行号偏移量的位置的偏移量来查找映射文件,并读取该偏移量以及下一行偏移量。从那里您可以得到数据应位于的数字范围(以字节为单位)。

例子:

数据:

hello\n 
world\n
(\n newline at end of file)
Run Code Online (Sandbox Code Playgroud)

创建地图。

Map:每个分组[数字]将代表文件中的8字节长度

[0][7][14]
//or in binary
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000111
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001110
Run Code Online (Sandbox Code Playgroud)

现在假设我想要第 2 行:

line offset = 2-1 * 8 // offset is 8 
Run Code Online (Sandbox Code Playgroud)

因为我们使用的是基于 0 的系统,所以它是文件中的第 9 个字节。因此,数字由字节 9 - 17 组成,它们是:

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000111
//or as decimal
7
Run Code Online (Sandbox Code Playgroud)

现在我们知道 out 行应该从数据文件中的偏移量 7 开始(该偏移量以 1 为基数,如果我们从 0 开始计数,则为 6)。

然后我们执行相同的过程来获取下一行的起始偏移量,即 14。

最后,我们查找字节范围 7-14(基数 1、6-13、基数 0)并将其存储为字符串并获取world\n

C++实现:

#include <iostream>
#include <fstream>

int main(int argc, const char * argv[]) {
    std::string filename = "path/to/input.txt";

    std::ifstream inputFile(filename.c_str(),std::ios::binary);
    std::ofstream outfile("path/to/map/file.bin",std::ios::binary|std::ios::ate);

    if (!inputFile.is_open() || !outfile.is_open()) {
        //use better error handling than this
        throw std::runtime_error("Error opening files");
    }


    std::string inputData;
    std::size_t offset = 0;
    while(std::getline(inputFile, inputData)){
        //write the offset as binary
        outfile.write((const char*)&offset, sizeof(offset));
        //increment the counter
        offset+=inputData.length()+2;
        //add one becuase getline strips the \n and add one to make the index represent the next line
    }
    outfile.close();

    offset=0;

    //from here on we are reading the map
    std::ifstream inmap("/Users/alexanderzywicki/Documents/xcode/textsearch/textsearch/map",std::ios::binary);
    std::size_t line = 2;//your chosen line number
    std::size_t idx = (line-1) * sizeof(offset); //the calculated offset
    //seek into the map
    inmap.seekg(idx);
    //read the binary at that location
    inmap.read((char*)&offset, sizeof(offset));
    std::cout<<offset<<std::endl;

    //from here you just need to lookup from the data file in the same manor


    return 0;
}
Run Code Online (Sandbox Code Playgroud)