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

问题描述 投票:1回答:3

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

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

c file memory
3个回答
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

3
投票

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

请注意 - 练习 - files是你的abstraction通过operating system提供的file systems(所以不知何故是一种幻觉)。阅读Operating Systems: Three Easy Pieces(可免费下载)以了解有关操作系统的更多信息。文件可能非常大(即使它们中的大多数都很小),例如当前的笔记本电脑或台式机上有数十亿字节(在服务器上有很多terabytes,也许还有更多)。

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

在实践中,processvirtual 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)可以在很长的数据流上以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然后freadfwrite,或lseek然后readwrite,或pread(2)pwrite ......)。


2
投票

一种方法是,如果问题是RAM,而不是虚拟地址空间,则是内存映射文件,可以是POSIX系统上的via 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通常会触发后续页面的更积极的预取,从而增加它们在你到达它们时在内存中的几率。通过这样做,你可以获得两全其美的效果;您几乎可以立即开始访问数据,延迟访问尚未分页的页面,但操作系统将在后台为您预加载页面,因此您最终可以全速运行(同时仍然更具弹性内存压力,因为操作系统可以只删除干净的页面,而不是先将它们写入交换)。

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

© www.soinside.com 2019 - 2024. All rights reserved.