C 构造一个 简单配置文件读取库
前言
最近看到这篇文章,
json引擎性能对比报告 http://www.oschina.net/news/61942/cpp-json-compare?utm_source=tuicool
感觉技术真是坑好多, 显露的高山也很多. 自己原先也 对着
json 标准定义 http://www.json.org/json-zh.html
写过一般json解析器, 1000行后面跟上面一对比, 真是弱鸡. 后面就看了其中吹得特别掉几个源码,确实有过人之处,深感
自己不足. 下载一些也在研究,发现看懂会用和会设计开发是两码事.
对于json设计主要基础点是在 结构设计和语法解析 . 继续扯一点对于一个框架的封装在于套路,套路明确,设计就能糅合
在一起. 不管怎样,只要学了,总会优化的. 下面 我们分享的比较简单, 但也是围绕结构设计 和 语法解析方面, 给C框架来个 配置
读取的能力.
正文
1.解析文件说明
这里先展示配置文件的直观展示如下 test.php
<?php // 这里是简单测试 php 变量解析 $abc = "123456"; $str = "1231212121212121212 21222222222 2121212\" ";
我们只需要解析上面数据, 保存在全局区下次直接调用就可以了. 例如
运行的结果如上. 对于上面配置 有下面几个规则
a. $后面跟变量名
b.中间用 = 分割
c.变量内容用 ""包裹, 需要用" 使用\"
上面就是配置的语法规则.下面我们逐渐讲解 语法解析内容
2.简单的核心代码,语法解析测试
具体的扫描算法如下
a.跳过开头空白字符 找到$字符
b.如果$后面是空白字符,那么直接 读取完毕这行
c.扫描到 = 字符,否则读取完毕这行
d扫描到 " 字符
e扫描到最后"字符,必须 前一个字符不是 \ , 否则读取这行完毕.
上面就是 处理的算法思路,比较容易理解, 具体测试代码如下
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #define _STR_PATH "test.php" /* * 这里处理文件内容,并输出解析的文件内容 */ int main(int argc, char* argv[]) { int c, n; char str[1024]; int idx; FILE* txt = fopen(_STR_PATH, "rb"); if (NULL == txt) { puts("fopen is error!"); exit(EXIT_FAILURE); } //这里处理读取问题 while ((c = fgetc(txt))!=EOF){ //1.0 先跳过空白字符 while (c != EOF && isspace(c)) c = fgetc(txt); //2.0 如果遇到第一个字符不是 '$' if (c != '$') { //将这一行读取完毕 while (c != EOF && c != '\n') c = fgetc(txt); continue; } //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕 if ((c=fgetc(txt))!=EOF && isspace(c)) { while (c != EOF && c != '\n') c = fgetc(txt); continue; } //开始记录了 idx = 0; //3.0 找到第一个等号 while (c!=EOF && c != '=') str[idx++]=c, c = fgetc(txt); if (c != '=') //无效的解析直接结束 break; //4.0 找到 第一个 " while (c != EOF && c !='\"') str[idx++] = c, c = fgetc(txt); if (c != '\"') //无效的解析直接结束 break; //4.1 寻找第二个等号 do { n = str[idx++] = c; c = fgetc(txt); } while (c != EOF && c != '\"' && n != '\\'); if (c != '\"') //无效的解析直接结束 break; str[idx] = '\0'; puts(str); //最后读取到行末尾 while (c != EOF && c != '\n') c = fgetc(txt); if (c != '\n') break; } fclose(txt); system("pause"); return 0; }
上面 代码的输出结果是 就是上面截图. 算法比较直白很容易理解. 到这里 写了一个小功能还是很有成就感的. 特别是看那些著名的开源代码库,特别爽.
上面代码完全可以自己练习一下,很有意思,到这里完全没有难度. 后面将从以前的框架角度优化这个代码.
3.最后定稿的接口说明
到这里我们将上面的思路用到实战上, 首先看 scconf.h 接口内容如下
#ifndef _H_SCCONF #define _H_SCCONF /** * 这里是配置文件读取接口, * 写配置,读取配置,需要配置开始的指向路径 _STR_SCPATH */ #define _STR_SCCONF_PATH "module/schead/config/config.ini" /* * 启动这个配置读取功能,或者重启也行 */ extern void sc_start(void); /* * 获取配置相应键的值,通过key * key : 配置中名字 * : 返回对应的键值,如果没有返回NULL,并打印日志 */ extern const char* sc_get(const char* key); #endif // !_H_SCCONF
接口只有两个,启用这个配置读取库,获取指定key的内容, 现在文件目录结构如下
上面接口使用方式也很简单例如
sc_start(); puts(sc_get("heoo"));
其中 config.ini 配置内容如下
/* * 这里等同于php 定义变量那样形式,定义 *后面可以通过,配置文件读取出来. sc_get("heoo") => "你好!" */ $heoo = "Hello World\n"; $yexu = "\"你好吗\", 我很好.谢谢!"; $end = "coding future 123 runing, ";
后面就直接介绍具体实现内容了.
4.融于当前开发库中具体实现
首先看scconf.c 实现的代码
#include <scconf.h> #include <scatom.h> #include <tree.h> #include <tstring.h> #include <sclog.h> //简单二叉树结构 struct dict { _TREE_HEAD; char* key; char* value; }; // 函数创建函数, kv 是 [ abc\012345 ]这样的结构 static void* __dict_new(tstring tstr) { char* ptr; //临时用的变量 struct dict* nd = malloc(sizeof(struct dict) + sizeof(char)*(tstr->len+1)); if (NULL == nd) { SL_NOTICE("malloc struct dict is error!"); exit(EXIT_FAILURE); } nd->__tn.lc = NULL; nd->__tn.rc = NULL; // 多读书, 剩下的就是伤感, 1% ,不是我, nd->key = ptr = (char*)nd + sizeof(struct dict); memcpy(ptr, tstr->str, tstr->len + 1); while (*ptr++) ; nd->value = ptr; return nd; } // 开始添加 static inline int __dict_acmp(tstring tstr, struct dict* rnode) { return strcmp(tstr->str, rnode->key); } //查找和删除 static inline int __dict_gdcmp(const char* lstr, struct dict* rnode) { return strcmp(lstr, rnode->key); } //删除方法 static inline void __dict_del(void* arg) { free(arg); } //前戏太长,还没有结束, 人生前戏太长了,最后 ... static tree_t __tds; //保存字典 默认值为NULL //默认的 __tds 销毁函数 static inline void __tds_destroy(void) { tree_destroy(&__tds); } static int __lock; //加锁用的,默认值为 0 static void __analysis_start(FILE* txt, tree_t* proot) { char c, n; TSTRING_CREATE(tstr); //这里处理读取问题 while ((c = fgetc(txt)) != EOF) { //1.0 先跳过空白字符 while (c != EOF && isspace(c)) c = fgetc(txt); //2.0 如果遇到第一个字符不是 '$' if (c != '$') { //将这一行读取完毕 while (c != EOF && c != '\n') c = fgetc(txt); continue; } //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕 if ((c = fgetc(txt)) != EOF && isspace(c)) { while (c != EOF && c != '\n') c = fgetc(txt); continue; } //开始记录了 tstr.len = 0; //3.0 找到第一个等号 while (c != EOF && c != '=') { if(!isspace(c)) tstring_append(&tstr, c); c = fgetc(txt); } if (c != '=') //无效的解析直接结束 break; c = '\0'; //4.0 找到 第一个 " while (c != EOF && c != '\"') { if (!isspace(c)) tstring_append(&tstr, c); c = fgetc(txt); } if (c != '\"') //无效的解析直接结束 break; //4.1 寻找第二个等号 for (n = c; (c = fgetc(txt)) != EOF; n = c) { if (c == '\"' && n != '\\') break; tstring_append(&tstr, c); } if (c != '\"') //无效的解析直接结束 break; //这里就是合法字符了,开始检测 了, tree_add(proot, &tstr); //最后读取到行末尾 while (c != EOF && c != '\n') c = fgetc(txt); if (c != '\n') break; } TSTRING_DESTROY(tstr); } /* * 启动这个配置读取功能,或者重启也行 */ void sc_start(void) { FILE* txt = fopen(_STR_SCCONF_PATH, "r"); if (NULL == txt) { SL_NOTICE("fopen " _STR_SCCONF_PATH " r is error!"); return; } ATOM_LOCK(__lock); //先释放 这个 __tds, 这个__tds内存同程序周期 __tds_destroy(); //这个底层库,内存不足是直接退出的 __tds = tree_create(__dict_new, __dict_acmp, __dict_gdcmp, __dict_del); //下面是解析读取内容了 __analysis_start(txt, &__tds); ATOM_UNLOCK(__lock); fclose(txt); } /* * 获取配置相应键的值,通过key * key : 配置中名字 * : 返回对应的键值,如果没有返回NULL,并打印日志 */ inline const char* sc_get(const char* key) { struct dict* kv; if ((!key) || (!*key) || !(kv = tree_get(__tds, key, NULL))) return NULL; return kv->value; }
数据结构采用的通用的 tree.h 二叉查找树结构. 锁采用的 scatom.h 原子锁, 保存内容采用的 tstring.h 字符串内存管理
语法解析 是 按照上面的读取思路 解析字符
static void __analysis_start(FILE* txt, tree_t* proot);
数据结构是
//简单二叉树结构 struct dict { _TREE_HEAD; char* key; char* value; };
简单带key的二叉树结构.
5.使用展示
我们来测试一下上面库, 首先测试代码如下 test_scconf.c
#include <schead.h> #include <scconf.h> // 写完了,又能怎样,一个人 int main(int argc, char* argv[]) { const char* value; sc_start(); //简单测试 配置读取内容 value = sc_get("heoo"); puts(value); value = sc_get("heoo2"); if (value) puts(value); else puts("heoo2不存在"); value = sc_get("yexu"); puts(value); value = sc_get("end"); puts(value); system("pause"); return 0; }
运行结果如下
其中配置 看 上面 config.ini . 到这里关于简单的配置文件功能我们就完成了. 我想扯一点, 用过较多的语言或库, 还是觉得 高级VS + .Net 库 开发最爽,
拼积木最愉快,什么功能都用,代码设计也很漂亮, 唯一可惜的就是速度有点慢,有点臃肿.个人感觉 开发 C#库都是window 届 C++的顶级大牛, C#没有推广
出去却把window上C++程序员坑的要死,都转行到 Linux C/C++开发行列中. 更加有意思的是 C#没有因为 微软老爸红了,却因Unity 3D的 干爹火了.
C#+VS写起来确实很优美, 但是 如果你不用VS那就 另说了, 考验一个window程序员功底就是 , 让他离开了vs是否开始那么 销魂.
推荐做为一个程序员还是多学点, 因为都是语言, 学多了以后交流方便.大家觉得呢.
后语
错误是难免的, 欢迎指正, 随着年纪增长愈发觉得自己还很水. 需要学习很多东西,也需要舍弃很多东西. 对于未知的前方,
全当乱走的记录.看开源的项目源码还是很爽的,下次有机会分享手把手写个高效cjson引擎.