我想读取一个文件,但它太大了,无法将其完全加载到内存中。
有没有办法在不加载到内存中的情况下阅读它?或者有更好的解决方案?
答案 0 :(得分:3)
我想读取一个文件,但它太大了,无法将其完全加载到内存中。
请注意,-in practice- 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大得多,但比单个磁盘小(但是,即使这并不总是如此;某些文件系统能够跨越多个物理磁盘,例如使用{{3技术)。
(在我的Linux桌面上有32G的RAM,最大的文件有69Gbytes,在ext4文件系统上有669G可用,总空间为780G,而我以前的文件确实超过了100GB)
您可能觉得有必要使用像LVM这样的数据库(或者是某些RDBMS的客户端,如sqlite等等),或者您可能对像{等索引文件的库感兴趣{3}}。当然,您也可以进行直接I / O操作(例如fseek
然后fread
或fwrite
,或lseek
然后read
或write
,或PostGreSQL或pwrite
...)。
答案 1 :(得分:3)
我需要内容来做校验和,所以我需要完整的消息
许多校验和库支持校验和的增量更新。例如,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
答案 2 :(得分:2)
一种方法是,如果问题是RAM,而不是虚拟地址空间,则是内存映射文件,POSIX系统上的via mmap
或Windows上的CreateFileMapping
/ MapViewOfFile
这可以让你看到看起来就像文件字节的原始数组一样,但是操作系统负责分页内容(如果你改变它们就把它们写回磁盘) 。当映射为只读时,它与仅malloc
- 内存块和fread
非常类似 - 填充它,但是:
malloc
内存时,必须将其写入交换,以便在您可能已在磁盘上超额订阅时增加磁盘流量性能方面,在默认设置下这可能会稍慢一些(因为没有内存压力,读取整个文件主要是保证它在内存中会被要求,而随机访问内存映射文件可能会触发尽管您可以posix_madvise
与POSIX_MADV_WILLNEED
(POSIX系统)或PrefetchVirtualMemory
(Windows 8及更高版本)使用{@ 3}}来提供第一次访问时填充每个页面的按需页面错误将需要整个文件,导致系统(通常)在后台将其分页,即使您正在访问它。在POSIX系统上,当不必要(或可能)分页整个文件时,可以使用其他advise
提示进行更细粒度的提示,例如:如果您从头到尾按顺序读取文件数据,则使用POSIX_MADV_SEQUENTIAL
通常会触发更积极的后续页面预取,从而增加他们到达内存时内存的几率。通过这样做,你可以获得两全其美的效果;您几乎可以立即开始访问数据,延迟访问尚未分页的页面,但操作系统将在后台为您预加载页面,因此您最终可以全速运行(同时仍然更具弹性内存压力,因为操作系统可以只删除干净的页面,而不是先将它们写入交换)。
这里的主要限制是虚拟地址空间。如果您使用的是32位系统,则可能仅限于(取决于现有地址空间的碎片程度)1-3 GB的连续地址空间,这意味着您必须映射该文件是块,并且无需随机系统调用,无法随时随地访问文件中的任何点。值得庆幸的是,在64位系统上,很少出现这种限制;即使是最受限制的64位系统(Windows 7),每个进程提供8 TB的用户虚拟地址空间,远远超过您可能遇到的大量浩大大部分文件(以及之后)版本将上限增加到128 TB。