如何在C++中读取大文件

21

如果我有一个巨大的文件(例如1TB或任何不能容纳在RAM中的大小。该文件存储在磁盘上)。它由空格分隔。而我的RAM只有8GB。我可以使用ifstream读取该文件吗?如果不行,如何读取文件块(例如4GB)?


它是如何分隔的?它是基于行的文本吗?你能一次读取一行吗? - nicomp
2
@nicomp 我怀疑一个文本文件的大小可以达到1TB。 - Oleg Andriyanov
3
你有尝试阅读过吗?如果没有,为什么?如果有,那是什么没起作用? - Cheers and hth. - Alf
1
@OlegAndriyanov,需要我给你发送一个吗? - nicomp
1
@nicomp 好啊,为什么不呢。/dev/null 似乎是一个很好的名称来存储它。 - Oleg Andriyanov
显示剩余7条评论
4个回答

33

你可以做一些事情。

首先,打开一个比你的内存大的文件没有问题。你不能做到的是将整个文件实时复制到内存中。最好的方法是找到一种读取每次只读取几块并处理它们的方式。你可以使用 ifstream 来实现这个目的(例如使用ifstream.read)。分配大约一兆字节的内存,将文件的第一个兆字节读入其中,反复执行此操作:

ifstream bigFile("mybigfile.dat");
constexpr size_t bufferSize = 1024 * 1024;
unique_ptr<char[]> buffer(new char[bufferSize]);
while (bigFile)
{
    bigFile.read(buffer.get(), bufferSize);
    // process data in buffer
}

另一种解决方案是将文件映射到内存中。大多数操作系统即使文件大小超过您拥有的物理内存量,也允许您将文件映射到内存中。这是因为操作系统知道与文件相关联的每个内存页面都可以按需映射和取消映射:当您的程序需要特定页面时,操作系统会将其从文件中读取到进程的内存中,并交换掉已经有一段时间没有使用的页面。

但是,这只适用于文件小于您的进程可以理论上使用的最大内存量的情况。在64位进程中使用1TB文件不是问题,但在32位进程中就行不通了。

此外,请注意您召唤的精神。将文件映射到内存中并不等同于从文件中读取数据。如果文件突然被其他程序截断,您的程序很可能会崩溃。如果您修改了数据,则可能会因为无法保存回磁盘而耗尽内存。此外,您操作系统用于分页内存的算法可能无法以明显优势的方式运行。由于存在这些不确定性,仅当使用第一种解决方案无法工作时,才应考虑将文件映射到内存中。

在Linux/OS X上,您需要使用mmap。在Windows上,您需要打开一个文件,然后使用CreateFileMapping,然后使用MapViewOfFile


1
旁注:常见错误;未测试流操作:while (bigFile) { bigFile.read(...); ... } - user2249683
如果bigFile.read()读取的数据量小于请求的数据量,则nread的数量在bigFile.gcount()中。我们可以使用这个nread值来索引缓冲区并继续读取循环吗? - daparic

8

我相信您不必将所有文件保存在内存中。通常,人们希望按块读取和处理文件。如果您想使用 ifstream,您可以这样做:

ifstream is("/path/to/file");
char buf[4096];
do {
    is.read(buf, sizeof(buf));
    process_chunk(buf, is.gcount());
} while(is);

is.read()有时只能读取少于4096字节,这种情况是否可能? - daparic
@typelogic 是的,如果已经到达文件末尾或发生某种读取错误,则 read 可以读取比请求的字节数更少的字节。请参见 https://en.cppreference.com/w/cpp/io/basic_istream/read - Oleg Andriyanov
抱歉,我的主要问题是在中断期间nread小于请求时是否可以继续读取循环。ifstream对象是否仍然正常?我之所以问这个问题,是因为在C语言中,在循环内部发生中断的情况非常普遍,而read会继续执行。 - daparic
@typelogic 短读取并不直接表示错误。如果您所说的“正常”是指“我可以继续从流中读取”,那么我认为您应该测试 fail() 的返回值。此外,如果您关心精确的错误处理和诸如信号中断之类的低级别事项,您可能希望坚持使用 C 和原始的 POSIX read() —— API 和文档更加清晰和详细。 - Oleg Andriyanov
1
使用do...while(is)相比仅使用while(is)有什么好处? - starriet
@starriet 你说得对,我认为没有任何好处。 - Oleg Andriyanov

3
更先进的方法是使用平台特定的API将文件映射到内存中,而不是将整个文件或其块读入内存中:
在Windows下:CreateFileMapping(),MapViewOfFile()
在Linux下:open(2)/creat(2),shm_open,mmap 您需要编译64位应用程序以使其正常工作。
有关更多详细信息,请参见此处:CreateFileMapping,MapViewOfFile,如何避免占用系统内存

1
你可以使用 fread
char buffer[size];
fread(buffer, size, sizeof(char), fp);

或者,如果您想使用C++ fstreams,您可以像buratino所说的那样使用read

同时请注意,您可以打开任何大小的文件,关键是要以适合RAM的块打开并读取它。


1
他问到了ifstream。我认为更相关的函数调用应该是read - buratino
我阅读了fread文档。所以如果我使用FILE * pFile; pFile = fopen ( "myfile.txt" , "rb" );而myfile.txt不能适应RAM,我仍然可以用这种方式打开它吗? - ZigZagZebra
2
fopen 不会将文件加载到内存中,因此您应该能够轻松完成它。 - marian0
1
C文件IO有2GB的限制吗? - 0xB00B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接