是否可以读取文件而不将其加载到内存中?

时间:2018-01-02 17:49:44

标签: c file memory

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

有没有办法在不加载到内存中的情况下阅读它?或者有更好的解决方案?

3 个答案:

答案 0 :(得分:3)

  

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

请注意,-in practice- files是您abstractionoperating system提供的file systems(因此有点幻觉)。阅读Operating Systems: Three Easy Pieces(可免费下载)以了解有关操作系统的更多信息。文件可能非常大(即使它们中的大多数都很小),例如当前笔记本电脑或台式机上有许多几十亿字节(服务器上有许多terabytes,可能更多)。

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

实际上,processvirtual address spacevirtual memory有关。

在许多operating systems - 非常Linux和POSIX-上,您可以使用mmap(2)和相关的system calls更改虚拟地址空间,并且可以使用memory-mapped files

  

有没有办法在不加载到内存中的情况下阅读它?

当然,您可以读取和写入某个文件的部分块(例如,使用freadfwritefseek或更低级别的系统调用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然后freadfwrite,或lseek然后readwrite,或PostGreSQLpwrite ...)。

答案 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非常类似 - 填充它,但是:

  1. 它是懒惰的:对于一个1 GB的文件,在你可以使用它的任何部分之前,你不会等待5-30秒来读取整个内容,相反,你只是支付访问每个页面的费用(有时,操作系统会在后台预读,因此您甚至不必等待每页加载)
  2. 在记忆压力下它的反应更好;如果你的内存不足,操作系统可以从内存中删除干净的页面,而不必将它们写入交换,知道它可以在需要时从文件中的黄金副本中将它们重新登录;使用malloc内存时,必须将其写入交换,以便在您可能已在磁盘上超额订阅时增加磁盘流量
  3. 性能方面,在默认设置下这可能会稍慢一些(因为没有内存压力,读取整个文件主要是保证它在内存中会被要求,而随机访问内存映射文件可能会触发尽管您可以posix_madvisePOSIX_MADV_WILLNEED(POSIX系统)或PrefetchVirtualMemory(Windows 8及更高版本)使用{@ 3}}来提供第一次访问时填充每个页面的按需页面错误将需要整个文件,导致系统(通常)在后台将其分页,即使您正在访问它。在POSIX系统上,当不必要(或可能)分页整个文件时,可以使用其他advise提示进行更细粒度的提示,例如:如果您从头到尾按顺序读取文件数据,则使用POSIX_MADV_SEQUENTIAL通常会触发更积极的后续页面预取,从而增加他们到达内存时内存的几率。通过这样做,你可以获得两全其美的效果;您几乎可以立即开始访问数据,延迟访问尚未分页的页面,但操作系统将在后台为您预加载页面,因此您最终可以全速运行(同时仍然更具弹性内存压力,因为操作系统可以只删除干净的页面,而不是先将它们写入交换)。

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