我想读取一个文件,但它太大了,无法将其完全加载到内存中。
有没有办法读取它而不将其加载到内存中?还是有更好的解决方案?
我需要内容来做校验和,所以我需要完整的消息
许多校验和库支持校验和的增量更新。例如,GLib有g_checksum_update()
。因此,您可以使用fread
一次读取一个块文件,并在阅读时更新校验和。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <glib.h>
int main(void) {
char filename[] = "test.txt";
// Create a SHA256 checksum
GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256);
if( sum == NULL ) {
fprintf(stderr, "Could not create checksum.\n");
exit(1);
}
// Open the file we'll be checksuming.
FILE *fp = fopen( filename, "rb" );
if( fp == NULL ) {
fprintf(stderr, "Could not open %s: %s.\n", filename, strerror(errno));
exit(1);
}
// Read one buffer full at a time (BUFSIZ is from stdio.h)
// and update the checksum.
unsigned char buf[BUFSIZ];
size_t size_read = 0;
while( (size_read = fread(buf, 1, sizeof(buf), fp)) != 0 ) {
// Update the checksum
g_checksum_update(sum, buf, (gssize)size_read);
}
// Print the checksum.
printf("%s %s\n", g_checksum_get_string(sum), filename);
}
我们可以通过将结果与sha256sum
进行比较来检查它是否有效。
$ ./test
0c46af5bce717d706cc44e8c60dde57dbc13ad8106a8e056122a39175e2caef8 test.txt
$ sha256sum test.txt
0c46af5bce717d706cc44e8c60dde57dbc13ad8106a8e056122a39175e2caef8 test.txt
我想读取一个文件,但它太大了,无法将其完全加载到内存中。
请注意 - 练习 - files是你的abstraction通过operating system提供的file systems(所以不知何故是一种幻觉)。阅读Operating Systems: Three Easy Pieces(可免费下载)以了解有关操作系统的更多信息。文件可能非常大(即使它们中的大多数都很小),例如当前的笔记本电脑或台式机上有数十亿字节(在服务器上有很多terabytes,也许还有更多)。
你没有定义什么是内存,C11标准n1570以不同的方式使用该词,说到§3.14中的内存位置,以及§7.22.3中的内存管理功能......
在实践中,process有virtual address space,与virtual memory有关。
在许多operating systems上 - 尤其是Linux和POSIX-你可以用mmap(2)和相关的system calls来改变虚拟地址空间,你可以使用memory-mapped files。
有没有办法读取它而不将其加载到内存中?
当然,您可以读取和写入某些文件的部分块(例如,使用fread,fwrite,fseek或更低级别的系统调用read(2),write(2),lseek(2),...)。出于性能原因,最好使用大缓冲区(至少几千字节)。在实践中,大多数checksums(或cryptographic hash functions)可以在很长的数据流上以chunkwise方式计算。
许多库都是在这些原语之上构建的(通过块进行直接IO)。例如,sqlite数据库库能够处理many terabytes的数据库文件(超过可用的RAM)。你可以使用RDBMS(它们是用C或C ++编写的软件)
所以当然你可以处理大于可用RAM的文件,并按块(或“记录”)读取或写入它们,至少从20世纪60年代开始就是如此。我甚至可以直观地说,文件可以(通常)比RAM大得多,但比单个磁盘小(但是,即使这并非总是如此;某些文件系统能够跨越多个物理磁盘,例如使用LVM技术) 。
(在我的Linux桌面上有32G的RAM,最大的文件有69Gbytes,在ext4文件系统上有669G可用和780G的总空间,而我在过去的文件中确实有超过100GB的文件)
您可能觉得有必要使用像sqlite这样的数据库(或者像某些RDBMS的客户端,如PostGreSQL等等),或者您可能对像gdbm这样的索引文件的库感兴趣。当然你也可以做直接I / O操作(例如fseek
然后fread
或fwrite
,或lseek
然后read
或write
,或pread(2)或pwrite
......)。
一种方法是,如果问题是RAM,而不是虚拟地址空间,则是内存映射文件,可以是POSIX系统上的via mmap
,也可以是Windows上的CreateFileMapping
/ MapViewOfFile
。
这可以让你看起来像文件字节的原始数组,但操作系统负责分页内容(并在你改变时将它们写回磁盘)。当映射为只读时,它非常类似于malloc
-ing一块内存和fread
-ing来填充它,但是:
malloc
-ed内存时,必须将其写出来进行交换,以便在磁盘上可能已超额订阅时增加磁盘流量性能方面,在默认设置下,这可能会略微变慢(因为,在没有内存压力的情况下,读取整个文件主要是保证在被要求时它将在内存中,而随机访问内存映射文件可能会按需触发虽然你可以使用posix_madvise
和POSIX_MADV_WILLNEED
(POSIX系统)或PrefetchVirtualMemory
(Windows 8和更高版本)来提供一个提示,即需要整个文件,导致系统(通常)页面它在后台,即使你正在访问它。在POSIX系统上,当不必(或可能)分页整个文件时,其他advise
提示可用于更细粒度的提示,例如,如果你从头到尾按顺序读取文件数据,那么使用POSIX_MADV_SEQUENTIAL
通常会触发后续页面的更积极的预取,从而增加它们在你到达它们时在内存中的几率。通过这样做,你可以获得两全其美的效果;您几乎可以立即开始访问数据,延迟访问尚未分页的页面,但操作系统将在后台为您预加载页面,因此您最终可以全速运行(同时仍然更具弹性内存压力,因为操作系统可以只删除干净的页面,而不是先将它们写入交换)。
这里的主要限制是虚拟地址空间。如果您使用的是32位系统,则可能仅限于(取决于现有地址空间的碎片程度)1-3 GB的连续地址空间,这意味着您必须以块的形式映射文件,并且无需额外的系统调用,任何时候都无法按需随机访问文件中的任何一点。值得庆幸的是,在64位系统上,很少出现这种限制;即使是最受限制的64位系统(Windows 7),每个进程提供8 TB的用户虚拟地址空间,远远大于您可能遇到的绝大多数文件(后来的版本将上限增加到128 TB)。