使用jq优化从大型JSON数组中的对象检索
我需要从一个非常大的JSON数组中获取一个特定索引的对象。这个数组里有200万个对象,文件大小大约是5GB。
我尝试了用jq
和Python结合的各种方法,但性能一直是个问题。以下是我尝试过的一些方法:
直接索引:
jq -c '.[100000]' Movies.json
先加载再索引:
jq --slurp '.[0].[100000]' Movies.json
使用
nth()
:jq -c 'nth(100000; .[])' Movies.json
虽然这些方法看起来能用,但速度太慢,达不到我的要求。我还尝试了使用流处理,这样性能有了显著提升:
jq -cn --stream 'nth(100000; fromstream(1|truncate_stream(inputs)))' Movies.json
不过,随着索引的增加,获取时间也在增加,我怀疑这和流处理的工作方式有关。
我知道一个选择是把文件分成小块,但我更不想通过这种方式来创建额外的文件。
JSON结构示例:
[
{
"Item": {
"Name": "Darkest Legend",
"Year": 1992,
"Genre": ["War"],
"Director": "Sherill Eal Eisenberg",
"Producer": "Arabella Orth",
"Screenplay": ["Octavia Delmer"],
"Cast": ["Johanna Azar", "..."],
"Runtime": 161,
"Rate": "9.0",
"Description": "Robin Northrop Cymbre",
"Reviews": "Gisela Seumas"
},
"Similars": [
{
"Name": "Smooth of Edge",
"Year": 1985,
"Genre": ["Western"],
"Director": "Vitoria Eustacia",
"Producer": "Auguste Jamaal Corry",
"Screenplay": ["Jaquenette Lance Gibe"],
"Cast": ["Althea Nicole", "..."],
"Runtime": 96,
"Rate": "6.5",
"Description": "Ashlan Grobe",
"Reviews": "Annnora Vasquez"
}
]
},
...
]
我该如何提高从这么大数组中获取对象的效率呢?
3 个回答
这不是一个经过验证的解决方案(因为缺少数据),但我认为这个表达式
nth(100000; fromstream(1|truncate_stream(inputs)))
会创建100000个对象,然后把它们扔掉(除了最后一个)。
这个表达式应该能避免这种浪费,可能会更快:
fromstream(1|truncate_stream( inputs | select(.[0][0] == 100000)))
如果你想要重复做这件事,可以先准备一个文件,每一行放一个项目,这样只需要花一次时间来获取最后一行。然后你可以用外部工具来快速获取数据,因为这些工具只需要查找行分隔符(在这里就是换行符)。这样,JSON的解析就只需要进行一次。
比如,可以使用 sed
作为外部工具:
jq -c '.[]' > lines # slow, once only
sed '100000q;d' lines # fast, repeatable
:
或者用 awk
:
jq -c '.[]' > lines
awk 'NR==100000 {print; exit}' lines
:
之后,你可以再次使用 jq
来处理获取到的项目。
如果你不想创建中间文件或数据库(比如@pmf建议的那样),但又想要比jq --stream
更快的工具,可以考虑使用基于“JSON Machine”的命令行程序,叫做jm(或者类似的Python程序jm.py
)。假设input.json
里有一个JSON数组,而你只想要第1000个项目,你可以这样写:
jm --limit 1000 input.json | sed -n '$p'
或者
jm.py --limit 1000 input.json | sed -n '$p'
这些方法通常会比使用jq --stream
稍微快一点,原因在于JSON Machine正好做了我们需要的事情:最小化解析顶层数组,以便“展开”它。
这里有一个性能总结(按运行时间从小到大排列),文件名为1e7.json,里面包含了1e7个对象的数组:
u+s(secs) mrss
jm.py --limit 100000 | sed -n '$p' 1.16 15163392
jm --limit 100000 | sed -n '$p' 1.9 15532032
gojq -n --stream ...skimpy... 2.54 10375168
jq -n --stream ...skimpy... 3.3 1826816
gojq -n --stream ... 4.47 189091840
jq -n --stream ... 5.41 1843200
jsonpointer /99999 17.8 4636860416
其中...
是jq程序:
nth(99999; fromstream( inputs|(.[0] |= .[1:]) | select(. != [[]]) ))
而简单的程序是:
nth(99999; fromstream(1|tostream(inputs)))
供参考:
$ /usr/bin/time -lp jq length 1e7.json
10000000
user 44.65
sys 9.38
5196390400 maximum resident set size
声明:我是jm和jm.py脚本的作者。