是否可以在不将其加载到内存的情况下读取文件?

我想读取一个文件,但它太大了,无法将其完全加载到内存中。

有没有办法读取它而不将其加载到内存中? 还是有更好的解决方案?

我需要内容来做校验和,所以我需要完整的消息

许多校验和库支持校验和的增量更新。 例如,GLib具有g_checksum_update() 。 因此,您可以使用fread一次读取一个块文件,并在阅读时更新校验和。

 #include  #include  #include  #include  #include  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 

我想读取一个文件,但它太大了,无法将其完全加载到内存中。

请注意-in practice- 文件是操作系统通过文件系统提供的抽象 (因此不知何故是一种幻觉)。 阅读操作系统:三个简单的部分 (可免费下载),以了解有关操作系统的更多信息。 文件可能非常大(即使它们中的大多数都很小),例如当前笔记本电脑或台式机上的数十亿字节(服务器上的许多兆兆字节 ,甚至更多)。

你没有定义什么是内存,C11标准n1570以不同的方式使用该字,说到§3.14中的内存位置,以及§7.22.3中的内存管理function……

实际上, 进程具有与虚拟内存相关的虚拟地址空间 。

在许多操作系统上 -尤其是Linux和POSIX-您可以使用mmap(2)和相关的系统调用来更改虚拟地址空间,并且可以使用内存映射文件 。

有没有办法读取它而不将其加载到内存中?

当然,您可以读取和写入某些文件的部分块(例如,使用fread , fwrite , fseek或更低级别的系统调用read(2) , write(2) , lseek(2) ,…)。 出于性能原因,最好使用大缓冲区(至少几千字节)。 实际上,大多数校验和 (或加密散列函数 )可以在非常长的数据流上以chunkwise方式计算。

许多库都是在这些原语之上构建的(通过块进行直接IO)。 例如, sqlite数据库库能够处理许多兆兆字节 (超过可用RAM)的数据库文件。 你可以使用RDBMS (它们是用C或C ++编写的软件)

所以当然你可以处理大于可用RAM的文件,并按块(或“记录”)读取或写入它们,至少从20世纪60年代开始就是如此。 我甚至可以直观地说,文件可以(通常)比RAM大得多,但比单个磁盘小(但是,即使这并非总是如此;某些文件系统能够跨越多个物理磁盘,例如使用LVM技术) 。

(在我的Linux桌面上使用32GB的RAM,最大的文件有69Gbytes,在ext4文件系统上有669G可用和780G的总空间,而我在过去的文件中确实有超过100GB的文件)

您可能觉得有必要使用像sqlite这样的数据库(或者像某些RDBMS的客户端,如PostGreSQL等等),或者您可能对像gdbm这样的索引文件的库感兴趣。 当然你也可以做直接的I / O操作(例如fseek然后freadfwrite ,或lseek然后readwrite ,或pread(2)或pwrite …)。

一种方法是,如果问题是RAM,而不是虚拟地址空间,则是通过 POSIX系统上的mmap或Windows上的CreateFileMapping / MapViewOfFile映射文件的内存。

这可以让你看起来像文件字节的原始数组,但操作系统负责分页内容(并在你改变时将它们写回磁盘)。 映射为只读时,它非常类似于malloc -ing一块内存和fread -ing来填充它,但是:

  1. 它是懒惰的:对于一个1 GB的文件,你没有等待5-30秒的时间来读取整个内容,然后才能使用它的任何部分,相反,你只需要为访问时的每个页面付费(有时候,OS将在后台预读,所以你甚至不必等待每页加载)
  2. 它在记忆压力下反应更好; 如果你的内存不足,操作系统可以从内存中删除干净的页面,而不必将它们写入交换,知道它可以在需要时从文件中的黄金副本中将它们重新登录; 使用malloc -ed内存时,它必须将其写出来进行交换,在磁盘上可能已超额预订时增加磁盘流量

性能方面,在默认设置下,这可能会略微变慢(因为,没有内存压力,读取整个文件主要是保证它在内存中会被要求,而随机访问内存映射文件可能会按需触发尽管您可以将posix_madvisePOSIX_MADV_WILLNEED (POSIX系统)或PrefetchVirtualMemory (Windows 8及更高版本)一起使用以提示需要整个文件的提示,从而导致系统(通常)页面出现页面错误以在第一次访问时填充每个页面)它在后台,即使你正在访问它。 在POSIX系统上,当不需要(或可能)分页整个文件时,可以使用其他advise提示进行更细粒度的提示,例如,如果您POSIX_MADV_SEQUENTIAL按顺序读取文件数据,则使用POSIX_MADV_SEQUENTIAL通常触发更积极的后续页面预取,增加了它们到达内存时的内存几率。 通过这样做,你可以获得两全其美的效果; 您几乎可以立即开始访问数据,延迟访问尚未分页的页面,但操作系统将在后台为您预加载页面,因此您最终可以全速运行(同时仍然更具弹性内存压力,因为操作系统可以只删除干净的页面,而不是先将它们写入交换)。

这里的主要限制是虚拟地址空间。 如果您使用的是32位系统,则可能仅限于(取决于现有地址空间的碎片程度)1-3 GB的连续地址空间,这意味着您必须以块的forms映射文件,并且无需额外的系统调用,任何时候都无法按需随机访问文件中的任何一点。 值得庆幸的是,在64位系统上,很少出现这种限制; 即使是最受限制的64位系统(Windows 7),每个进程提供8 TB的用户虚拟地址空间,远远大于您可能遇到的绝大多数文件(后来的版本将上限增加到128 TB)。