7

すべての行に可変長のレコードが含まれている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が1つのシステムコールを発行し、100MB全体をフェッチすると仮定するのは正しいですか?
  2. レコードは改行で区切られているので、バッファ内の改行文字を文字ごとに検索し、各レコードを再構築します。

私の質問は、これがチャンクで読み取る方法ですか、それともチャンクでデータを読み取り、各レコードを再構成するためのより良い代替手段がありますか?1回の呼び出しでASCIIファイルからx個の可変サイズの行を読み取る別の方法はありますか?

次に書き込み中に、私は同じことをします。書き込みcharバッファーがあり、これをfwriteに渡して、1回の呼び出しでレコードのセット全体を書き込みます。

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

更新:バッファが100MBのcharバッファであるsetbuf(stream、buffer)の場合、fgetsはバッファから戻るか、ディスクIOを引き起こしますか?

4

3 に答える 3

6
  1. はい、fread一度にすべてをフェッチします。(通常のファイルであると仮定します。)ただし、ファイル自体が105 MBでない限り、105 MBは読み取られません。戻り値を確認しないと、実際に読み取られたデータの量や、そこにあるかどうかを知る方法がありません。エラーでした。

  2. の代わりにfgets(を参照)を使用します。これにより、改行が検索されます。man fgetsfread

    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で済ますことができます。

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ファイルでも)、プログラムがメモリにアクセスすると、システムは自動的に新しいチャンクを読み取ります。古いチャンクは自動的に破棄され、プログラムで再度必要になった場合は再読み取りされます。

これは、大きなファイルを処理するための非常に優れた方法です。

于 2012-05-10T03:53:09.360 に答える
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);
于 2012-05-10T04:38:16.697 に答える
0

私の意見は、fgets(buff)改行の自動検出に使用しています。

strlen(buff)次に、バッファサイズのカウントに使用します。

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

次に、新しいチャンクに書き込みます。

ただし、チャンクのサイズは104857600バイトにはなりません。

CMIIW

于 2012-05-10T03:53:41.120 に答える