在處理大型XML 文件時,單線程解析可能導致內存使用過高或執行時間過長。 PHP 本身並不原生支持“真正的”多線程(除非使用擴展如pthreads或Swoole),但我們可以通過(例如使用proc_open創建多個子進程)來並行處理大型XML 文件,以提升解析效率。
本文將演示如何結合xml_parse函數和proc_open實現偽多線程解析大型XML 文件。
xml_parse是PHP 的底層解析函數之一,屬於Expat 解析器的一部分。它支持基於事件的解析方式,非常適合流式處理大型XML 數據流。相比DOM 加載整個文檔到內存中, xml_parse更節省資源。
我們不能直接讓多個線程共享xml_parser對象,但可以:
將大型XML 文件分塊(按節點劃分) ;
使用proc_open()或shell_exec()啟動多個PHP 子進程;
每個子進程解析自己的XML 塊;
主進程收集結果並合併。
假設我們有一個大型的XML 文件/data/huge.xml ,結構如下:
<items>
<item><id>1</id><name>Item 1</name></item>
<item><id>2</id><name>Item 2</name></item>
...
</items>
<?php
$sourceFile = '/data/huge.xml';
$tempDir = '/tmp/xml_chunks/';
$chunkSize = 1000; // 每個子進程解析 1000 個 <item>
$urls = [];
// 確保臨時目錄存在
if (!is_dir($tempDir)) {
mkdir($tempDir, 0777, true);
}
// 分裂 XML 文件
$handle = fopen($sourceFile, 'r');
$chunkIndex = 0;
$buffer = '';
$itemCount = 0;
while (($line = fgets($handle)) !== false) {
if (strpos($line, '<item>') !== false) {
$itemCount++;
}
$buffer .= $line;
if ($itemCount >= $chunkSize || feof($handle)) {
$chunkFile = $tempDir . "chunk_{$chunkIndex}.xml";
file_put_contents($chunkFile, "<items>\n" . $buffer . "\n</items>");
$urls[] = "http://m66.net/worker.php?file=" . urlencode($chunkFile);
$chunkIndex++;
$buffer = '';
$itemCount = 0;
}
}
fclose($handle);
// 並行調用 worker 解析器(可以改為 curl_multi_exec 提高效率)
foreach ($urls as $url) {
shell_exec("php worker.php '{$url}' > /dev/null &");
}
echo "啟動了 " . count($urls) . " 個解析任务。\n";
<?php
if ($argc < 2) {
exit("請傳入 XML 文件路徑參數\n");
}
$xmlFile = urldecode($argv[1]);
if (!file_exists($xmlFile)) {
exit("文件不存在: $xmlFile\n");
}
$parser = xml_parser_create();
xml_set_element_handler($parser, "startElement", "endElement");
xml_set_character_data_handler($parser, "characterData");
$currentTag = '';
$currentItem = [];
function startElement($parser, $name, $attrs) {
global $currentTag;
$currentTag = strtolower($name);
}
function endElement($parser, $name) {
global $currentTag, $currentItem;
if (strtolower($name) == 'item') {
// 示例:將解析結果保存到文件或數據庫
file_put_contents('/tmp/parsed_result.txt', json_encode($currentItem) . PHP_EOL, FILE_APPEND);
$currentItem = [];
}
$currentTag = '';
}
function characterData($parser, $data) {
global $currentTag, $currentItem;
if (trim($data)) {
$currentItem[$currentTag] = trim($data);
}
}
$fp = fopen($xmlFile, 'r');
while ($data = fread($fp, 4096)) {
xml_parse($parser, $data, feof($fp)) or
die(sprintf("XML 錯誤: %s", xml_error_string(xml_get_error_code($parser))));
}
fclose($fp);
xml_parser_free($parser);
echo "解析完成: $xmlFile\n";
性能提升:在多核CPU 上,每個子進程獨立運行,可並行加快整體解析速度。
內存控制:每個子進程處理的數據量可控,避免爆內存。
安全性:確保不在生產環境中通過URL 參數直接傳入文件路徑,應加白名單校驗。
進程管理:可使用pcntl_fork或Swoole替代shell_exec實現更穩定的子進程管理。
雖然PHP 本身不是並發處理的理想語言,但通過xml_parse與進程控制技巧,我們依然能高效地解析大型XML 文件。這種方式特別適合日誌處理、導入數據等對效率有要求的任務場景。
若需要進一步提升效率,推薦結合隊列系統(如RabbitMQ)或使用Go/Python 這類對並發友好的語言重寫解析模塊,然後通過PHP 調度。
相關標籤:
xml_parse