1

我正在开发一个类似数据库的应用程序,它存储一个包含以下内容的结构:

struct Dictionary
{
    char *key;
    char *value;

    struct Dictionary *next;
};

如您所见,我使用链表来存储信息。但是当用户退出程序时问题就开始了。我希望将信息存储在某个地方。所以我正在考虑使用 fopen 将链表存储在永久或临时文件中,然后,当用户启动程序时,检索链表。这是将链表打印到控制台的方法:

void PrintList()
{
    int count = 0;
    struct Dictionary *current;

    current = head;

    if (current == NULL)
    {
            printf("\nThe list is empty!");
        return;
    }

    printf("    Key \t  Value\n");
    printf("  ======== \t ========\n");

    while (current != NULL)
    {
        count++;
        printf("%d.  %s \t %s\n", count, current->key, current->value);
        current = current->next;
    }
}

因此,我正在考虑修改此方法以通过 fprintf 而不是 printf 打印信息,然后程序将从文件中获取信息。有人可以帮助我如何读写这个文件吗?它应该是什么类型的文件,临时的还是常规的?我应该如何格式化文件(就像我想先有键,然后是值,然后是换行符)?

4

4 回答 4

2

该文件可能应该是常规的。下次启动应用程序时,不能保证临时文件存在。此外,您的格式对人类来说看起来很好,对机器来说不是很好。我建议您创建自己的二进制文件格式或使用 XML(或者 JSON?)。你可能可以很容易地格式化它

key1\0value1\0key2\0value2\0....

我会写一个简单的例子是伪代码:

//To write...
Dictionary *this=begin_list;
while(this!=null){
  for(i=0;i<strlen(this->key);i++){
    write_byte(this->key[i]);
  }
  for(i=0;i<strlen(this->value);i++){
    write_byte(this->value[i]);
  }
  this=this->next;
}

//to read...
Dictionary *prev;
Dictionary *this;
char *buffer;
while(!eof){
  buffer=malloc(MAX_STRING_LEN);
  int i=0;
  this=malloc(sizeof(Dictionary)
  while(i<MAX_STRING_LEN){ //note no error checking
    buffer[i]=read_byte();
    if(buffer[i]==0){
      break;
    }
  }
  this->key=buffer;
  buffer=malloc(MAX_STRING_LEN)
  while(i<MAX_STRING_LEN){ //note no error checking
    buffer[i]=read_byte();
    if(buffer[i]==0){
      break; 
    }
  }
  this->value=buffer;
  if(prev!=null){
    prev->next=this;
  }
  this->next=null;
  prev=this;
}

我知道这是一个糟糕的例子。我认为 scanf 或类似的东西可能会使工作变得更容易,但我的 C 技能正在生锈。

于 2010-04-14T20:32:32.847 回答
2

根本问题是指针不会转换为外部存储。无法保证当您的程序再次执行时,它将具有相同的内存范围(地址)。鉴于此原则,有其他方法可以存储您的数据。

持久数据的处理:
1. 使用数据库,无论大小。
2. 将您的数据转换为可扫描格式的 ASCII 文本。
3. 使用固定长度的二进制记录
4. 使用可变大小的二进制记录
5. 使用文件偏移量而不是指针来实现字典数据结构。

使用数据库
让专业的应用程序(已经过测试和工作)管理您的数据。这使您可以专注于使用数据而不是存储和检索。

转换为可扫描的格式
这里的想法是以易于检索和维护的格式将数据写入文件。示例包括逗号分隔值 (CSV)、XML 和 INI。这需要您编写代码来读取和写入数据。有图书馆可以提供帮助。

使用固定长度的二进制记录
使用固定长度的记录,数据会从文件中读取并插入到您的字典中。二进制文件在传输数据方面非常有效,但不是很便携,尤其是在操作系统版本更改、平台更改或编译器版本更改时。文本记录可能会浪费空间。

使用可变大小的二进制记录
这种技术可以节省空间,但会增加处理时间。必须处理每条记录才能找到下一条记录的位置。随机访问记录很困难。否则类似于固定长度的二进制记录。

在文件中实现字典数据结构
与基于内存的数据结构相同的算法,除了使用文件偏移量而不是指针。新记录可以附加到文件的末尾。回收已删除的条目很困难,并且会导致碎片化。可以通过写入新文件来解决碎片。如果您正在经历这么多的努力,您不妨使用现有的数据库应用程序。

于 2010-04-14T20:56:59.147 回答
1

您可以读取或写入文件的一种方法是像这样使用 freopen:freopen("file.out", "wt", stdout),然后您的 printf 将转到 file.out,您不需要修改代码很多。

您可以以纯文本形式存储信息,但我真的认为最好的方法是将信息保存在二进制文件中。您可以查看更多关于 fread 和 fwrite 的搜索信息。

于 2010-04-14T20:43:46.833 回答
0

这是解决问题的一种方法。

为您的列表项创建一个数据结构,如下所示:

struct DictionaryArchive {
    char key[MAX_KEY_LENGTH];
    char value[MAX_VALUE_LENGTH];
    int next;
};

您将需要根据您所期望的数据MAX_KEY_LENGTH来确定值。MAX_VALUE_LENGTH

现在,将您的链表转换为这些结构的数组。您将存储下一项的数组索引,而不是存储用于定位下一项的指针。这会将您的列表转换为一种格式,其中每个元素都是可预测的大小,您的整个列表是一个连续的内存跨度。现在,您可以fwrite将此数组转换为二进制文件以将其存档,然后fread将其退回以恢复它。

使用上述固定大小数组的一种更节省空间的替代方法char是定义自定义文件格式而不是使用静态结构。对于您的情况,您可以使用这样的文件格式以可检索的方式存储数据:

  • 列表按顺序写入文件,从头部开始,跟随next指向尾部的指针
  • 每个列表项将使用四个数据字段按以下顺序存储:
    1. 16 位整数,key_length
    2. key_length包含元素的 8 位 char 数组,key_data
    3. 16 位整数,value_length
    4. value_length包含元素的 8 位 char 数组,value_data

现在,您可以遍历列表,将数据逐个节点转储到文件中。要重新构建数据,请通读二进制文件,struct Dictionary为每个条目生成新元素,并按照它们在文件中出现的顺序将它们链接在一起。

您将数据写入数据文件的代码如下所示(未经测试,仅用于说明目的):

FILE* fd;
size_t len;
struct Dictionary* pDict = list_head;
fd = fopen("output_file.dat", "w");

// Walk through the list, storing each node
while (pDict != NULL) {
    // Store key
    len = strlen(pDict->key);
    fwrite(&len, sizeof(len), 1, fd);
    fwrite(pDict->key, len, sizeof(char), fd);

    // Store value
    len = strlen(pDict->value);
    fwrite(&len, sizeof(len), 1, fd);
    fwrite(pDict->value, len, sizeof(char), fd);

    // Move to next list node
    pDict = pDict->next;
};

fclose(fd);

您读取数据的代码将非常相似(读取而不是写入,并struct Dictionary为每个循环迭代创建一个新对象)。

于 2010-04-14T21:24:43.577 回答