使用jq优化从大型JSON数组中的对象检索

1 投票
3 回答
111 浏览
提问于 2025-04-14 18:23

我需要从一个非常大的JSON数组中获取一个特定索引的对象。这个数组里有200万个对象,文件大小大约是5GB。

我尝试了用jq和Python结合的各种方法,但性能一直是个问题。以下是我尝试过的一些方法:

  1. 直接索引:

    jq -c '.[100000]' Movies.json
    
  2. 先加载再索引:

    jq --slurp '.[0].[100000]' Movies.json
    
  3. 使用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 个回答

0

这不是一个经过验证的解决方案(因为缺少数据),但我认为这个表达式

nth(100000; fromstream(1|truncate_stream(inputs)))

会创建100000个对象,然后把它们扔掉(除了最后一个)。

这个表达式应该能避免这种浪费,可能会更快:

fromstream(1|truncate_stream( inputs | select(.[0][0] == 100000)))
2

如果你想要重复做这件事,可以先准备一个文件,每一行放一个项目,这样只需要花一次时间来获取最后一行。然后你可以用外部工具来快速获取数据,因为这些工具只需要查找行分隔符(在这里就是换行符)。这样,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 来处理获取到的项目。

2

如果你不想创建中间文件或数据库(比如@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脚本的作者。

撰写回答