分析Go中的各种高性能JSON解析库
liuian 2025-01-08 15:19 63 浏览
比较fastjson、gjson和jsonparser的性能、优点和缺点。
本文深入分析了Go中的标准库如何解析JSON,然后探索了流行的JSON解析库、其特征,以及它们如何更好地帮助我们在不同场景中的开发。
我不打算调查JSON库的性能问题。然而,最近,我在我的项目上做了一个pprof,从下面的火焰图中发现,业务逻辑处理中一半以上的性能消耗是在JSON解析期间。因此,这篇文章出现了。
本文深入分析了Go中的标准库如何解析JSON,然后探索了流行的JSON解析库、其特征,以及它们如何更好地帮助我们在不同场景中的开发。
主要介绍以下库的分析(2024-06-13):
JSON Unmarshal
func Unmarshal(数据 []字节,v interface{})“官方的JSON解析库需要两个参数:要序列化的对象和该对象的类型。在实际执行JSON解析之前,调用reflect.ValueOf来获取参数v的反射对象。然后,根据传入数据对象开头的非空字符来确定解析方法。”
func (d *decodeState) value(v reflect.Value) error {
开关d.opcode {
默认:
恐慌(phasePanicMsg)
// 数组
案例扫描BeginArray:
...
// 结构或地图
案例扫描BeginObject:
...
// 文字,包括int、string、float等。
案例扫描BeginLiteral:
...
}
返回零
}如果解析对象以[开头,则表示这是一个数组对象,并将进入scanBeginArray分支;如果以{开头,则表示解析对象是结构或映射,然后进入scanBeginObject分支,以此为由。
子摘要
查看Unmarshal的源代码,可以看出大量反射被用来获取字段值。如果JSON嵌套,则需要递归反射来获取值。因此,可以想象表现很差。
然而,如果性能不受到高度重视,直接使用它是一个不错的选择。它具有完整的功能,官方团队不断对其进行了其中和优化。也许它的性能在未来版本中也会带来质的飞跃。它应该是唯一一个可以直接将JSON对象转换为Go结构的。
fastjson
这个库的特点是速度快,就像它的名字一样。它的介绍页是这样说的:
快。像往常一样,比标准编码/json快15倍。
它的用法也很简单,如下:
func main() {
var p fastjson.Parser
v, _ := p.Parse(`{
"str": "bar",
"int":123,
浮动":1.23,
"bool":真的,
"arr": [1, "foo", {}]
}`)
fmt.Printf("foo=%s\n", v.GetStringBytes("str"))
fmt.Printf("int=%d\n", v.GetInt("int"))
fmt.Printf("float=%f\n", v.GetFloat64("float"))
fmt.Printf("bool=%v\n", v.GetBool("bool"))
fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))
}
// 输出:
// foo=bar
// int=123
// 浮动=1.230000
// bool=true
// arr.1=foo要使用fastjson,首先将JSON字符串交给Parser解析器进行解析,然后通过Parse方法返回的对象检索它。如果它是一个嵌套对象,在将参数传递给Get方法时,您可以直接传递相应的父子键。
分析
fastjson的设计与标准库Unmarshal不同,它将JSON解析分为两部分:解析和获取。
Parse负责将JSON字符串解析为结构并返回它。然后从返回的结构中检索数据。解析过程是无锁的,因此如果您想同时调用解析,则需要使用ParserPool。
fastjson通过从上到下遍历JSON,将解析的数据存储在Value结构中来处理JSON:
type Value struct { o Object a []*Value s string t Type }这个结构很简单:
- o Object:表示解析的结构是一个对象。
- a []*Value:表示解析的结构是一个数组。
- s string:如果解析的结构既不是对象也不是数组,则其他类型的值将作为字符串存储在此字段中。
- t Type:表示此结构的类型,可以是TypeObject、TypeArray、TypeString、TypeNumber等。
type Object struct { kvs []kv keysUnescaped bool } type kv struct { k string v *Value }这种结构存储对象的递归结构。在上述示例中解析JSON字符串后,结果结构如下所示:
代码
在实现方面,缺乏反射代码使整个解析过程非常干净。让我们直接看看解析的中心部分:
func parseValue(s string, c *cache, depth int) (*Value, string, error) {
如果 len(s) == 0 {
返回nil,s,fmt.Errorf(“无法解析空字符串”)
}
深度++
// json字符串的最大深度不能超过MaxDepth
如果深度 > MaxDepth {
返回nil,s,fmt.Errorf(“对于嵌套的JSON来说深度太大;它超过%d”,MaxDepth)
}
// 解析对象
如果 s[0] == '{' {
v, tail, err := parseObject(s[1:], c, depth)
如果是,那就是吧!= nil {
返回nil,tail,fmt.Errorf(“无法解析对象:%s”,err)
}
返回v,尾巴,零
}
// 解析数组
if s[0] == '[' {
...
}
// 解析字符串
如果 s[0] == '"' {
...
}
...
返回v,尾巴,零
}parseValue将根据字符串的第一个非空字符确定要解析的类型。在这里,一个对象类型用于解析:
func parseObject(s string, c *cache, depth int) (*Value, string, error) {
...
o := c.getValue()
o.t = 类型对象
o.o.reset()
为了{
var err 错误
// 获取对象结构中的kv对象
kv := o.o.getKV()
...
// 解析键值
kv.k, s, err = parseRawKey(s[1:])
...
// 递归解析值
kv.v, s, err = parseValue(s, c, depth)
...
// 如果遇到,请继续解析
如果 s[0] == ',' {
s = s[1:]
继续
}
// 解析完成
如果 s[0] == '}' {
返回o,s[1:],nil
}
返回nil, s, fmt.Errorf(“在对象值后缺少',”)
}
}parseObject函数也很简单。它将在循环中获取键值,然后递归调用parseValue函数,从上到下解析该值,逐个解析JSON对象,直到最后遇到}。
子摘要
通过上述分析,可以看出fastjson的实现要简单得多,并且比标准库具有更高的性能。使用解析解析JSON树后,可以多次重复使用,避免重复解析和提高性能。
然而,其功能非常简陋,缺乏常见的操作,如JSON到结构或JSON到地图转换。如果您只想简单地从JSON中检索值,那么使用此库非常方便。但是,如果您想将JSON值转换为结构,则需要自己手动设置每个值。
GJSON
在我的测试中,尽管GJSON的性能不像fastjson那样极端,但其功能非常完整,性能也相当不错。接下来,让我简要介绍一下GJSON的功能。
GJSON的用法类似于fastjson;它也非常简单。只需传递JSON字符串和需要作为参数获得的值。
json := `{"name":{"first":"li","last":"dj"},"age":18}`
姓氏:= gjson.Get(json,“name.last”)除了此功能外,还可以执行简单的模糊匹配。它支持通配符*和?在键中。*匹配任意数量的字符,而?匹配单个字符,如下所示:
json := `{
"name":{"first":"Tom", "last": "Anderson"},
年龄:37岁,
“孩子”:[“萨拉”,“亚历克斯”,“杰克”]
}`
fmt.Println("third child*:", gjson.Get(json, "child*.2"))
fmt.Println(“第一个c?ild:", gjson.Get(json, "c?ildren.0"))- child*.2:首先,child*匹配children,.2读取第三个元素;
- c?ildren.0:c?ildren匹配children,.0读取第一个元素;
除了模糊匹配外,它还支持修饰符操作。
json := `{
"name":{"first":"Tom", "last": "Anderson"},
年龄:37岁,
“孩子”:[“萨拉”,“亚历克斯”,“杰克”]
}`
fmt.Println("third child*:", gjson.Get(json, "children|@reverse"))children|@reverse 首先阅读数组“children”,然后使用修饰符“@reverse”来反转它并返回输出。
nestedJSON := `{"nested": ["one", "two", ["three", "four"]]}` fmt.Println(gjson.Get(nestedJSON, "nested|@flatten"))@flatten扁平化nested到外部数组的数组的内部数组,并返回:
["一," "二," "三," "四"]还有其他一些令人兴奋的功能,您可以在官方文档中查看。
分析
gjson的Get方法参数包括一个JSON字符串和一个路径,表示要获取的JSON值的匹配路径。
在gjson中,解析分为两部分,因为它需要满足解析场景的许多定义。在遍历JSON字符串之前,您需要解析路径。
如果您在解析过程中遇到可以匹配的值,它将直接返回,无需继续跟踪。如果匹配多个值,则将始终遍历整个JSON字符串。如果您遇到无法在JSON字符串中匹配的路径,则必须遍历完整的JSON字符串。
在解析过程中,解析内容不会保存在像fastjson这样的结构中,这种结构可以重复使用。因此,当您调用GetMany返回多个值时,您需要多次遍历JSON字符串,以便效率相对较低。
重要的是要知道@flatten函数不会验证JSON。这意味着,即使输入字符串不是有效的JSON,它仍然会被解析。因此,用户需要仔细检查输入是否为有效的JSON,以避免潜在问题。
代码
func Get(json,路径字符串)结果{
// 解析路径
if len(path) > 1 {
...
}
var i int
var c = &parseContext{json: json}
if len(path) >= 2 && path[0] == '.' && path[1] == '.'{
c.lines = 真实
parseArray(c,0,路径[2:])
} 否则 {
// 根据不同的对象进行解析,并在这里循环,直到找到'{'或'['
for ; i < len(c.json); i++ {
如果 c.json[i] == '{' {
i++
解析对象(c,i,路径)
打破
}
如果 c.json[i] == '[' {
i++
解析数组(c,i,路径)
打破
}
}
}
如果c.piped {
res := c.value.Get(c.pipe)
res.Index = 0
返回
}
fillIndex(json,c)
返回c.value
}在Get方法中,您可以看到一个用于解析各种路径的长代码字符串。然后,for循环连续遍历JSON,直到在执行相应的逻辑处理之前找到“{”或“[”。
func parseObject(s string, c *cache, depth int) (*Value, string, error) {
...
o := c.getValue()
o.t = 类型对象
o.o.reset()
为了{
var err 错误
// 获取对象结构中的kv对象
kv := o.o.getKV()
...
// 解析键值
kv.k, s, err = parseRawKey(s[1:])
...
// 递归解析值
kv.v, s, err = parseValue(s, c, depth)
...
// 如果遇到,请继续解析
如果 s[0] == ',' {
s = s[1:]
继续
}
// 解析完成
如果 s[0] == '}' {
返回o,s[1:],nil
}
返回nil, s, fmt.Errorf(“在对象值后缺少',”)
}
}在审查parseObject代码时,目的不是教授JSON解析或字符串遍历,而是说明一个糟糕的情况。嵌套循环和连续if语句可能会让人不知所措,可能会提醒您在工作中遇到的同事的代码。
子摘要
优点:
- 性能:与标准库相比,jsonparser的性能相对较好。
- 灵活性:它提供各种检索方法和可定制的返回值,使其非常方便。
缺点:
- 没有JSON验证:它不检查JSON输入的正确性。
- 代码气味:代码结构繁琐且难以阅读,使维护具有挑战性。
笔记
当解析JSON以检索值时,GetMany函数将根据指定的键多次遍历JSON字符串。将JSON转换为地图可以减少遍历次数。
结论
虽然jsonparser具有显著的性能和灵活性,但它缺乏JSON验证和复杂、难以阅读的代码结构存在重大缺点。如果您需要经常解析JSON和检索值,请考虑性能和代码可维护性之间的权衡。
json解析器
分析
jsonparser还处理输入JSON字节切片,并允许通过传递多个键快速定位和返回值。
与GJSON类似,jsonparser不会像fastjson那样在数据结构中缓存解析的JSON字符串。然而,当需要解析多个值时,EachKey函数可以通过JSON字符串在一次路径中解析多个值。
如果找到匹配的值,jsonparser将立即返回,无需进一步遍历。对于许多匹配,它遍历整个JSON字符串。如果路径与JSON字符串中的任何值不匹配,它仍然会遍历整个字符串。
jsonparser在JSON遍历期间使用循环来减少递归的使用,减少调用堆栈深度,并提高性能。
在功能方面,ArrayEach、ObjectEach和EachKey函数允许传递自定义函数以满足特定需求,大大增强了jsonparser的实用性。
jsonparser的代码简单明了,易于分析。有兴趣的人可以自己检查。
子摘要
与标准库相比,jsonparser的高性能可以归因于:
- 使用循环来最小化递归。
- 避免使用反射,与标准库不同。
- 找到相应的键值后立即退出,无需进一步递归。
- 在传递的JSON字符串上操作,而不分配新空间,从而减少内存分配。
此外,API设计很方便。ArrayEach、ObjectEach和EachKey等函数允许传递自定义函数,解决实际业务开发中的许多问题。
然而,jsonparser有一个重大缺点:它不验证JSON。如果输入不是有效的JSON,jsonparser将不会检测到它。
性能比较
解析小JSON字符串
解析大约190字节的简单JSON字符串
190字节JSON测试结果
解析中号JSON字符串
解析一个中等复杂度的JSON字符串,大约2.3KB
2.3KB JSON测试结果
解析大型JSON字符串
解析高复杂度的JSON字符串,大约2.2MB
2.2MB JSON测试结果
摘要
在这次比较中,我分析了几个高性能JSON解析库。很明显,这些图书馆有几个共同的特征:
- 他们避免使用反射。
- 他们通过按顺序遍历JSON字符串的字节来解析JSON。
- 他们通过直接解析输入JSON字符串来最小化内存分配。
- 他们为了性能而牺牲了一些兼容性。
Despite these trade-offs, each library offers unique features. The fastjsonAPI is the simplest to use; GJSON offers fuzzy searching capabilities and high customizability; jsonparser supports inserting callback functions during high-performance parsing, providing a degree of convenience.
对于我的用例,即简单地从具有预定字段和偶尔自定义操作的HTTP响应JSON字符串中解析特定字段,jsonparser是最合适的工具。
因此,如果性能与您有关,请考虑根据您的业务需求选择JSON解析器。
相关推荐
-
- 驱动网卡(怎么从新驱动网卡)
-
网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...
-
2026-01-30 00:37 liuian
- win10更新助手装系统(微软win10更新助手)
-
1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...
- windows11专业版密钥最新(windows11专业版激活码永久)
-
Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...
-
- 手机删过的软件恢复(手机删除过的软件怎么恢复)
-
操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...
-
2026-01-29 23:55 liuian
- 一键ghost手动备份系统步骤(一键ghost 备份)
-
步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。 步骤...
- 怎么创建局域网(怎么创建局域网打游戏)
-
1、购买路由器一台。进入路由器把dhcp功能打开 2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。 3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...
- 精灵驱动器官方下载(精灵驱动手机版下载)
-
是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...
- 一键还原系统步骤(一键还原系统有哪些)
-
1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。 2、在弹出的“准备安装”窗口中,可...
- 电脑加速器哪个好(电脑加速器哪款好)
-
我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...
- 任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)
-
是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...
- u盘怎么恢复文件(u盘文件恢复的方法)
-
开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...
- 系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)
-
1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...
-
- 剪贴板权限设置方法(剪贴板访问权限)
-
1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...
-
2026-01-29 21:37 liuian
- 平板系统重装大师(平板重装win系统)
-
如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...
- 联想官网售后服务网点(联想官网售后服务热线)
-
联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...
- 一周热门
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
