我们先来看下ini的配置文件是啥,例如我们的配置文件如下,database就是我们的俗称的区段,ip,port就是字段,127.0.0.1和3306就是字段的值。
[database]
ip = 127.0.0.1 ;
port = 3306 ;
user = root ;
pwd = 123456 ;
db = dongnaobike;
[server]
port = 9090
OK,那我们怎么实现从ini文件里加载上这些内容呢?首先分析就是读取文件内容,然后依照文件内容,把这些字段和值组成key-value的形式保存在某个数据结构中,这样我们通过key(区段+字段)就能找到value(值),那如何设计这个数据结构呢?
最容易想到的就是字典了(dictionary),这个在我们的数据结构的课堂上应该学过吧,字典是一个非常常用的数据结构了,如果大家有兴趣还可以去深度探索下redis的存储结构,也用到了字典,只不过它的那个字典设计更加复杂而已。
typedef struct _dictionary_ {
int n ; /** 字典中已经存储的个数 */
ssize_t size ; /** 字典中节点的个数 */
char ** val ; /** value列表 */
char ** key ; /** key列表 */
unsigned * hash ; /** hash值列表 */
} dictionary ;
那他是如何存储数据的呢?我们首先来看它还应当提供的接口:
dictionary * dictionary_new(size_t size);
void dictionary_del(dictionary * vd);
unsigned dictionary_hash(const char * key);
const char * dictionary_get(const dictionary * d, const char * key, const char * def);
int dictionary_set(dictionary * vd, const char * key, const char * val);
void dictionary_unset(dictionary * d, const char * key);
void dictionary_dump(const dictionary * d, FILE * out);
自然不用说,我们首先得通过dictionary_new接口创建一个字典,创建这个接口的实现如下,其实val和key都是字符串的二维数组,hash是一个无符号整形的数组,创建一个字典就是首先分配内存:
dictionary * dictionary_new(size_t size)
{
dictionary * d ;
/* If no size was specified, allocate space for DICTMINSZ */
if (size<DICTMINSZ) size=DICTMINSZ ;
d = (dictionary*) calloc(1, sizeof *d) ;
if (d) {
d->size = size ;
d->val = (char**) calloc(size, sizeof *d->val);
d->key = (char**) calloc(size, sizeof *d->key);
d->hash = (unsigned*) calloc(size, sizeof *d->hash);
}
return d ;
}
创建了字典,那如何往字典里插入key-value对呢?上面的这个数据结构其实就是这样的一个东西,用图表示如下: 从上图,我们不难理解,就是在插入key-value时,首先计算出一个key的hash值(hash),然后把hash-key-value三元组存放在上图的结构的位置d->n位置,最后d->n移动一个位置,只不过我们在存放这个hash-key-value这个三元组之前要做各种判断,如:
- hash值是否在字典中存在,如果存在则更新key和value;
- 是否已经超出了字典的大小,如果是的则需要扩充字典的节点个数(当前大小的两倍);
- 找到d->n位置;
int dictionary_set(dictionary * d, const char * key, const char * val)
{
ssize_t i ;
unsigned hash ;
if (d==NULL || key==NULL) return -1 ;
/* Compute hash for this key */
hash = dictionary_hash(key) ;
/* Find if value is already in dictionary */
if (d->n>0) {
for (i=0 ; i<d->size ; i++) {
if (d->key[i]==NULL)
continue ;
if (hash==d->hash[i]) { /* Same hash value */
if (!strcmp(key, d->key[i])) { /* Same key */
/* Found a value: modify and return */
if (d->val[i]!=NULL)
free(d->val[i]);
d->val[i] = (val ? xstrdup(val) : NULL);
/* Value has been modified: return */
return 0 ;
}
}
}
}
/* Add a new value */
/* See if dictionary needs to grow */
if (d->n==d->size) {
/* Reached maximum size: reallocate dictionary */
if (dictionary_grow(d) != 0)
return -1;
}
/* Insert key in the first empty slot. Start at d->n and wrap at
d->size. Because d->n < d->size this will necessarily
terminate. */
for (i=d->n ; d->key[i] ; ) {
if(++i == d->size) i = 0;
}
/* Copy key */
d->key[i] = xstrdup(key);
d->val[i] = (val ? xstrdup(val) : NULL) ;
d->hash[i] = hash;
d->n ++ ;
return 0 ;
}
那如何从字典中取数据呢?我们先来看接口定义:
const char * dictionary_get(const dictionary * d, const char * key, const char * def);
也就是要通过key返回对应的value,从上图我们更加要知道,我们去找这个value就是要找到对应的key的hash值所在的位置,也就是首先计算key的hash值,得到这个hash值后从d->hash数组里遍历出hash值,然后对应数组的下标 i 就是key和value所在d->key 和 d->value的位置,这样取走就行。好的,来看实现:
const char * dictionary_get(const dictionary * d, const char * key, const char * def)
{
unsigned hash ;
ssize_t i ;
hash = dictionary_hash(key);
for (i=0 ; i<d->size ; i++) {
if (d->key[i]==NULL)
continue ;
/* Compare hash */
if (hash==d->hash[i]) {
/* Compare string, to avoid hash collisions */
if (!strcmp(key, d->key[i])) {
return d->val[i] ;
}
}
}
return def ;
}
OK,了解了最基本的数据结构,那就是解析ini文件内容了,这个不再细讲,详见开源库的实现https://github.com/ndevilla/iniparser。
最终来看下我们如何使用这个开源库的:
typedef struct st_env_config
{
//数据库的配置
std::string db_ip;
unsigned short db_port;
std::string db_user;
std::string db_pwd;
std::string db_name;
//服务的配置
unsigned short svr_port;
st_env_config()
{
};
st_env_config(const std::string& db_ip, unsigned int db_port, const std::string& db_user, \
const std::string& db_pwd, const std::string& db_name, unsigned short svr_port)
{
this->db_ip = db_ip;
this->db_port = db_port;
this->db_user = db_user;
this->db_pwd = db_pwd;
this->db_name = db_name;
this->svr_port = svr_port;
};
st_env_config& operator =(const st_env_config& config)
{
if (this != &config)
{
this->db_ip = config.db_ip;
this->db_port = config.db_port;
this->db_user = config.db_user;
this->db_pwd = config.db_pwd;
this->db_name = config.db_name;
this->svr_port = config.svr_port;
}
return *this;
}
}_st_env_config;
bool Iniconfig::loadfile(const std::string& path)
{
dictionary* ini = NULL;
ini = iniparser_load(path.c_str());
if (ini==NULL)
{
LOG_ERROR("cannot parse file: %s\n", path.c_str());
return false;
}
char* ip = iniparser_getstring(ini, "database:ip", "127.0.0.1");
int port = iniparser_getint(ini, "database:port", 3306);
char* user = iniparser_getstring(ini, "database:user", "root");
char* pwd = iniparser_getstring(ini, "database:pwd", "123456");
char* db = iniparser_getstring(ini, "database:db", "dongnaobike");
int sport = iniparser_getint(ini, "server:port", 9090);
_config = st_env_config(std::string(ip), port, std::string(user), \
std::string(pwd), std::string(db), sport);
iniparser_freedict(ini);
_isloaded = true;
return true;
}