前言
最近看到這篇文章,
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引擎.