C言語 ファイル処理 メモリ動的確保 ファイルを一気に読み込む

バイオインフォ道場、くまぞうです。

バイオインフォマティクスの解析作業では、大きなファイルを扱うことが多いです。スクリプト言語はとても扱いやすいですが、一般的に実行速度を犠牲にします。こんなときはC言語などのプログラム言語を使うと高速な処理を行うことができます。

スポンサーリンク



ファイルを一気に読み込むメリット

ファイルの読み込みには少し時間がかかります。読み込んだデータを繰り返し使う場合は、配列などに読み込んでメモリに展開してから処理を行う方が速くて効率的です。

make_file.sh(ファイルの準備)

#! /bin/bash

MAX=10
if [ $# -eq 1 ]; then
    MAX=$1
fi

for x in `seq 1 ${MAX}`; do
    echo hello! ${x}
done
$ chmod +x make_file.sh
$ ./make_file.sh | tee hello.txt
hello! 1
hello! 2
hello! 3
hello! 4
hello! 5
hello! 6
hello! 7
hello! 8
hello! 9
hello! 10

$ls
hello.txt

ファイルのオープンとクローズ

ファイルのオープンとクローズは忘れないようにセットで処理します。何かのエラーが発生した場合に、「閉じ忘れ」がないように、例外処理も含めて記述すると良いと思います。ファイルは、fopen関数で開き、fclose関数で閉じます。テキスト処理について、読み込みの際は"rt"フラグ、書き込みの際は"wt"フラグや"at"フラグを指定します。

ファイル読み込み – 1行ずつファイル読み込み

ファイルポインタとfgets関数を使って、先頭から1行ずつ読み込みます。ファイルの終端ではNULLが返るので、それを目印に読み込み処理を抜けます。

file_open.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define READ_SIZE 80

void main(int argc, char** argv)
{
  char buf[READ_SIZE];                         // 1行分データ読み込み配列
  memset(buf, 0x00, READ_SIZE);

  FILE* fp;
  if (!(fp=fopen(argv[1], "rt"))) exit(1);     // ファイルオープン
  while (fgets(buf, READ_SIZE, fp) != NULL) {  // 1行読み込み
    buf[strlen(buf)-1] = '\0';                 // 終端処理
    printf("%s\n", buf);                       // ファイル内容を表示
    memset(buf, 0x00, READ_SIZE);              // バッファクリア
  }
  fclose(fp);                                  // ファイルクローズ
}
$ gcc file_open.c -o file_open.out
$ ./file_open.out hello.txt
hello! 1
hello! 2
hello! 3
hello! 4
hello! 5
hello! 6
hello! 7
hello! 8
hello! 9
hello! 10

メモリの動的確保について

ファイルの読み込みを一気に行う場合、配列などを利用してデータの格納場所を準備する必要があります。ファイルによって大きさが異なるので、格納する大きさも可変になるように、メモリは動的に確保する方が良いと思います。1行分のデータ×行数分の領域と考えればわかりやすいと思います。

メモリの確保と解放

メモリはmalloc関数を使って動的確保を行います。解放はfree関数で行います。2つはセットで考えます。解放を忘れると、確保されたメモリが残り続けてしまうメモリーリークを引き起こしたり、重大なメモリーエラーを引き起こす原因になったりします。

file_mem.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define READ_SIZE 256

void main(int argc, char** argv)
{
  char* file = argv[1];
  int   rows = strtol(argv[2], NULL, 0);

  typedef char* ptr;
  ptr* ptrs = (ptr *)malloc(sizeof(ptr)*rows); // 1行毎の管理
  memset(ptrs, 0x00, sizeof(ptr)*rows);

  FILE* fp;
  if (!(fp=fopen(file, "rt"))) exit(1);        // ファイルを開く

  char buf[READ_SIZE];
  ptr* p = ptrs;
  while (fgets(buf, READ_SIZE, fp) != NULL) {  // 1行ずつ読み込み
    int len = strlen(buf);
    buf[len-1] = '\0';
    *p = (char *)malloc(sizeof(char)*len+1);   // 1行分のメモリ確保
    strcpy(*p, buf);                           // 1行分をコピー
    p++;
  }
  fclose(fp);                                  // ファイルを閉じる

  int i = 0;
  for (i=0; i<rows; i++) {
    printf("%s\n", ptrs[i]);                   // ファイル内容表示(配列から)
    if (ptrs[i]) free(ptrs[i]);                // 1行分のメモリ解放
  }
  if (ptrs) free(ptrs);                        // 1行毎の管理解放
}
$ gcc file_mem.c -o file_mem.out
$ ./file_mem.out hello.txt 10
hello! 1
hello! 2
hello! 3
hello! 4
hello! 5
hello! 6
hello! 7
hello! 8
hello! 9
hello! 10

ファイルを一気に読み込む

ファイルを一気に読み込んで処理を効率化します。open関数read関数などの低水準入力関数を使います。行データの分割は、文字列をsplitするのに便利なstrtok関数を用いました。

file_all.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

void main(int argc, char** argv)
{
  char* file = argv[1];
  int   size = strtol(argv[2], NULL, 0);
  int   line = strtol(argv[3], NULL, 0);

  char* buf = (char *)malloc(sizeof(char)*size+1);  // ファイル全体メモリ確保
  memset(buf, 0x00, sizeof(char)*size+1);

  int fd = open(file, O_RDONLY);                    // ファイルを開く
  if (fd == -1) exit(1);

  int rc = read(fd, buf, sizeof(char)*size);        // 指定サイズ分読み込む
  close(fd);                                        // ファイルを閉じる

  const char* delim = "\n";
  char** ptr = (char **)malloc(sizeof(char*)*line); // 1行分のデータ管理
  char** p   = ptr;
  *p = strtok(buf, delim);                          // ファイル全体を改行で分割
  while ((*p) != NULL) {
    p++;
    *p = strtok(NULL, delim);
  }

  int i;
  for(i=0; i<line; i++) printf("%s\n", ptr[i]);     // データの再利用

  free(buf);                                        // 全体メモリ解放
  free(ptr);                                        // 行データ管理解放
}
$ gcc file_all.c -o file_all.out
$ ./file_all.out hello.txt 91 10  <- 91はファイルの大きさ(バイト数)
hello! 1
hello! 2
hello! 3
hello! 4
hello! 5
hello! 6
hello! 7
hello! 8
hello! 9
hello! 10
スポンサーリンク