我正在尝试从文件中读取200,000条记录,然后使用tokenizer
来解析字符串并删除每个部分周围的引号。但与正常读取字符串相比,运行时间非常长。只需要25秒读取这些记录(每条记录0.0001秒????)。我的编程是否有任何问题,或者如果没有更快的方法吗?
int main()
{
int counter = 0;
std::string getcontent;
std::vector<std::string> line;
std::vector< std::vector<std::string> > lines;
boost::escaped_list_separator<char> sep( '\\', '*', '"' ) ;
boost::tokenizer<> tok(getcontent);
std::ifstream openfile ("test.txt");
if(openfile.is_open())
{
while(!openfile.eof())
{
getline(openfile,getcontent);
// THIS LINE TAKES A LOT OF TIME
boost::tokenizer<> tok(getcontent);
for (boost::tokenizer<>::iterator beg=tok.begin(); beg!=tok.end(); ++beg){
line.push_back(*beg);
}
lines.push_back(line);
line.clear();
counter++;
}
openfile.close();
}
else std::cout << "No such file" << std::endl;
return 0;
}
答案 0 :(得分:2)
而不是boost::tokenizer<> tok(getcontent);
,它构建了对boost::tokenizer
的每次调用的新getline
。使用assign
成员函数:
boost::escaped_list_separator<char> sep( '\\', '*', '"' ) ;
boost::tokenizer<boost::escaped_list_separator<char>> tok(getcontent, sep);
// Other code
while(getline(openfile,getcontent))
{
tok.assign(getcontent.begin(), getcontent.end()); // Use assign here
line.assign(tok.begin(), tok.end()); // Instead of for-loop
lines.push_back(line);
counter++;
}
看看是否有帮助。 另外,如果可能,请尝试事先分配矢量存储器。
答案 1 :(得分:2)
好的,从评论看来,你似乎想要一个尽可能快的解决方案。
以下是我要做的事情,以达到接近该要求的目的。
虽然你可能会得到一个内存池分配器来分配你的字符串,但STL并不是我的强项,所以我打算手工完成。要注意这不一定是C ++的方法。所以C ++ - 负责人可能会有点畏缩。有时,当你想要一些专门的东西时,你只需要这样做。
所以,你的数据文件大约是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容器的内存汇集起来,我预计这些分配器和容器本身的开销仍会比我给你的慢。很抱歉这个很长的答案。我想我只是喜欢这些东西。玩得开心,希望这会有所帮助。