1

我正在使用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用于测试目的。有成千上万的用户,我仍在试验代码。

我的问题:

  • 读完这篇文章后,我决定fwrite()在每个元素上使用而不是编写整个结构。我还避免将指针写入邮箱,因为我不需要保存该指针。那么,这是要走的路吗?fwrite()整个结构的多个而不是全局?那不是更慢吗?
  • 如何回读此内容?我知道我必须使用fread(),但我不知道字符串的大小,因为我曾经strlen()写过它们。我可以strlen()在写字符串之前写出输出,但是有没有更好的方法没有额外的写?
4

3 回答 3

5

如果您的程序需要完全可移植,那么您不应该将整数和短路作为内存块写入磁盘:当您尝试在具有不同字长的计算机上读取它们时数据将被破坏(例如 32bit -> 64 位)或不同的字节顺序。

对于字符串,您可以先写入长度,也可以在末尾包含终止符。

最好的方法通常是使用基于文本的格式。例如,您可以将每条记录写成单独的行,字段之间用制表符或冒号分隔。(作为奖励,您不再需要在文件开头写入记录数 --- 只需读入记录,直到您到达文件末尾。)

编辑:但如果这是你的课堂作业,你可能不需要担心可移植性。将字符串中的终止符写入'\0'磁盘以分隔它们。回读时不要担心效率,最慢的位是磁盘访问。

或者甚至fwrite()是整个结构,fread()然后全部返回。担心那个指针?当你读入它时用一个安全的值覆盖它。不要担心磁盘上的空间浪费(除非你被要求最小化磁盘使用)。

如果您确实需要以可移植二进制格式将非负整数写入磁盘,您可以这样做:

  • 第一个字节是后面的字节数
  • 第二个字节是 int 中最重要的非零字节
  • ...
  • 最后一个字节是 int 中的最低有效字节

所以:

  • 0 编码为 00
  • 1 编码为 01 01
  • 2 编码为 01 02
  • 255 -> 01 后
  • 256 -> 02 01 00
  • 65535 -> 02 ff ff
  • 65536 -> 03 01 00 00
  • ETC

如果您还需要对负数进行编码,则需要在某处为符号保留一点。

于 2010-04-14T14:13:50.140 回答
2

你是对的:正如你现在所做的那样,没有办法回读内容,因为你无法分辨一个字符串在哪里结束,而下一个字符串从哪里开始。

您引用的避免对结构化数据使用 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
于 2010-04-14T14:08:27.020 回答
1
  • 慢。调用函数 x 次比调用一次 x>1 慢。如果性能成为一个问题,您可以将fwrite/fread与 sizeof(structure) 一起使用以进行常规使用,并编写一个可移植的序列化版本来导入/导出。但首先检查它是否真的有问题。大多数格式不再使用二进制数据,因此您可以看出至少fread性能不是他们主要关心的问题。

  • 不,没有。另一种方法是进行fgetc(3)基于 strlen。

于 2010-04-14T14:05:16.207 回答