好的,从评论看来,您想要一个尽可能快的解决方案。
这是我为实现接近该要求而要做的事情。
虽然你可能会得到一个内存池分配器来分配你的字符串,但 STL 不是我的强项,所以我将手动完成。请注意,这不一定是 C++ 方法。所以 C++-heads 可能会有点畏缩。有时,当您想要一些专业的东西时,您只需要这样做。
因此,您的数据文件大约为 10 GB……将其分配在一个块中是一个坏主意。您的操作系统很可能会拒绝。但是可以把它分成一大堆相当大的块。也许这里有一个神奇的数字,但我们说大约 64MB。是寻呼专家的人可以在这里发表评论吗?我记得曾经读过一次,使用小于精确的页面大小倍数是很好的(尽管我不记得为什么),所以让我们撕掉几个 kB:
const size_t blockSize = 64 * 1048576 - 4096;
现在,一个结构来跟踪你的记忆怎么样?不妨把它列一个清单,这样你就可以把它们放在一起。
struct Block {
SBlock *next;
char *data; // Some APIs use data[1] so you can use the first element, but
// that's a hack that might not work on all compilers.
} SBlock;
是的,所以你需要分配一个块 - 你将分配一大块内存并使用第一个小块来存储一些信息。请注意,data
如果需要对齐内存,可以更改指针:
SBlock * NewBlock( size_t blockSize, SBlock *prev = NULL )
{
SBlock * b = (SBlock*)new char [sizeof(SBlock) + blockSize];
if( prev != NULL ) prev->next = b;
b->next = NULL;
b->data = (char*)(blocks + 1); // First char following struct
b->length = blockSize;
return b;
}
现在你要读...
FILE *infile = fopen( "mydata.csv", "rb" ); // Told you C++ers would hate me
SBlock *blocks = NULL;
SBlock *block = NULL;
size_t spilloverBytes = 0;
while( !feof(infile) ) {
// Allocate new block. If there was spillover, a new block will already
// be waiting so don't do anything.
if( spilloverBytes == 0 ) block = NewBlock( blockSize, block );
// Set list head.
if( blocks == NULL ) blocks = block;
// Read a block of data
size_t nBytesReq = block->length - spilloverBytes;
char* front = block->data + spilloverBytes;
size_t nBytes = fread( (void*)front, 1, nBytesReq, infile );
if( nBytes == 0 ) {
block->length = spilloverBytes;
break;
}
// Search backwards for a newline and treat all characters after that newline
// as spillover -- they will be copied into the next block.
char *back = front + nBytes - 1;
while( back > front && *back != '\n' ) back--;
back++;
spilloverBytes = block->length - (back - front);
block->length = back - block->data;
// Transfer that data to a new block and resize current block.
if( spilloverBytes > 0 ) {
block = NewBlock( blockSize, block );
memcpy( block->data, back, spilloverBytes );
}
}
fclose(infile);
好吧,就这样吧。你明白了。请注意,此时,您读取文件的速度可能比多次调用std::getline
. 如果您可以禁用任何缓存,您仍然可以变得更快。在 Windows 中,您可以使用CreateFile
API 并对其进行调整以实现真正的快速读取。因此,我之前关于可能对齐数据块(与磁盘扇区大小)的评论。不确定 Linux 或其他操作系统。
因此,这是一种将整个文件放入内存的复杂方法,但它足够简单,易于访问且具有适度的灵活性。希望我没有犯太多错误。现在你只想浏览你的块列表并开始索引它们。
我不打算在这里详细介绍,但总体思路是这样的。您可以通过在适当的位置闪现 NULL 值并跟踪每个标记的开始位置来就地标记化。
SBlock *block = blocks;
while( block ) {
char *c = block->data;
char *back = c + block->length;
char *token = NULL;
// Find first token
while( c != back ) {
if( c != '"' && c != '*' ** c != '\n' ) break;
c++;
}
token = c;
// Tokenise entire block
while( c != back ) {
switch( *c ) {
case '"':
// For speed, we assume all closing quotes have opening quotes. If
// we have closing quote without opening quote, this won't be correct
if( token != c) {
*c = 0;
token++;
}
break;
case '*':
// Record separator
*c = 0;
tokens.push_back(token); // You can do better than this...
token = c + 1;
break;
case '\n':
// Record and line separator
*c = 0;
tokens.push_back(token); // You can do better than this...
lines.push_back(tokens); // ... and WAY better than this...
tokens.clear(); // Arrrgh!
token = c + 1;
break;
}
c++;
}
// Next block.
block = block->next;
}
最后,您将在上面看到那些类似矢量的调用。现在,再次,如果你可以记忆你的向量,那是伟大而简单的。但再一次,我只是从不这样做,因为我发现直接使用内存更直观。您可以执行与我对文件块所做的类似的操作,但为数组(或列表)创建内存。您将所有令牌(只是 8 字节指针)添加到此内存区域并根据需要添加新的内存块。
您甚至可以制作一个小标题来跟踪其中一个令牌数组中有多少项。关键是永远不要计算一次你以后可以计算而无需额外成本的东西(即数组大小——你只需要在添加最后一个元素后计算它)。
你对线条再次做同样的事情。您所需要的只是一个指向标记块中相关部分的指针(如果您想要数组索引,如果一行吃进一个新块,您必须做溢出的事情)。
你最终会得到一个指向标记数组的行数组,它们直接指向你从文件中吸出的内存。虽然有一点内存浪费,但可能并不过分。这是您为快速编写代码所付出的代价。
我敢肯定它可以完美地包含在几个简单的类中,但我在这里把它原封不动地给了你。即使您创建了一堆 STL 容器的内存池,我预计这些分配器的开销以及容器本身仍然会使它比我给您的要慢。很抱歉,答案很长。我想我只是喜欢这些东西。玩得开心,希望这会有所帮助。