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
オススメ記事
C言語 配列 sort,search,再帰 スクリプトの書き方
C言語 シェルソート,基本選択法 スクリプトの書き方
C言語 バブルソート シェルスクリプトの書き方
メモリの動的確保について
ファイルの読み込みを一気に行う場合、配列などを利用してデータの格納場所を準備する必要があります。ファイルによって大きさが異なるので、格納する大きさも可変になるように、メモリは動的に確保する方が良いと思います。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
[amazonjs asin="4756116396" locale="JP" title="エキスパートCプログラミング―知られざるCの深層 (Ascii books)"]