检查大型 XML 文件(100k+ 条目)每天更新 PHP 中更改的最快、最有效的方法

Fastest, most efficient way to check for changes in a large XML file (100k+ entries) updated daily with PHP

我正在处理 xml 个包含 50-150k+ 个条目的文件,大小约为 50-100MB+,每天都在变化。所有条目都是唯一的,每个条目有 10-15 个元素(id、title 等)。我目前正在将 xml 文件拉成一个字符串,使用简单的 xml 来解析它,然后遍历每个条目以检查更改。

这是基本代码...

$data = file_get_contents("test.xml");
$data = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $data);
    

$xml = simplexml_load_string($data);

$item_count = count($xml->entry);   

foreach (range(0, $item_count - 1, 1) as $num) {
    
    $id = (string)$xml->entry[$num]->id;
    $title = (string)$xml->entry[$num]->title;

    // ... etc. ...
    

我在 VPS 服务器上(1CPU,4GB RAM,40GB)。没有任何其他代码,仅迭代具有 70k 个条目的 xml 文件,大约 80MB 需要 25-30 分钟。性能随着时间的推移而下降,CPU 几分钟后达到 98%(RAM 很好)。前 30K+ 条目需要 10 分钟,而第二个 40K 大约需要 20 分钟。

是否有比简单xml更快、更有效的方法来执行此操作?...或者检查每天更改的大型 XML 文件的更好方法? (即 MySQL import/queries 等)

我看过使用 SAX 解析器的建议,但我确实喜欢 simplexml 提供的对元素的轻松访问。如果我可以流式传输 xml 并节省 RAM,那也是更可取的。

最后一点,如果有帮助的话,我当前检查更改的逻辑如下:

  1. 创建一个以前导入的条目数组,以条目 ID 作为键,以要检查的条目值字符串作为值
  2. 遍历 xml 文件并检查当前条目 id/value 对是否存在...
// Check if current entry is in array 
if ($previously_imported_entries[$id] === $value1 . " ---- " . $value2 . " --- " . $value3) {

   // Item is in array, and values are the same
   // No changes
   // etc...   
} else {
   
   // Item isn't in the array or values have changed
   // Import entry either way  

}

The XMLReader class 可能会解决问题

“如果您必须处理大量 XML 文档,请使用 XMLReader 来处理它们。不要尝试将整个 XML 文档收集到 PHP 数据结构使用 XMLReader 和 PHP xml2assoc() 函数,您正在重新发明 SimpleXML 轮子。 使用 XMLReader 解析大量 XML 文档时,收集执行操作所需的数据,然后在跳到下一个节点之前执行它。"

You can read more here

XML读者 + 简单XML.

通过 XMLReader 流式传输 xml,然后将每个条目加载到 SimpleXML 中以解析并轻松访问元素。

不可思议!!相同的概念,相同的文件,新的结果时间:6-10 秒。

图片来源:Bartosz Pachołek, Linkedin Post "Parsing huge XML files with PHP"

*也感谢 Whosebug 用户 Maharramoff 将我指向 XMLReader

这是 Bartosz 的示例代码:

<?php
//include "memcheck.php";
$start = time();

$xml = XMLReader::open('random5.xml');
//go to the first 'object' element
while ($xml->name !== 'object') { 
    $xml->read(); 
}

do {
    $object = simplexml_load_string($xml->readOuterXml());
    $id = (string) $object->id;
    $name = (string) $object->name;
    $features = [];
    foreach($object->features->feature as $feature) {
        $features[(string)$feature->id] = (string) $feature->name;
    }

    $services = [];
    foreach($object->services->service as $service) {
        $services[(string)$service->id] = (string) $service->service;
    }

    //here again we have all data of an object
} while ($xml->next('object'));

//var_dump("Mem in MiB: " . round((processPeakMemUsage() / 1024)));
var_dump("Time in seconds:  " . (time() - $start));

这对我来说太棒了。

只是将第 7 行和第 4 行中的 'object' 从底部替换为 'entry' 以循环遍历我的 xml 文件中的所有条目标签,并替换了特定元素 names/variables 来匹配我的 xml 文件(即 $title = (string) $object->title 而不是 'name',等等)。

*我注释掉了 memcheck.php 和倒数第二行,因为我没有使用它。

注意:对于我使用的 xml 文件,我首先必须删除所有无效字符以避免 PCDATA 无效字符错误。如果对任何人有帮助,这是我使用的脚本:

$data = file_get_contents('test.xml');
        
$data = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $data);
        
file_put_contents('new_file.xml', $data);

Bartosz 的文章对 4GB 和其他 XML 文件进行了多项测试,比较了 SimpleXML、DOM、SAX Expat Parser、XMLReader、等非常有帮助。如果您对他的其他发现感兴趣,请查看。

对于我需要的速度和效率,加上我喜欢 SimpleXML 的简单性,这个解决方案是最好的。

测试看看它是否适合你。