Entry

プログラミングメモ - ファイルから1行読み取る関数(続き)

2006年11月25日

この前書いたファイルから1行読み取る関数ですけれど,あまりにも効率が悪いので直すことにしました。具体的には,1回確保したバッファを足りなくなるまで使い回すことに変更。これまではバッファの余裕にかかわらず,行の数だけ malloc(3) と free(3) を繰り替えしていたわけですけれど,使い回すと極端に malloc(3) する回数が減りました。こういう方法の方が普通に考えても自然ですよね。

malloc(3) → free(3) というのは,速度的に見てもオーバーヘッドが大きいので,できることならあまり呼びたくないのでした。

問題はどうやって使い回すかです。呼び出し側でバッファを管理するとなると面倒っちいし,ルーチンの使い回しも難しくなるので,ハンドラを作って状態を持たせることにしました。XML をいぢるライブラリをはじめとしてこういうのは常套句でしょうから,大上段に言うことでもないんですけれど,オブジェクト指向ちっくでちょっとかっこいいです。

能書きをたれるのもアレなので,とりあえずソースをどうぞ。

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

#define STRING_READER_BUFSIZE_DEFAULT 1024

typedef struct _STRING_READER {
    FILE   *fp;
    char   *buffer;
    unsigned int buffer_size;
    unsigned int line_number;
} STRING_READER;

STRING_READER *string_reader_create(char *, unsigned int);
void string_reader_free(STRING_READER *);
char *string_reader_exec(STRING_READER *);

void string_reader_free(STRING_READER *sr)
{
    if (sr->buffer != NULL) {
        free(sr->buffer);
        sr->buffer = NULL;
    }
    fclose(sr->fp);
    if (sr != NULL) {
        free(sr);
    }
    return;
}

STRING_READER *string_reader_create(char *filename, unsigned int buf_size)
{
    STRING_READER *sr;
    if ((sr = malloc(sizeof(STRING_READER))) == NULL) {
        return NULL;
    }
    sr->fp = NULL;
    sr->buffer = NULL;
    sr->buffer_size = STRING_READER_BUFSIZE_DEFAULT;
    sr->line_number = 0;
    
    if (filename != NULL) {
        sr->fp = fopen(filename, "r");
        if (sr->fp == NULL) {
            string_reader_free(sr);
            return NULL;
        }
    } else {
        return NULL;
    }
    if (buf_size != 0) {
        sr->buffer_size = buf_size;
    }
    return sr;
}

char *string_reader_exec(STRING_READER *sr)
{
    char *new_buf;
    char *ptr;
    unsigned int read_size;

    if (sr->buffer == NULL) {
        if ((sr->buffer = (char *)calloc(sizeof(char), sr->buffer_size)) == NULL) {
            return NULL;
        }
    }
    read_size = 0;
    for (ptr = sr->buffer; ; ptr++, read_size++) {
        if (read_size == sr->buffer_size) {
            sr->buffer_size *= 2;
            new_buf = NULL;
            if ((new_buf = (char *)calloc(sizeof(char), sr->buffer_size)) == NULL) {
                return NULL;
            }
            memcpy(new_buf, sr->buffer, read_size);
            free(sr->buffer);
            sr->buffer = new_buf;
            ptr = sr->buffer + read_size;
        }        
        fread(ptr, sizeof(char), 1, sr->fp);
        if (*ptr == '\n' || feof(sr->fp)) {
            *ptr = '\0';
            sr->line_number++;
            read_size++;
            break;
        }
    }
    return sr->buffer;
}

STRING_READER 型のオブジェクトを作って,そこにバッファへのポインタやら読んでいる行の番号やらを詰め込むことにしました。呼び出し側は,このハンドラを作って,string_reader_exec() をガンガン呼び出せばいいだけです。また,前回は strdup(3) みたいなことをして,その文字列を返していたけれど,今回はバッファを返すことにしました。これは単純に気分の問題です。

どうやって使うのか,呼び出し側を見ることにします。

int main(int argc, char *argv[])
{
    STRING_READER *sr;
    int count;

    if (argc != 2) {
        fprintf(stderr, "arg error\n");
        exit(-1);
    }
    if ((sr = string_reader_create(argv[1], 0)) == NULL) {
        fprintf(stderr, "sr error\n");
        exit(-1);
    }
 
    for (count = 1; !feof(sr->fp); count++) {
        if (string_reader_exec(sr) == NULL) {
            fprintf(stderr, "%s: string read error.\n", argv[0]);
            string_reader_free(sr);
            exit(-1);
        }
        printf("%05d: %s\n", sr->line_number, sr->buffer);
    }
    string_reader_free(sr);
 
    return 0;
}

STRING_READER 型のポインタを作って,string_reader_create() でオブジェクトを作成。string_reader_exec() でしこたま読んだら,string_reader_free() で後片付けです。string_reader_create() の引数には,開くファイルのファイル名と初期のバッファサイズを渡します。バッファサイズを0にした場合は,デフォルトの STRING_READER_BUFSIZE_DEFAULT (ここでは1024)が設定されます。

あとは,string_reader_exec() で読んでいきます。string_reader_exec() は1回呼ぶと1行分の文字列を buffer に収めて返ってきます。失敗した場合は NULL が返るので,ちゃんと検知するようにします。また,ファイルの終端は STRING_READER ハンドラの fp というファイルポインタが EOF を指すので,これを feof(3) あたりで検知するようにします。ここでの例は,前回の例と同じ,ファイルを読んで行番号を付けるプログラムですけれど,sr->line_number というメンバを使っているのがポイント。呼び出し側で count というローカル変数を宣言しているけれど,こういうもんを作らなくても,ハンドラの中で宜しく管理してくれるのがうれしいところです。

ありがちなネタをここまで引っ張ることもないんですけれど,とりあえず「作ってみたよ」ということで……。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN