【问题标题】:Is it possible to read a file without loading it into memory?是否可以在不将文件加载到内存的情况下读取文件?
【发布时间】:2018-01-03 04:45:05
【问题描述】:

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

有没有办法在不加载到内存的情况下读取它?还是有更好的解决方案?

【问题讨论】:

  • fread() 指定你想读多少,下一个 fread() 会在上一次调用结束时继续,一直继续直到你到达文件末尾
  • @MartinBeckett 您应该将该评论作为答案。
  • 我需要内容来做校验,所以我需要完整的消息
  • @RaulSanMartin,校验和可以更新:md5(a+b) = md5(a).update(b)。因此,您不需要加载整个文件。
  • 我很惊讶你这么问。直观地说,文件可能非常大,比内存还大……而且情况一直如此。谁告诉你文件需要足够小才能放入内存?

标签: c file memory


【解决方案1】:

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

请注意-在实践中-files 是由您的operating systemfile systems 提供的abstraction(所以某种程度上是一种错觉)。阅读Operating Systems: Three Easy Pieces(可免费下载)以了解有关操作系统的更多信息。文件可能非常大(即使它们中的大多数都很小),例如当前的笔记本电脑或台式机上有几十 GB(在服务器上还有很多 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

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

当然,您可以读取和写入某些文件的部分块(例如使用freadfwritefseek,或者较低级别的系统调用read(2)write(2)lseek(2), ...)。出于性能原因,最好使用大缓冲区(至少几千字节)。在实践中,大多数checksums(或cryptographic hash functions)都可以在很长的数据流上逐块计算。

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

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

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

您可能会发现使用sqlite 之类的数据库是值得的(或者是PostGreSQL 等一些RDBMS 的客户端),或者您可能对gdbm 之类的索引文件库感兴趣。当然你也可以直接进行 I/O 操作(例如fseek 然后freadfwrite,或lseek 然后readwrite,或pread(2)pwrite ...) .

【讨论】:

    【解决方案2】:

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

    许多校验和库支持校验和的增量更新。例如,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
    

    【讨论】:

    • 这得到了 OP 的问题 Y 的 X。
    【解决方案3】:

    如果问题是 RAM,而不是虚拟地址空间,那么一种方法是内存映射文件,在 POSIX 系统上是 via mmap,在 Windows 上是 CreateFileMapping/MapViewOfFile

    这可以让你得到 看起来 像文件字节的原始数组的东西,但是操作系统负责在你进行时将内容分页(如果你改变它们,则将它们写回磁盘) .当映射为只读时,它与malloc-ing 一块内存和fread-ing 填充它非常相似,但是:

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

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

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

    【讨论】:

      猜你喜欢
      • 2011-10-15
      • 2012-11-02
      • 1970-01-01
      • 1970-01-01
      • 2012-06-27
      • 2019-05-30
      • 2019-12-17
      • 1970-01-01
      • 2014-09-15
      相关资源
      最近更新 更多