INI 库:链表(抽象)问题
我正在开发自己的 - 简单、简短、愚蠢的 - ini 库,我将用于其他项目。我编写了一个 ini 解析器,到目前为止非常简单且有效,现在是时候转向更困难的部分了 - 如何以及在哪里存储来自 ini 文件的解析数据。
我想过使用简单的链表(也许是双链表),但我无法在脑海中真正想象它。
struct keys
{
char *keyname;
char *keyval;
unsigned long hashkey;
struct keys *next;
struct keys *prev;
}
struct ini
{
char *section;
struct keys *okeys;
unsigned long hashkey;
struct ini *next;
struct ini *prev;
}
首先,我想以这种方式编码,但我看起来有点愚蠢,而且不够有效,我知道有更好的方法在链接列表中存储节、键和键值,但我想不出任何方法。或者,也许我应该尝试使用其他数据结构?我不知道,以这种方式创建一个 ini 库需要太多的函数,例如 - 用于添加部分的单独函数,以及用于添加键的函数等。我真的很想弄清楚要使用什么,但我陷入了困境。
这是我的简单 ini 解析器的代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#define BUFF_SIZE 1024
#define INI_FILE "config.ini"
#define ERROR_FILE "error.log"
void load_ini(const char *name);
void log_it(const char *filename, const char *format, ...);
void trim(char *str);
unsigned int hash(const char *key);
int main(int argc, char *argv[])
{
load_ini(INI_FILE);
return 0;
}
/*2. load ini function */
void load_ini(const char *name)
{
char *line = NULL;
char *p = NULL;
FILE *ifile = NULL;
ifile = fopen(name, "r");
if(ifile == NULL)
{
log_it(ERROR_FILE, "(load_ini) can't open %s file", name);
return;
}
line = malloc(BUFF_SIZE * sizeof(char) + 1);
if(line == NULL)
{
log_it(ERROR_FILE, "(load_ini) malloc fail");
return;
}
while(fgets(line, BUFF_SIZE+1, ifile) != NULL)
{
trim(line);
if(strlen(line) < 3 || *line == ';' || *line == '#')
{
continue;
}
if(*line == '[')
{
if(p = strchr(line, ']'))
{
*p = '\0';
trim(line+1);
/* add section here */
}
}
else if(p = strchr(line, '='))
{
*p = '\0';
trim(line);
trim(p+1);
/* add key, and key value here */
}
}
free(line);
fclose(ifile);
}
/* 3. multifunctional log function with variable number of arguments */
void log_it(const char *filename, const char *format, ...)
{
va_list arglist;
FILE *fp = NULL;
fp = fopen(filename,"a");
if(fp == NULL)
{
/* :) */
return;
}
va_start(arglist, format);
vfprintf (fp, format, arglist);
va_end (arglist);
fclose (fp);
}
/* 4. trim function */
void trim(char *str)
{
char *start = str;
char *end = start + strlen(str);
while (--end >= start)
{
if (!isspace(*end))
{
break;
}
}
*(++end) = '\0';
while (isspace(*start))
{
start++;
}
if (start != str)
{
memmove(str, start, end - start + 1);
}
}
/* Bernstein hash */
unsigned int hash(const char *key)
{
unsigned int hash = 5381;
unsigned int i = 0;
int len = strlen(key);
for(i; i < len; ++i)
{
hash = 33 * hash + key[i];
}
return hash ^ (hash >> 16);
}
I'm working on my own - simple, short, stupid - ini library that I will use for other projects. I coded an ini parser, pretty simple, and effective so far and now it's time to move to the harder part - how, and where to store parsed data from an ini file.
I thought of using simple linked list (maybe double linked list) but I can't really picturise it in my head.
struct keys
{
char *keyname;
char *keyval;
unsigned long hashkey;
struct keys *next;
struct keys *prev;
}
struct ini
{
char *section;
struct keys *okeys;
unsigned long hashkey;
struct ini *next;
struct ini *prev;
}
First, I thought to code it this way, but I kinda looks stupid, and not effective enough, I know there is better way to store sections, keys and key values in a linked list but I can't think of any. Or, maybe I should try using some other data structure? I donno, doing an ini library this way would require too many functions like - a separate function for adding sections, and the one for adding keys, etc. I'm really trying to figure out what to use and I'm stuck.
Here's the code of my simple ini parser:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#define BUFF_SIZE 1024
#define INI_FILE "config.ini"
#define ERROR_FILE "error.log"
void load_ini(const char *name);
void log_it(const char *filename, const char *format, ...);
void trim(char *str);
unsigned int hash(const char *key);
int main(int argc, char *argv[])
{
load_ini(INI_FILE);
return 0;
}
/*2. load ini function */
void load_ini(const char *name)
{
char *line = NULL;
char *p = NULL;
FILE *ifile = NULL;
ifile = fopen(name, "r");
if(ifile == NULL)
{
log_it(ERROR_FILE, "(load_ini) can't open %s file", name);
return;
}
line = malloc(BUFF_SIZE * sizeof(char) + 1);
if(line == NULL)
{
log_it(ERROR_FILE, "(load_ini) malloc fail");
return;
}
while(fgets(line, BUFF_SIZE+1, ifile) != NULL)
{
trim(line);
if(strlen(line) < 3 || *line == ';' || *line == '#')
{
continue;
}
if(*line == '[')
{
if(p = strchr(line, ']'))
{
*p = '\0';
trim(line+1);
/* add section here */
}
}
else if(p = strchr(line, '='))
{
*p = '\0';
trim(line);
trim(p+1);
/* add key, and key value here */
}
}
free(line);
fclose(ifile);
}
/* 3. multifunctional log function with variable number of arguments */
void log_it(const char *filename, const char *format, ...)
{
va_list arglist;
FILE *fp = NULL;
fp = fopen(filename,"a");
if(fp == NULL)
{
/* :) */
return;
}
va_start(arglist, format);
vfprintf (fp, format, arglist);
va_end (arglist);
fclose (fp);
}
/* 4. trim function */
void trim(char *str)
{
char *start = str;
char *end = start + strlen(str);
while (--end >= start)
{
if (!isspace(*end))
{
break;
}
}
*(++end) = '\0';
while (isspace(*start))
{
start++;
}
if (start != str)
{
memmove(str, start, end - start + 1);
}
}
/* Bernstein hash */
unsigned int hash(const char *key)
{
unsigned int hash = 5381;
unsigned int i = 0;
int len = strlen(key);
for(i; i < len; ++i)
{
hash = 33 * hash + key[i];
}
return hash ^ (hash >> 16);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我会将这些部分保留在链接列表中。平均 ini 文件中可能没有足够的部分来保证设置哈希表。或者,您可以使用二叉搜索树,这需要最少的努力。
另一种方法是使用一个哈希表并使用节名称和分隔标记为每个值添加前缀。这种方法的缺点是您不能在键名和节名中使用分隔标记。如果你控制了ini格式,那么这没有问题。如果这是一个通用的 ini 解析器,那就是了。
对于任何一种方法,您都需要为无节(即全局节)提供一个节名称。
虽然二叉搜索树和哈希表的查找速度比链接列表更快,但您需要考虑它对于您的应用程序是否值得。如果您的应用程序仅读取 ini 文件一次,并且应用程序中的值集状态在其生命周期内不再查询,则查找速度并不重要。但是,如果应用程序不断查询这些值,则首选哈希表。如果在应用程序生命周期内更有可能查询某些值,则展开树可能会更有效。然后,如果访问配置值是应用程序的重要且可衡量的部分,那么也许是时候拆分一些功能了。
I would keep the sections in a linked list. Chances are there are not nearly enough sections in the average ini file to warrant setting up a hash table. Alternatively, you can use a binary search tree that requires minimal effort.
Another approach is to use one hash table and prefix each value with the section name and a separation token. The drawback to this approach is that you cannot use the separation token in the key names and section names. If you control the ini format, then this is no problem. If this is a general ini parser, it will be.
For either approach you need to come up with a section name for the section-less i.e. global section.
While binary search trees and hash tables are faster lookups then linked lists, you need to consider whether it's worth it to your application. If your application only reads the ini file once and the values set states within the application that do not queried anymore during it's lifetime, lookup speed is not important. However, if the application constantly queries these values, then hash tables are preferred. If certain values are more likely to be queried during the application lifetime, splay trees may work favorably. Then again if accessing configuration values is a significant and measurable part of the application, then maybe it's time to split off some functionality.