要做存储业务,我解析了一个项目的源码

最近在做存储相关的业务,更具体的来说是存储相关的研发,于是就上网查了一下相关的资料,思虑再三打算从最简单的 Json 数据交换格式开始研究。

JSON是独立于编程语言的数据交换格式,几乎所有与网络开发相关的语言都有JSON函数库,例如:C语言里的cJSON,Golang语言里的package json,Java语言里的jackson,Python语言中的pyson等。

1、JSON介绍

2、cJSON源码分析

3、如何设计“key-value”的数据结构

4、如何设计JSON节点操作函数

5、如何设计解析JSON字符串函数

6、如何设计JSON节点转JSON字符串

1、JSON介绍

可能有些还是比较还在读书的学弟学妹还不知道什么是JSON,有什么约定,这里就大致介绍一下,已经了解的可以直接跳过该部分。

要开发JSON编解码器,当然要了解一下什么是JSON

JSON基本数据格式采用的是“键值对”,即“key : value”形式,其中key只能是字符串,而value的数据类型有多种;多个“键值对”之间使用逗号,分隔,键与值之间用冒号:分隔

JSON的value基本数据类型:

  1. 数值(number):十进制数,不能有前导0,可以为负数,可以有小数部分。不区分整数与浮点数

  2. 字符串(string):以双引号""括起来的零个或多个Unicode码位。支持反斜杠开始的转义字符序列

  3. 对象(object):由无序的“键值对”组成,使用花括号{}包裹

  4. 数组(array):有序的零个或者多个值,每个值可以为任意类型,使用方括号包裹,形如:[value, value]

  5. true:布尔值true

  6. false:布尔值false

  7. null:空值

四个特定字符被认为是空白符:空格符(' ')、水平制表符('\t')、回车符('\r')、换行符('\n')

JSON字符串中,在元素前后允许存在无意义的空白符(会被忽略)

JSON字符串示例

{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "children": [
    "Catherine",
    "Thomas",
    "Trevor"
  ],
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "spouse": null
}

2、cJSON源码分析

这里选择cJSON做进一步了解和解析,因为C语言是编程语言之母,也是我的第一门语言。

013b9037753f420e64e493f899046da2.png
cJSON仓库

cJSON是github上一个star数为8.8k的仓库,是很多C/C++从业者开始学习源码知识都会开始选择的一个项目。

本文将对cJSON源码进行一个介绍,碍于篇幅原因写不了太多东西,这里只写一些源码解析过程中的宏观介绍。即使是这样,这篇文章也有将近5900 字之多。

3f96fe5e3e3ef41ecf944af53f30e111.png

所以具体的实现细节可以参考附详细注释的源码仓库:https://github.com/MingWangSong/MycJSON

我们了解清楚JSON数据的约定后,即可边提设计需求边分析cJSON是如何实现,

JSON编解码工具包主要提供JSON序列化和反序列化的这个两大块功能。

其中,序列化是指将编程语言结构化数据转化为JSON字符串;反序列化则是指相反的操作。

表述形象一点,JSON序列化就是输出或者打印JSON字符串,JSON反序列化操作是为了解析JSON字符串。

97b418738d63bd3797c9a1c177b96592.png
JSON序列化与反序列化

JSON数据整体就是一个对象类型的数据,可以假象成一个key缺省的根节点;将一个“key-value”视为JSON中的基本结构,整个JSON中处处为“key-value”(这句式有Linux味)。

因此,要想实现JSON序列化和反序列化,则需定义“key-value”的数据结构,该结构称之为JSON节点

3、如何设计“key-value”的数据结构

  1. “key-value”数据结构的设计需要考虑两个事情:单一“key-value”的数据表示多个“key-value”之间的关系表示

  2. 针对多个“key-value”之间的关系表示,cJSON的设计想法是通过链表来串联JSON节点,具体来讲,使用单链表实现JSON嵌套关系,使用双向链表实现JSON同级关系。

    因此,后续的所有JSON节点操作都很链表相关。下面举例绘图说明:

    {
      "firstName": "John",
      "lastName": "Smith",
      "children": [
        "Catherine",
        "Thomas",
        "Trevor"
      ],
      "address": {
        "streetAddress": "2nd-Street",
        "city": "New York"
      }
    }

    cJSON处理上述JSON数据的结构示意图如下:

    755e9435f2ea3817a42495a6b4cfd867.png

    在上图第一列中,黄色块为JSON根节点,该节点key缺省,value对应JSON字符串最外围{}包裹的若干个JSON节点,这些节点为根节点的子节点,图中红色箭头代表指向子节点的指针;

    第二列代表根节点下的一级JSON子节点,同级节点构成双链表,图中黑色指针为双链表的前驱和后继指针。

    值得注意的是,cJSON处理数组对象时,是将数组中每个元素视为一个JSON节点存储,同样采用双链表的形式存储,数组类型的JSON节点的value是没有值的,而是通过指向子节点的指针来存储数组地址,因此对象类型节点和数组类型节点的后续很多操作都可复用,非常的妙!

  3. cJSON节点数据结构设计如下:

    typedef struct cJSON {
        struct cJSON *next;  // 同级节点后继指针
        struct cJSON *prev;  // 同级节点前驱指针
        struct cJSON *child; // 子节点指针
        int type;            // “value”的类型,一共有9种类型
        char *valuestring;   // “value”类型为字符串对应的存值变量
        int valueint;        // “value”类型为整数对应的存值变量(写入valueint已弃用)
        double valuedouble;  // “value”类型为浮点数对应的存值变量
        char *string;        // 键值对中的“key”值
    } cJSON;

    其中,next,prev,child分别代表前驱节点、后继节点和子节点,用于处理JSON数据节点间的层级关系;type表示该JSON节点的value类型。特别注意,变量string代表着key(这个命名让我很不爽)。

    因为类型定义中区分了布尔值的true和false两种类型,所以布尔类型的JSON节点实际不需要其他变量存储value值。具体类型定义如下:

    #define cJSON_Invalid (0)       // 非法类型
    #define cJSON_False (1 << 0)
    #define cJSON_True (1 << 1)
    #define cJSON_NULL (1 << 2)
    #define cJSON_Number (1 << 3)
    #define cJSON_String (1 << 4)
    #define cJSON_Array (1 << 5)
    #define cJSON_Object (1 << 6)
    #define cJSON_Raw (1 << 7)      // raw json
    #define cJSON_IsReference (1 << 8)
    #define cJSON_StringIsConst (1 << 9)

4、如何设计JSON节点操作函数

无论是JSON序列化还是反序列化,都无法避免针对JSON节点的操作,因此先了解cJSON中JSON节点操作设计

  1. 创建JSON节点

    4ca9d10430e2b8e10b2a72284c7f27ec.png

    上图为cJSON中创建JSON节点的调用流程图。根据cJSON源码实现方式,创建JSON结点函数可大致分为三类:创建非数组类型JSON节点,创建数组类型JSON节点,创建JSON节点引用。由于同类函数实现逻辑相似。

    创建JSON节点拢共分三步:开辟空间,设置节点类型,设置节点value。

    注意,创建节点的时候不会设置key值,因此要新增节点的时候不会直接调用此类函数,而是提供了对应的Add函数,后文将会详细介绍。

  • 创建非数组类型JSON节点

    创建非数组类型节点时,先调用cJSON_New_Item()为cJSON节点申请空间并用memset进行初始化,然后再设置节点类型和value。如果是添加指定的节点到指定的对象或者数组,则需设置nextchild指针,源码如下:

    cJSON * cJSON_CreateObjectReference(const cJSON *child){
        cJSON *item = cJSON_New_Item(&global_hooks);
        if (item != NULL) {
            item->type = cJSON_Object | cJSON_IsReference;
            item->child = (cJSON*)cast_away_const(child);
        }
        return item;
    }
    cJSON * cJSON_CreateArrayReference(const cJSON *child) {
        cJSON *item = cJSON_New_Item(&global_hooks);
        if (item != NULL) {
            item->type = cJSON_Array | cJSON_IsReference;
            item->child = (cJSON*)cast_away_const(child);
        }
        return item;
    }
  • 创建数组类型JSON节点

    创建数组类型节点时,先调用cJSON_New_Item()为cJSON节点申请空间并用memset进行初始化,然后再设置节点类型,接着,需要根据数组数组构建子节点链表,子节点链表是双向链表,并且头结点前驱指针指向尾结点,以便尾插入的时候快速定位尾结点。

新增JSON节点

93728fc430e6624660ceb13fc63e7f83.png

由于cJSON中采用链表结构存储节点,因此新增JSON节点涉及的是链表中的插入操作。因为在cJSON中,value中的数组元素也是采用JSON节点来存储的,所以在数组用添加元素也是一样的链表操作。

由上图可知,新增JSON节点的核心函数是add_item_to_array(),该方法已尾插法的形式插入新建的JSON节点,为了能快速找到尾结点,每次插入时会更新头结点的前驱指针指向尾结点,具体插入过程如下图所示。

cJSON还提供了cJSON_InsertItemInArray(),该函数实现了在数组任意位置插入元素。

01ba8db6f1a7587463d225cf18cc3de6.png

替换JSON节点

9b4b8736d309d0ae733cc7cbeae06eaa.png

替换JSON节点的实现核心函数是cJSON_ReplaceItemViaPointer,主要是双链表中元素的替换操作。

删除JSON节点

89570931f49215011e9e461b55f8a463.png

删除JSON节点的核心函数是cJSON_DeletecJSON_DetachItemViaPointer,递归删除指定节点及其所有子节点,但是cJSON_IsReference类型的节点不会删除。

其中,cJSON_DetachItemViaPointer主要处理待删除节点的相关指针;cJSON_Delete主要用于释放删除后的节点存储空间。

查找JSON节点

ac8cc87a07c0e1370f2a2a84cc52bb08.png

查找JSON节点的核心函数是get_object_item,由于是链表存储结构,因此只能遍历查找。

5、如何设计解析JSON字符串函数

4bec8bc81a55cb57262c31232ff3b514.png

使用cJSON来解析JSON节点时,直接调用cJSON_Parse()函数,上图展示该函数的调用关系,核心函数是parse_valueparse_object。其中,parse_value实现了分别解析不同的类型的value值,布尔类型和null类型直接在本函数中处理,数字、字符串、数组、对象类型通过封装函数分别处理;parse_object主要实现了针对JSON节点的解析,通过先借助parse_string解析key值,再通过parse_value解析value值。

特别地,因为cJSON中嵌套对象和数组的数据结构设计是一样的,因此parse_objectparse_array的实现几乎一样,区别主要在于parse_array没有key值需要解析。

6、如何设计JSON节点转JSON字符串函数

32592c519dadaffbea46aee8fcbca48b.png

对比解析JSON字符串函数的函数调用图可以发现,JSON节点转JSON字符串函数的设计逻辑类似,也包含两个核心函数print_valueprint_object,这里就不做更进一步的解析了。

希望今天的源码解析能对大家有帮助,以后会带来更多的源码级项目解析。


参考链接

  • DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C (github.com):https://github.com/DaveGamble/cJSON

  • JSON - 维基百科,自由的百科全书 (wikipedia.org):https://zh.wikipedia.org/zh-cn/JSON

  • JSON:https://www.json.org/json-zh.html

  • cJSON源码及解析流程详解_Tyler_Zx的博客-CSDN博客:https://blog.csdn.net/qq_38289815/article/details/103307262

  • 如何解析JSON数据及内存钩子的使用方法-腾讯云开发者社区-腾讯云 (tencent.com):https://cloud.tencent.com/developer/beta/article/1662821

  • cJSON更换默认的malloc函数 - chilkings - 博客园 (cnblogs.com):https://www.cnblogs.com/chilkings/p/15821140.html

  • cjson库的使用以及源码阅读 - cfzhang - 博客园 (cnblogs.com):https://www.cnblogs.com/cfzhang/p/99da02ab2f02520d458c415a5314f83d.html

  • C语言中.h和.c文件解析(很精彩)-腾讯云开发者社区-腾讯云 (tencent.com):https://cloud.tencent.com/developer/article/1690858

  • MingWangSong/MycJSON (github.com):https://github.com/MingWangSong/MycJSON

↓↓推荐↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22210.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+SpringMvc+vue+element实现高效学生社团平台管理

基于JavaSpringMvcvueelement实现高效学生社团平台管理 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

oracle数据库当中用户的创建,添加,授权,以及表的创建与表的简单介绍,以及在oracle数据库当中的约束以及约束条件的简单介绍

系列文章目录 (3条消息) oracle数据库简介 文章目录 系列文章目录 前言 一、用户的创建 1.1、创建命令 1.2、给予scott用户权限 1.3、以scott用户进行连接登录 二、表和表的设计原则 2.1、表的概念 2.1.1、表是从属于用户的 2.1.2、表是逻辑表(概念表)&#xff0c;不…

gpt.4.0-gpt 国内版

gpt 使用 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种预训练的语言模型&#xff0c;可用于多种自然语言处理任务&#xff0c;如情感分析、文本分类、文本生成等。下面是使用GPT的一些步骤和建议&#xff1a; 确定任务和数据集&#xff1a;首先&…

Hibernate 快速入门

Hibernate 快速入门 〇、前言一、搭建 Hibernate 项目步骤1:新建 Java 项目附录1:新建Java项目中的相关文件信息步骤2:添加 Hibernate 框架支持附录2:添加Hibernate框架支持后,Java项目中的相关文件信息步骤3:其他关键配置1、添加数据库驱动包(本文以MySQL为例)2、配置…

C++11 列表初始化initializer_list

引子 C11&#xff0c;是继C98后的一次有力更新&#xff0c;引进了很多好用的语法&#xff0c;STL也添加了几个新容器&#xff0c;也解决了很多的问题。本篇博客就学习一下C11列表初始化的新语法和 initializer_list 文章目录 引子一. 列表初始化二. initializer_list结束语 一…

计算机底层知识

汇编语言&#xff08;机器语言&#xff09;的执行过程 汇编语言的本质&#xff1a;机器语言的助记符 其实他就是机器语言 计算机通电->CPU读取内存中程序&#xff08;电信号输入&#xff09; ->时钟发生器不断震荡通电 ->推动CPU内部一步一步执行&#xff08;执行多…

安卓开发 | 将Vue项目打包为app

知识目录 一、写在前面✨二、Hbuilder X准备&#x1f495;2.1 Hbuilder X简介2.2 下载 三、打包&#x1f495;3.1 获取dist目录3.2 新建5app3.3 替换文件3.4 编写manifast.json文件3.5 app云打包 四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xf…

OJ练习第107题——二叉搜索子树的最大键值和

二叉搜索子树的最大键值和 力扣链接&#xff1a;1373. 二叉搜索子树的最大键值和 题目描述 给你一棵以 root 为根的 二叉树 &#xff0c;请你返回 任意 二叉搜索子树的最大键值和。 二叉搜索树的定义如下&#xff1a; 任意节点的左子树中的键值都 小于 此节点的键值。 任意…

龙蜥白皮书精选:利用 io_uring 提升数据库系统性能

文/高性能存储 SIG 01 背景介绍 传统的 IO 软件栈已经无法完全释放出高性能存储设备的性能&#xff0c;高性能 IO 栈是当前存储领域重点研究的课题之一&#xff0c;代表性的如用户态方案 SPDK&#xff0c;以及标准的内核态方案 io_uring。 02 关键技术 Linux 社区从零开始设…

SeaweedFs使用-通过http接口实现文件操作

通过http接口实现文件操作 SeaweedFs可通过filer的http接口/master中的http接口来进行文件上传 1.通过master的接口进行上传文件 通过各种方式进行请求接口&#xff1a;http://localhost:9333/submit, ip和端口号是master服务的信息。此接口通过post请求方式将文件的二进制流…

esp32CAM环境安装教程---串口驱动安装

前言 &#xff08;1&#xff09;本人安装好arduino 的ESP32环境之后&#xff0c; 发现一直下载不进去程序。一直说Cannot configure port, something went wrong. Original message: PermissionError。 &#xff08;2&#xff09;查阅了很多资料&#xff0c;用了各种办法&#…

自动生成测试用例_接口测试用例自动生成工具

前言 写用例之前&#xff0c;我们应该熟悉API的详细信息。建议使用抓包工具Charles或AnyProxy进行抓包。 har2case 我们先来了解一下另一个项目har2case 他的工作原理就是将当前主流的抓包工具和浏览器都支持将抓取得到的数据包导出为标准通用的 HAR 格式&#xff08;HTTP A…

图片模块封装:Glide高级使用+使用设计模式图片框架封装+Bitmap尺寸压缩和质量压缩+Bitmap加载大图长图

图片模块封装&#xff1a;Glide高级使用使用设计模式图片封装Bitmap尺寸压缩和质量压缩Bitmap加载大图长图 一.如何更换图片框架二.策略模式构建者模式图片框架搭建1.ImageOptions图片参数设置2.IImageLoader接口以及实现子类3.图片加载策略4.ImageLoaderManager6.业务模块中使…

tcp/ip

这里写自定义目录标题 线程 防止阻塞 123 windows下4 https://zhuanlan.zhihu.com/p/139454200 https://www.bilibili.com/video/BV1eg411G7pW/?spm_id_from333.337.search-card.all.click&vd_sourcee7d12c9f66ab8294c87125a95510dac9 with socket.socket() as s:s.bind(…

小航编程题库2022年NOC决赛图形化(小高组)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:A 第1题运行下面的程序&#xff0c;最终“我的变量”的值是多少&#xff1f; A、5B、10C、25D、30 答案…

计及N-k安全约束的含光热电站电力系统优化调度模型【IEEE14节点、118节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

南京邮电大学算法与设计实验三:动态规划法(最全最新,与题目要求一致)

实验原理&#xff1a; 1、用动态规划法和备忘录方法实现求两序列的最长公共子序列问题。要求掌握动态规划法思想在实际中的应用&#xff0c;分析最长公共子序列的问题特征&#xff0c;选择算法策略并设计具体算法&#xff0c;编程实现两输入序列的比较&#xff0c;并输出它们的…

编译原理之词法分析实验(附完整C/C++代码与总结)

一、实验内容 通过完成词法分析程序&#xff0c;了解词法分析的过程。编制一个读单词程序&#xff0c;对PL/0语言进行词法分析&#xff0c;把输入的字符串形式的源程序分割成一个个单词符号&#xff0c;即基本保留字、标识符、常数、运算符、分界符五大类。 对PL/0语言进行词法…

【野火启明_瑞萨RA6M5】按键输入检测

文章目录 一、GPIO输入——按键输入检测二、硬件设计三、软件设计下载验证 一、GPIO输入——按键输入检测 按键检测原理 按键机械触点断开、闭合时&#xff0c;由于触点的弹性作用&#xff0c;按键开关不会马上稳定接通或一下子断开&#xff0c;使用按键时会产生 下图中的带波…

APlayer MetingJS 音乐播放器使用指南

文章目录 1.引用2.安装3.APlayer 原生用法4.MetingJS 的用法 1.引用 APlayer 是一个简洁漂亮、功能强大的 Html5 音乐播放器&#xff0c;GitHub地址&#xff1a;https://github.com/DIYgod/APlayer MetingJS 是为 APlayer 添加网易云、QQ音乐等支持的插件&#xff0c;GitHub地…