我现在想处理硬盘上文件的每一行。是先将整个文件加载然后基于换行符(使用boost)进行分割,还是最好使用getline()
?我的问题是当调用getline()
时,它是否会读取单个行(导致多次硬盘访问)或读取整个文件并逐行给出?
我现在想处理硬盘上文件的每一行。是先将整个文件加载然后基于换行符(使用boost)进行分割,还是最好使用getline()
?我的问题是当调用getline()
时,它是否会读取单个行(导致多次硬盘访问)或读取整个文件并逐行给出?
getline
会在 C 库的深处调用系统调用 read()
。它被调用的次数以及如何调用取决于 C 库的设计。但很可能在一次读取一个文件行和一次性读取整个文件上没有明显区别,因为底层操作系统会每次读取(至少)一个磁盘块,很可能是一个“页面”(4KB)或更多。$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.98 (user:1.83 system: 0.14)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.68 system: 0.389)
Lines=24812608
Wallclock time for readwhole is 2.52 (user:1.79 system: 0.723)
$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.96 (user:1.83 system: 0.12)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.67 system: 0.392)
Lines=24812608
Wallclock time for readwhole is 2.48 (user:1.76 system: 0.707)
这里有三种不同的函数用于读取文件(当然还有一些测量时间等方面的代码,但为了缩小本文的大小,我选择不全部发布 - 而且我还尝试过更改顺序以查看是否有任何差异,因此上面的结果与这里的函数顺序不同)
void func_readwhole(const char *name)
{
string fullname = string("bigfile_") + name;
ifstream f(fullname.c_str());
if (!f)
{
cerr << "could not open file for " << fullname << endl;
exit(1);
}
f.seekg(0, ios::end);
streampos size = f.tellg();
f.seekg(0, ios::beg);
char* buffer = new char[size];
f.read(buffer, size);
if (f.gcount() != size)
{
cerr << "Read failed ...\n";
exit(1);
}
stringstream ss;
ss.rdbuf()->pubsetbuf(buffer, size);
int lines = 0;
string str;
while(getline(ss, str))
{
lines++;
}
f.close();
cout << "Lines=" << lines << endl;
delete [] buffer;
}
void func_getline(const char *name)
{
string fullname = string("bigfile_") + name;
ifstream f(fullname.c_str());
if (!f)
{
cerr << "could not open file for " << fullname << endl;
exit(1);
}
string str;
int lines = 0;
while(getline(f, str))
{
lines++;
}
cout << "Lines=" << lines << endl;
f.close();
}
void func_mmap(const char *name)
{
char *buffer;
string fullname = string("bigfile_") + name;
int f = open(fullname.c_str(), O_RDONLY);
off_t size = lseek(f, 0, SEEK_END);
lseek(f, 0, SEEK_SET);
buffer = (char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, f, 0);
stringstream ss;
ss.rdbuf()->pubsetbuf(buffer, size);
int lines = 0;
string str;
while(getline(ss, str))
{
lines++;
}
munmap(buffer, size);
cout << "Lines=" << lines << endl;
}
fstreams已经有了合理的缓冲区。操作系统对硬盘的底层访问也有了合理的缓冲区。硬盘本身也有一个合理的缓冲区。如果您逐行读取文件,或者逐个字符地读取文件,那么您肯定不会触发更多的硬盘访问。
因此,没有必要将整个文件加载到一个大缓冲区中并在该缓冲区上进行操作,因为它已经在缓冲区中了。通常也没有必要一次缓冲一行。为什么要分配内存来缓冲已经在ifstream中缓冲的字符串呢?如果可以的话,直接在流上工作,不要把所有东西从一个缓冲区扔到另一个缓冲区。除非它支持可读性和/或您的分析器告诉您磁盘访问显著地减慢了程序速度。
mmap
映射文件,然后使用基于迭代器对的自定义字符串类来表示字符串,而不移动任何数据,则速度可能会更快。然而,这需要付出很多努力,除非真的非常必要,否则我不会费心去做。 - James Kanzegetline
)将被缓冲,以至于您不会注意到显着的差异。如果可以容纳在内存中,最好获取所有数据,因为每当您请求I/O时,程序会失去处理并放入等待队列。
然而,如果文件大小很大,那么最好一次读取所需处理的尽可能多的数据。因为较大的读操作将比较小的读操作花费更多时间来完成。CPU进程切换时间远小于整个文件读取时间。