大家好,欢迎来到IT知识分享网。
RapidJSON是腾讯的开源JSON解析框架,是一个高效的C++ JSON解析/生成器。凭借其简洁的API和优化的内存管理,它成为了许多开发者的首选JSON库。本文从性能、优点、用法、内部结构及注意事项等五个方面对其进行详解。
一 RapidJSON性能对比
RapidJSON和其他常见C++ JSON库的解析耗时对比如下:
可见RapidJson在一众C++ Json库中,性能非常优秀!解析耗时是JsonCpp的1/20、CAJUN的1/141。
二 RapidJSON优点
RapidJSON 是腾讯的开源JSON解析框架,是一个高效的C++ JSON解析/生成器,提供SAX及DOM风格API,它的灵感来自RapidXml,有如下优点:
- RapidJSON 小而全。它同时支持 SAX 和 DOM 风格的 API。SAX 解析器只有约 500 行代码。
- RapidJSON 快。它的性能可与 strlen() 相比。可支持 SSE2/SSE4.2 加速。
- RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至不依赖于 STL。
- RapidJSON 对内存友好。在大部分 32/64 位机器上,每个 JSON 值只占 16 字节(除字符串外)。它预设使用一个快速的内存分配器,令分析器可以紧凑地分配内存。
- RapidJSON 对 Unicode 友好。它支持 UTF-8、UTF-16、UTF-32 (大端序/小端序),并内部支持这些编码的检测、校验及转码。例如,RapidJSON 可以在分析一个 UTF-8 文件至 DOM 时,把当中的 JSON 字符串转码至 UTF-16。它也支持代理对(surrogate pair)及 “\u0000″(空字符)。
三 RapidJSON用法
3.1 查询Value
假设我们用 C 语言的字符串储存一个 JSON:
{ "hello": "world", "t": true , "f": false, "n": null, "i": 123, "pi": 3.1416, "a": [1, 2, 3, 4] }
把它解析至一个 Document:
#include "rapidjson/document.h" using namespace rapidjson; // ... Document document; document.Parse(json);
那么现在该 JSON 就会被解析至 document 中,成为一棵 *DOM 树 *:
查询一下根 Object 中有没有 “hello” 成员。由于一个 Value 可包含不同类型的值,我们可能需要验证它的类型,并使用合适的 API 去获取其值。在此例中,”hello” 成员关联到一个 JSON String。
assert(document.HasMember("hello")); assert(document["hello"].IsString()); printf("hello = %s\n", document["hello"].GetString());
3.2 查询Array
缺省情况下,SizeType 是 unsigned 的 typedef。在多数系统中,Array 最多能存储 2^32-1 个元素。
你可以用整数字面量访问元素,如 a[0]、a[1]、a[2]。
Array 与 std::vector 相似,除了使用索引,也可使用迭代器来访问所有元素。
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) printf("%d ", itr->GetInt());
3.3 查询 Object
和 Array 相似,我们可以用迭代器去访问所有 Object 成员:
static const char* kTypeNames[] = { "Null", "False", "True", "Object", "Array", "String", "Number" }; for (Value::ConstMemberIterator itr = document.MemberBegin(); itr != document.MemberEnd(); ++itr) { printf("Type of member %s is %s\n", itr->name.GetString(), kTypeNames[itr->value.GetType()]); }
3.4 查询 Number
JSON 只提供一种数值类型──Number。数字可以是整数或实数。RFC 4627 规定数字的范围由解析器指定。
由于 C++ 提供多种整数及浮点数类型,DOM 尝试尽量提供最广的范围及良好性能。
当解析一个 Number 时, 它会被存储在 DOM 之中,成为下列其中一个类型:
当查询一个 Number 时, 你可以检查该数字是否能以目标类型来提取:
3.5 查询String
除了 GetString(),Value 类也有一个 GetStringLength()。这里会解释个中原因。
根据 RFC 4627,JSON String 可包含 Unicode 字符 U+0000,在 JSON 中会表示为 “\u0000″。问题是,C/C++ 通常使用空字符结尾字符串(null-terminated string),这种字符串把 `‘\0’` 作为结束符号。
为了符合 RFC 4627,RapidJSON 支持包含 U+0000 的 String。若你需要处理这些 String,便可使用 GetStringLength() 去获得正确的字符串长度。
例如,当解析以下的 JSON 至 Document d 之后:
{ "s" : "a\u0000b" }
“a\u0000b” 值的正确长度应该是 3。但 strlen() 会返回 1。
GetStringLength() 也可以提高性能,因为用户可能需要调用 strlen() 去分配缓冲。
此外,std::string 也支持这个构造函数:
string(constchar* s, size_t count);
此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
3.6 比较两个 Value
你可使用 == 及 != 去比较两个 Value。当且仅当两个 Value 的类型及内容相同,它们才当作相等。你也可以比较 Value 和它的原生类型值。以下是一个例子。
if (document["hello"] == document["n"]) /*...*/; // 比较两个值 if (document["hello"] == "world") /*...*/; // 与字符串字面量作比较 if (document["i"] != 123) /*...*/; // 与整数作比较 if (document["pi"] != 3.14) /*...*/; // 与 double 作比较
3.7 改变 Value 类型
当使用默认构造函数创建一个 Value 或 Document,它的类型便会是 Null。要改变其类型,需调用 SetXXX() 或赋值操作,例如:
Document d; // Null d.SetObject(); Value v; // Null v.SetInt(10); v = 10; // 简写,和上面的相同
3.8 转移语义
在设计 RapidJSON 时有一个非常特别的决定,就是 Value 赋值并不是把来源 Value 复制至目的 Value,而是把来源 Value 转移(move)至目的 Value。例如:
Value a(123); Value b(456); b = a; // a 变成 Null,b 变成数字 123。
这么做的好处是:减少了大量的内存拷贝
3.9 创建 String
RapidJSON 提供两个 String 的存储策略。
- copy-string: 分配缓冲区,然后把来源数据复制至它。
- const-string: 简单地储存字符串的指针。
Copy-string 总是安全的,因为它拥有数据的克隆。Const-string 可用于存储字符串字面量。当我们把一个 copy-string 赋值时, 调用含有 allocator 的 SetString() 重载函数:
Document document; Value author; char buffer[10]; int len = sprintf(buffer, "%s %s", "Milo", "Yip"); // 动态创建的字符串。 author.SetString(buffer, len, document.GetAllocator()); memset(buffer, 0, sizeof(buffer)); // 清空 buffer 后 author.GetString() 仍然包含 "Milo Yip"
正常解析:
原位解析:
3.10 修改Array
Array 类型的 Value 提供与 std::vector 相似的 API。
- Clear()
- Reserve(SizeType, Allocator&)
- Value& PushBack(Value&, Allocator&)
- template <typename T> GenericValue& PushBack(T, Allocator&)
- Value& PopBack()
- ValueIterator Erase(ConstValueIterator pos)
- ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)
注意,Reserve(…) 及 PushBack(…) 可能会为数组元素分配内存,所以需要一个 allocator。
以下是 PushBack() 的例子:
Value a(kArrayType); Document::AllocatorType& allocator = document.GetAllocator(); for (int i = 5; i <= 10; i++) a.PushBack(i, allocator); // 可能需要调用 realloc() 所以需要 allocator // 流畅接口(Fluent interface) a.PushBack("Lua", allocator).PushBack("Mio", allocator);
3.11 修改Object
Object 是键值对的集合。每个键必须为 String。要修改 Object,方法是增加或移除成员。以下的 API 用来增加成员:
- Value& AddMember(Value&, Value&, Allocator& allocator)
- Value& AddMember(StringRefType, Value&, Allocator&)
- template <typename T> Value& AddMember(StringRefType, T value, Allocator&)
以下是一个例子。
Value contact(kObject); contact.AddMember("name", "Milo", document.GetAllocator()); contact.AddMember("married", true, document.GetAllocator());
四 RapidJSON内部结构
4.1 GenericValue
4.2 JSON数据加载&输出
4.3 SAX 和 DOM
下面的 UML 图显示了 SAX 和 DOM 的基本关系。
关系的核心是 Handler 概念。在 SAX 一边,Reader 从流解析 JSON 并将事件发送到 Handler。Writer 实现了 Handler 概念,用于处理相同的事件。在 DOM 一边,Document 实现了 Handler 概念,用于通过这些时间来构建 DOM。Value 支持了 Value::Accept(Handler&) 函数,它可以将 DOM 转换为事件进行发送。
在这个设计,SAX 是不依赖于 DOM 的。甚至 Reader 和 Writer 之间也没有依赖。这提供了连接事件发送器和处理器的灵活性。除此之外,Value 也是不依赖于 SAX 的。所以,除了将 DOM 序列化为 JSON 之外,用户也可以将其序列化为 XML,或者做任何其他事情。
4.4 工具类
SAX 和 DOM API 都依赖于3个额外的概念:Allocator、Encoding 和 Stream。它们的继承层次结构如下图所示。
4.5 值的数据布局
Value 是可变类型。在 RapidJSON 的上下文中,一个 Value 的实例可以包含6种 JSON 数据类型之一。通过使用 union ,这是可能实现的。每一个 Value 包含两个成员:union Data data_ 和 unsigned flags_。flags_ 表明了 JSON 类型,以及附加的信息。
下表显示了所有类型的数据布局。32位/64位列表明了字段所占用的字节数。
4.6 解析器
迭代解析器是一个以非递归方式实现的递归下降的 LL(1) 解析器。
解析器使用的语法是基于严格 JSON 语法的:
注意到左因子被加入了非终结符的 values 和 members 来保证语法是 LL(1) 的。
在 RapidJSON 中,解析时会将解析表编码为状态机。 规则由头部和主体组成。 状态转换由规则构造。 除此之外,额外的状态被添加到与 array 和 object 有关的规则。 通过这种方式,生成数组值或对象成员可以只用一次状态转移便可完成, 而不需要在直接的实现中的多次出栈/入栈操作。 这也使得估计栈的大小更加容易。
状态图如如下所示:
五 RapidJson的注意事项
- Value赋值使用move语义,而不是copy语义。也就是说,拷贝构造和拷贝赋值函数都是用move语义实现的。
- 当string的生命周期不足时,Value应该使用Copy-string存储策略,否则value无法长期存储字符串。
- StringBuffer是一个简单的输出流,当该缓冲区满溢时会自动增加容量(默认为256个字符)。
- FileReadStream / FileWriteStream,和IStreamWrapper / OStreamWrapper,它们都是字节流,不处理编码。若输入流或输出流数据是非UTF-8编码时,输入流数据需要用EncodedInputStream或AutoUTFInputStream包装,而输出流数据需要用EncodedOutputStream 或AutoUTFOutputStream包装。
- 原位解析适合用于短期的、用完即弃的JSON。数据流的来源编码与目标编码必须相同,并且还要保证缓冲区的生命周期大于DOM。
- RapidJSON支持SSE2,SSE4.2和ARM Neon指令加速解析,并且只对UTF-8编码的内存流启用。然而,若在不支持这些指令集的机器上执行这些可执行文件,会导致崩溃。
- 不建议使用wistream和wostream。
六 参考资料
[1] github:git@github.com:Tencent/rapidjson.git
[2] rapidjson官网:http://rapidjson.org/zh-cn/index.html
[3] c++ json库对比:https://blog.csdn.net/_/article/details/
[4] rapidjson讲解:https://blog.csdn.net/kds0714/article/details/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/97950.html