1

fwrite()およびを使用fread()して初めてデータ構造をディスクに書き込みますが、ベスト プラクティスと適切な方法についていくつか質問があります。

ディスクに書き込んでいるのは (後で読み戻せるようにするため)、グラフ構造に挿入されたすべてのユーザー プロファイルです。各グラフ頂点は次のタイプです。

typedef struct sUserProfile {
    char name[NAME_SZ];
    char address[ADDRESS_SZ];
    int socialNumber;
    char password[PASSWORD_SZ];

    HashTable *mailbox;
    short msgCount;
} UserProfile;

そして、これは私が現在すべてのプロファイルをディスクに書き込んでいる方法です:

void ioWriteNetworkState(SocialNetwork *social) {
    Vertex *currPtr = social->usersNetwork->vertices;
    UserProfile *user;

    FILE *fp = fopen("save/profiles.dat", "w");

    if(!fp) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    fwrite(&(social->usersCount), sizeof(int), 1, fp);

    while(currPtr) {
        user = (UserProfile*)currPtr->value;

        fwrite(&(user->socialNumber), sizeof(int), 1, fp);
        fwrite(user->name, sizeof(char)*strlen(user->name), 1, fp);
        fwrite(user->address, sizeof(char)*strlen(user->address), 1, fp);
        fwrite(user->password, sizeof(char)*strlen(user->password), 1, fp);
        fwrite(&(user->msgCount), sizeof(short), 1, fp);

        break;

        currPtr = currPtr->next;
    }

    fclose(fp);
}

ノート:

  • 最初fwrite()に表示されるグラフには合計ユーザー数が表示されるので、読み返す必要があるデータの量がわかります。
  • breakテスト用です。何千人ものユーザーがいて、私はまだコードを試しています。

私の質問:

  • これを読んだ後fwrite()、構造全体を書くのではなく、各要素で使用することにしました。また、そのポインターを保存する必要がないため、ポインターをメールボックスに書き込むことも避けます。それで、これは行く方法ですか?fwrite()構造全体のグローバルなものではなく、複数の ? 遅くないですか?
  • このコンテンツを読み返すにはどうすればよいですか? 使用する必要fread()があることはわかっていますが、文字列のサイズはわかりません。以前は文字列strlen()を書いていたからです。文字列を書き込む前にの出力を書き込むことはできますが、strlen()余分な書き込みを行わないより良い方法はありますか?
4

3 に答える 3

5

プログラムを移植可能にする必要がある場合は、int と short をメモリのブロックとしてディスクに書き込むべきではありません。異なるワード サイズ (たとえば 32 ビット -> 64 ビット) または異なるバイト順。

文字列の場合、最初に長さを記述するか、最後にターミネータを含めることができます。

最良の方法は、通常、テキスト ベースの形式を使用することです。たとえば、各レコードを個別の行として記述し、フィールドをタブまたはコロンで区切ることができます。(おまけとして、ファイルの先頭にレコード数のカウントを書き込む必要がなくなりました --- ファイルの終わりに到達するまでレコードを読み込むだけです。)

編集:しかし、これが与えられたクラスの課題である場合、おそらく移植性について心配する必要はありません. '\0'ターミネータを文字列からディスクに書き込み、区切ります。読み返すときの効率について心配する必要はありません。最も遅いビットはディスク アクセスです。

またはfwrite()、構造全体を均等にして、fread()すべて元に戻します。そのポインターが心配ですか? 読み込むときに安全な値で上書きします。ディスクの無駄なスペースについて心配する必要はありません (ディスクの使用を最小限に抑えるように求められた場合を除きます)。

移植可能なバイナリ形式で非負の整数をディスクに書き込む必要がある場合は、次のようにすることができます。

  • 最初のバイトは後続のバイト数です
  • 2 番目のバイトは、int の最上位のゼロ以外のバイトです。
  • ...
  • 最後のバイトは int の最下位バイトです

そう:

  • 0 は 00 としてエンコードされます
  • 1 は 01 01 としてエンコードします
  • 2 は 01 02 としてエンコードします
  • 255 -> 01ff
  • 256 -> 02 01 00
  • 65535 -> 02 ff ff
  • 65536 -> 03 01 00 00

負の数もエンコードする必要がある場合は、符号用にビットをどこかに予約する必要があります。

于 2010-04-14T14:13:50.140 に答える
2

その通りです。現在行っているように、1 つの文字列がどこで終わり、次の文字列がどこで始まるかを判断できないため、コンテンツを読み返す方法はありません。

構造化データに fwrite() を使用しないように引用するアドバイスは適切ですが、各要素を個別に fwrite() する必要があることを意味するようにそのアドバイスを解釈することは、最善の解決策ではない可能性があります。

fwrite() で生の値を書き込むのではなく、ファイルに別の形式を使用することを検討する必要があると思います。(たとえば、バイト順が異なるマシンにファイルを移植することはできません。)

ほとんどの要素が文字列と整数のように見えるので、書き込みに fprintf() を使用し、読み取りに fscanf() を使用するテキストベースの形式を検討しましたか? アプリケーション固有のバイナリ形式ではなく、テキストベースの形式の大きな利点の 1 つは、標準ツール (デバッグなど) で表示できることです。

また、どの形式を選択する場合でも、将来フィールドを追加する必要がある可能性を考慮してください。少なくとも、ファイル自体または個々のエントリのいずれかに対して、何らかのヘッダーにバージョン番号を含める必要があることを意味します。さらに良いのは、個々のフィールドにタグを付けることです (オプションの属性を許可するため)。たとえば、次のようになります。

name: user1
address: 1600 pennsylvania ave
favorite color: blue

name: user2
address: 1 infinite loop
last login: 12th of never
于 2010-04-14T14:08:27.020 に答える
1
  • 遅いです。関数を x 回呼び出すと、x>1 の場合に 1 回呼び出すよりも遅くなります。パフォーマンスが問題になる場合は、fwrite/freadを sizeof(structure) と一緒に通常の使用に使用し、移植可能なシリアル化されたバージョンを作成してインポート/エクスポートすることができます。ただし、最初に本当に問題があるかどうかを確認してください。ほとんどの形式はバイナリ データを使用しなくなったため、少なくともfreadパフォーマンスは主な関心事ではないことがわかります。

  • いいえ、ありません。別の方法は、fgetc(3)ベースの strlen を実行することです。

于 2010-04-14T14:05:16.207 に答える