使用c在linux上读取和写入块

时间:2012-05-10 03:37:31

标签: c linux

我有一个ASCII文件,其中每一行都包含一个可变长度的记录。例如

Record-1:15 characters
Record-2:200 characters
Record-3:500 characters
...
...
Record-n: X characters

由于文件大小约为10GB,我想以块的形式阅读记录。一旦读取,我需要转换它们,将它们写入二进制格式的另一个文件。

所以,为了阅读,我的第一反应是创建一个char数组,如

FILE *stream; 
char buffer[104857600]; //100 MB char array
fread(buffer, sizeof(buffer), 104857600, stream);
  1. 假设linux会发出一个系统调用并获取整个100MB,这是正确的吗?
  2. 由于记录以新行分隔,我会逐​​个字符地搜索缓冲区中的新行字符并重建每条记录。
  3. 我的问题是,这是我应该如何读取块或者是否有更好的替代方法来读取块中的数据并重新构建每条记录?是否有另一种方法可以在一次调用中从ASCII文件中读取x个可变大小的行?

    接下来在写作期间,我也这样做。我有一个写字符缓冲区,我传递给fwrite在一次调用中写入一整套记录。

    fwrite(buffer, sizeof(buffer), 104857600, stream);
    

    更新:如果我的setbuf(流,缓冲区),缓冲区是我的100MB字符缓冲区,fgets会从缓冲区返回还是导致磁盘IO?

3 个答案:

答案 0 :(得分:6)

  1. 是的,fread会立刻获取整个内容。 (假设它是一个常规文件。)但它不会读取105 MB,除非文件本身是105 MB,如果你没有检查返回值,你无法知道实际读取了多少数据,或者是否有是一个错误。

  2. 使用fgets(请参阅man fgets)代替fread。这将为您搜索换行符。

    char linebuf[1000];
    FILE *file = ...;
    while (fgets(linebuf, sizeof(linebuf), file) {
        // decode one line
    }
    
  3. 您的代码存在问题。

    char buffer[104857600]; // too big
    

    如果您尝试在堆栈上分配一个大缓冲区(105 MB肯定很大),那么它将失败并且您的程序将崩溃。如果你需要一个大的缓冲区,你必须在malloc或类似的堆上分配它。我肯定会将单个函数的堆栈使用量保持在最多数十KB,尽管在大多数Linux系统上你可能会得到几MB的数据。

  4. 作为替代方案,您可以将整个文件mmap放入内存中。在大多数情况下,这不会改善或降低性能,但更容易使用。

    int r, fdes;
    struct stat st;
    void *ptr;
    size_t sz;
    
    fdes = open(filename, O_RDONLY);
    if (fdes < 0) abort();
    r = fstat(fdes, &st);
    if (r) abort();
    if (st.st_size > (size_t) -1) abort(); // too big to map
    sz = st.st_size;
    ptr = mmap(NULL, sz, PROT_READ, MAP_SHARED, fdes, 0);
    if (ptr == MAP_FAILED) abort();
    close(fdes); // file no longer needed
    
    // now, ptr has the data, sz has the data length
    // you can use ordinary string functions
    

    使用mmap的好处是你的程序不会耗尽内存。在64位系统上,您可以将整个文件同时放入地址空间(即使是10 GB文件),系统将在程序访问内存时自动读取新块。旧的块将被自动丢弃,如果您的程序需要它们,则重新读取。

    这是一种非常好的方式来浏览大文件。

答案 1 :(得分:2)

如果可以,您可能会发现mmap文件最简单。 mmap将(文件的一部分)映射到内存中,因此整个文件基本上可以作为字节数组进行访问。在您的情况下,您可能无法立即映射整个文件,如下所示:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>


/* ... */

struct stat stat_buf;
long pagesz = sysconf(_SC_PAGESIZE);
int fd = fileno(stream);
off_t line_start = 0;
char *file_chunk = NULL;
char *input_line;
off_t cur_off = 0;
off_t map_offset = 0;
/* map 16M plus pagesize to ensure any record <= 16M will always fit in the mapped area */
size_t map_size = 16*1024*1024+pagesz;
if (map_offset + map_size > stat_buf.st_size) {
  map_size = stat_buf.st_size - map_offset;
}
fstat(fd, &stat_buf);
/* map the first chunk of the file */
file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset);
// until we reach the end of the file
while (cur_off < stat_buf.st_size) {
  /* check if we're about to read outside the current chunk */
  if (!(cur_off-map_offset < map_size)) {
    // destroy the previous mapping
    munmap(file_chunk, map_size);
    // round down to the page before line_start
    map_offset = (line_start/pagesz)*pagesz;
    // limit mapped region to size of file
    if (map_offset + map_size > stat_buf.st_size) {
      map_size = stat_buf.st_size - map_offset;
    }
    // map the next chunk
    file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset);
    // adjust the line start for the new mapping
    input_line = &file_chunk[line_start-map_offset];
  }
  if (file_chunk[cur_off-map_offset] == '\n') {
    // found a new line, process the current line
    process_line(input_line, cur_off-line_start);
    // set up for the next one
    line_start = cur_off+1;
    input_line = &file_chunk[line_start-map_offset];
  }
  cur_off++;
}

大多数复杂情况是避免制作太大的映射。您可以使用

映射整个文件
char *file_data = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0);

答案 2 :(得分:0)

我的意见是使用fgets(buff)自动检测新行。

然后使用strlen(buff)计算缓冲区大小,

if( (total+strlen(buff)) > 104857600 )

然后写入新的块..

但是块的大小几乎不会是104857600字节。

CMIIW