Пара вопросов с использованием fwrite / fread со структурами данных - PullRequest
1 голос
/ 14 апреля 2010

Я впервые использую fwrite() и fread() для записи некоторых структур данных на диск, и у меня есть пара вопросов о передовых методах и правильных способах выполнения действий.

То, что я пишу на диск (чтобы позже я мог его прочитать), - это все профили пользователей, вставленные в структуру Graph. Каждая вершина графа имеет следующий тип:

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 предназначен для тестирования. Там тысячи пользователей, и я все еще экспериментирую с кодом.

Мои вопросы:

  • После прочтения this я решил использовать fwrite() для каждого элемента вместо записи всей структуры. Я также избегаю писать указатель на почтовый ящик, поскольку мне не нужно сохранять этот указатель. Итак, это путь? Несколько fwrite() вместо глобального для всей структуры? Разве это не медленнее?
  • Как мне прочитать этот контент? Я знаю, что должен использовать fread(), но я не знаю размер строк, потому что я использовал strlen() для их записи. Я мог бы написать вывод strlen() перед записью строки, но есть ли лучший способ без дополнительных записей?

Ответы [ 3 ]

5 голосов
/ 14 апреля 2010

Если ваша программа должна быть вообще переносимой, то вам не следует записывать целые и короткие записи на диск в виде блоков памяти: данные будут повреждены при попытке их прочитать на компьютере с другим размером слова (например, 32 бита -> 64 бита) или другой порядок байтов.

Для строк вы можете либо сначала написать длину, либо включить в конце терминатор.

Лучше всего обычно использовать текстовый формат. Например, вы можете написать каждую запись в виде отдельной строки с полями, разделенными табуляцией или двоеточием. (В качестве бонуса вам больше не нужно записывать количество записей в начале файла - просто читайте записи, пока не дойдете до конца файла.)

Редактировать: Но если это задание класса, которое вам дано, вам, вероятно, не нужно беспокоиться о переносимости. Запишите '\0' терминаторы из строк на диск, чтобы разделить их. Не беспокойтесь об эффективности, читая его обратно, самый медленный бит - доступ к диску.

Или даже fwrite() из всей структуры и fread() все это обратно. Обеспокоены этим указателем? Перезаписывайте его безопасным значением, когда вы читаете его. Не беспокойтесь о потраченном впустую месте на диске (если вас не попросили минимизировать использование диска).

Если вам нужно записать неотрицательные целые на диск в переносимом двоичном формате, вы можете сделать это следующим образом:

  • первый байт - это число следующих байтов
  • второй байт является самым значимым ненулевым байтом в int
  • ...
  • последний байт - младший байт в int

Итак:

  • 0 кодируется как 00
  • 1 кодируется как 01 01
  • 2 кодируется как 01 02
  • 255 -> 01 и далее
  • 256 -> 02 01 00
  • 65535 -> 02 и далее
  • 65536 -> 03 01 00 00
  • и т.д.

Если вам также нужно кодировать отрицательные числа, вам нужно будет зарезервировать немного для знака где-нибудь.

2 голосов
/ 14 апреля 2010

Вы правы: поскольку вы делаете это сейчас, нет способа прочитать содержимое, потому что вы не можете сказать, где заканчивается одна строка и начинается следующая.

Совет, который вы цитируете, чтобы избегать использования fwrite () для структурированных данных, хорош, но интерпретация этого совета, означающая, что вы должны fwrite () для каждого элемента в отдельности, может быть не лучшим решением.

Я думаю, вам следует рассмотреть возможность использования другого формата для вашего файла вместо записи необработанных значений с помощью fwrite (). (Например, ваши файлы не будут переноситься на компьютер с другим порядком байтов.)

Поскольку похоже, что большинство ваших элементов являются строками и целыми числами, рассматривали ли вы текстовый формат с использованием fprintf () для записи и fscanf () для чтения? Одним большим преимуществом текстового формата вместо двоичного формата для конкретного приложения является то, что вы можете просматривать его стандартными инструментами (для отладки и т. Д.)

Кроме того, какой бы формат вы ни выбрали, убедитесь, что вы учитываете возможность добавления дополнительных полей в будущем. Как минимум, это означает, что вы должны включить номер версии в какой-то заголовок, либо для самого файла, либо для каждой отдельной записи. Более того, пометьте отдельные поля (для учета необязательных атрибутов), например:

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

name: user2
address: 1 infinite loop
last login: 12th of never
1 голос
/ 14 апреля 2010
  • Это медленнее . Вызов функции x раз медленнее, чем один раз, когда x> 1. Если производительность оказывается проблемой, вы можете использовать fwrite / fread с sizeof (структурой) для регулярного использования и написать переносную сериализованную версию для импорта / экспорта. Но сначала проверьте, действительно ли это проблема. Большинство форматов больше не используют двоичные данные, поэтому вы можете сказать, что по крайней мере fread производительность не является их главной задачей.

  • Нет, нет. альтернатива - стрельба на основе fgetc(3).

...