在PHP中处理大型JSON文件

在PHP中处理大型JSON文件,php,json,large-files,Php,Json,Large Files,我正在尝试处理一些较大(可能高达2亿)的JSON文件。 文件的结构基本上是一个对象数组 因此,大致如下: [ {"property":"value", "property2":"value2"}, {"prop":"val"}, ... {"foo":"bar"} ] 每个对象都具有任意属性,不必与数组中的其他对象共享这些属性(如中所示,具有相同的属性) 我想对数组中的每个对象应用一个处理,因为文件可能很大,所以我无法在内存中读取整个文件内容,解码JSON并迭代PHP数组 因此

我正在尝试处理一些较大(可能高达2亿)的JSON文件。 文件的结构基本上是一个对象数组

因此,大致如下:

[
  {"property":"value", "property2":"value2"},
  {"prop":"val"},
  ...
  {"foo":"bar"}
]
每个对象都具有任意属性,不必与数组中的其他对象共享这些属性(如中所示,具有相同的属性)

我想对数组中的每个对象应用一个处理,因为文件可能很大,所以我无法在内存中读取整个文件内容,解码JSON并迭代PHP数组

因此,理想情况下,我希望读取该文件,为每个对象获取足够的信息并对其进行处理。 如果JSON有类似的库,SAX类型的方法就可以了


有没有关于如何最好地处理这个问题的建议?

有,我自己没有用过。

有类似的建议,但只针对和。除非您可以从PHP访问其中一个库,否则据我所知,在PHP中没有实现此功能的方法,只有
json\u read()
。但是,如果json的结构如此简单,那么只需在下一个
}
之前读取文件,然后通过
json\u read()
处理接收到的json就很容易了。但是您最好执行缓冲处理,比如读取10kb,按}拆分,如果未找到,则读取另一个10k,否则处理找到的值。然后阅读下一个块,依此类推。

我决定使用基于事件的解析器。它还没有完全完成,当我推出一个令人满意的版本时,它将编辑带有我工作链接的问题

编辑:

我终于找到了一个我满意的解析器版本。可在GitHub上获得:


可能还有改进的余地,我欢迎反馈。

这是一个简单的流式解析器,用于处理大型JSON文档。使用它来解析非常大的JSON文档,以避免将整个内容加载到内存中,这就是几乎所有其他PHP JSON解析器的工作方式


我已经为PHP7编写了一个流式JSON拉式解析器,其api基于

它与基于事件的解析器的显著不同之处在于,您不需要设置回调并让解析器完成它的工作,而是调用解析器上的方法来按照需要移动或检索数据。找到所需的位并要停止分析?然后停止解析(并调用
close()
,因为这样做很好。)

(有关pull与基于事件的解析器的更详细概述,请参阅。)


例1: 从JSON中将每个对象作为一个整体读取

use pcrov\JsonReader\JsonReader;

$reader = new JsonReader();
$reader->open("data.json");

$reader->read(); // Outer array.
$depth = $reader->depth(); // Check in a moment to break when the array is done.
$reader->read(); // Step to the first object.
do {
    print_r($reader->value()); // Do your thing.
} while ($reader->next() && $reader->depth() > $depth); // Read each sibling.

$reader->close();
输出: 对象以字符串键控数组的形式返回(部分原因是)边缘情况,在这种情况下,有效的JSON将生成PHP对象中不允许的属性名。解决这些冲突是不值得的,因为一个贫乏的stdClass对象对一个简单的数组没有任何价值


例2: 分别读取每个命名元素

$reader = new pcrov\JsonReader\JsonReader();
$reader->open("data.json");

while ($reader->read()) {
    $name = $reader->name();
    if ($name !== null) {
        echo "$name: {$reader->value()}\n";
    }
}

$reader->close();
输出:
例3: 读取给定名称的每个属性。好处:从字符串而不是URI中读取数据,再加上从同一对象中具有重复名称的属性中获取数据(这在JSON中是允许的,真有趣。)



如何更好地阅读JSON取决于JSON的结构以及您想用它做什么。这些例子应该给你一个开始的地方。

最近我制作了一个名为JSON机器的库,它可以有效地解析不可预测的大JSON文件。使用方法是通过simple
foreach
。我自己用它来做我的项目

例如:

foreach (JsonMachine::fromFile('employees.json') as $employee) {
    $employee['name']; // etc
}

参见

我知道已经提到了JSON流解析器。但是我最近(ish)添加了一个新的监听器,试图让它更易于开箱即用,我想我应该(改变一下)发布一些关于它的功能的信息

在上有一篇关于基本解析器的非常好的文章,但是我在标准设置中遇到的问题是,您总是需要编写一个侦听器来处理文件。这并不总是一项简单的任务,如果/当JSON发生变化时,还可能需要一定量的维护。所以我写了
RegexListener

基本原则是允许您说出您感兴趣的元素(通过正则表达式),并给它一个回调,告诉它在找到数据时该做什么。在读取JSON时,它会跟踪每个组件的路径——类似于目录结构。因此
/name/forename
或数组
/items/item/2/partid
——这就是正则表达式所匹配的内容

一个例子是(来自

只是一些解释

'/1/name' => function ($data)
因此
/1
是数组中的第二个元素(基于0),因此允许访问元素的特定实例
/name
name
元素。然后将该值作为
$data

"(/\d*)" => function ($data, $path )
这将选择阵列的每个元素,并一次传递一个,因为它使用捕获组,此信息将作为
$path
传递。这意味着当文件中存在一组记录时,您可以一次处理一个项目。也知道哪些元素不需要跟踪

最后一个

'(/.*/nested array)' => function ($data, $path):
有效地扫描任何名为
嵌套数组
的元素,并传递每个元素及其在文档中的位置

我发现的另一个有用的特性是,如果在一个大的JSON文件中,您只需要顶部的摘要细节,您可以获取这些位,然后停止

$filename = __DIR__.'/../tests/data/ratherBig.json';
$listener = new RegexListener();
$parser = new Parser(fopen($filename, 'rb'), $listener);
$listener->setMatch(["/total_rows" => function ($data ) use ($parser) {
    echo "/total_rows=".$data.PHP_EOL;
    $parser->stop();
}]);
当您对其余内容不感兴趣时,这可以节省时间

需要注意的一点是,它们会对内容做出反应,因此,当找到匹配内容的结尾时,会触发每个内容,并且可能会以不同的顺序触发。而且解析器只跟踪您感兴趣的内容,并丢弃任何其他内容

如果您发现任何有趣的功能(有时被称为bug),请告诉我或在github页面上报告问题。

最新提交命令
$filename = __DIR__.'/../tests/data/example.json';
$listener = new RegexListener([
    '/1/name' => function ($data): void {
        echo PHP_EOL."Extract the second 'name' element...".PHP_EOL;
        echo '/1/name='.print_r($data, true).PHP_EOL;
    },
    '(/\d*)' => function ($data, $path): void {
        echo PHP_EOL."Extract each base element and print 'name'...".PHP_EOL;
        echo $path.'='.$data['name'].PHP_EOL;
    },
    '(/.*/nested array)' => function ($data, $path): void {
        echo PHP_EOL."Extract 'nested array' element...".PHP_EOL;
        echo $path.'='.print_r($data, true).PHP_EOL;
    },
]);
$parser = new Parser(fopen($filename, 'r'), $listener);
$parser->parse();
'/1/name' => function ($data)
"(/\d*)" => function ($data, $path )
'(/.*/nested array)' => function ($data, $path):
$filename = __DIR__.'/../tests/data/ratherBig.json';
$listener = new RegexListener();
$parser = new Parser(fopen($filename, 'rb'), $listener);
$listener->setMatch(["/total_rows" => function ($data ) use ($parser) {
    echo "/total_rows=".$data.PHP_EOL;
    $parser->stop();
}]);