我正在尝试处理一个大约3GB的XML源。脚本运行良好,在运行后成功加载到数据库中,没有PHP错误。
对于较小的测试源(1GB及以下),脚本也能完美地运行。然而,在处理较大的源时,脚本在读取了大约1GB的XML文件后停止读取,但其余部分继续运行。
有人遇到过类似的问题吗?如果有,你是如何解决的?
谢谢您的帮助。
我最近也遇到了同样的问题,想分享一下我的经验。
问题似乎出在PHP编译方式上,无论是是否支持64位文件大小/偏移量进行编译。
使用32位,您只能寻址4GB的数据。您可以在这里找到一个有点混乱但好的解释:http://blog.mayflower.de/archives/131-Handling-large-files-without-PHP.html
我必须使用Perl实用程序xml_split
来拆分文件,您可以在此处找到它:http://search.cpan.org/~mirod/XML-Twig/tools/xml_split/xml_split
我用它将巨大的XML文件拆分成可管理的块。该工具的好处是它可以将XML文件分割为整个元素。不幸的是,它的速度不太快。
我只需要做一次就行了,并且它适合我的需求,但我不建议反复使用它。拆分后,我在大小约为1GB
的较小文件上使用了XMLReader。
将文件拆分肯定会有所帮助。其他尝试的方法包括...
取决于您的操作系统,RAM块的分配可能也会有2GB的限制。如果您正在运行32位操作系统,则很可能出现此问题。
需要注意的是,PHP通常有一个最大文件大小限制。PHP不允许使用无符号整数或长整数,这意味着您的整数被限制在2^31(或64位系统的2^63)以内。这很重要,因为PHP使用整数作为文件指针(您在读取文件时的位置),这意味着它无法处理大于2^31字节的文件。
然而,这应该超过1GB。我遇到了两个GB的问题(如预期的那样,因为2^31大约是20亿)。
你有没有遇到任何错误?
libxml_use_internal_errors(true);
libxml_clear_errors();
// your parser stuff here....
$r = new XMLReader(...);
// ....
foreach( libxml_get_errors() as $err ) {
printf(". %d %s\n", $err->code, $err->message);
}
当解析器提前停止时?
使用WindowsXP,NTFS文件系统和php 5.3.2,这个测试脚本没有任何问题。
<?php
define('SOURCEPATH', 'd:/test.xml');
if ( 0 ) {
build();
}
else {
echo 'filesize: ', number_format(filesize(SOURCEPATH)), "\n";
timing('read');
}
function timing($fn) {
$start = new DateTime();
echo 'start: ', $start->format('Y-m-d H:i:s'), "\n";
$fn();
$end = new DateTime();
echo 'end: ', $start->format('Y-m-d H:i:s'), "\n";
echo 'diff: ', $end->diff($start)->format('%I:%S'), "\n";
}
function read() {
$cnt = 0;
$r = new XMLReader;
$r->open(SOURCEPATH);
while( $r->read() ) {
if ( XMLReader::ELEMENT === $r->nodeType ) {
if ( 0===++$cnt%500000 ) {
echo '.';
}
}
}
echo "\n#elements: ", $cnt, "\n";
}
function build() {
$fp = fopen(SOURCEPATH, 'wb');
$s = '<catalogue>';
//for($i = 0; $i < 500000; $i++) {
for($i = 0; $i < 60000000; $i++) {
$s .= sprintf('<item>%010d</item>', $i);
if ( 0===$i%100000 ) {
fwrite($fp, $s);
$s = '';
echo $i/100000, ' ';
}
}
$s .= '</catalogue>';
fwrite($fp, $s);
flush($fp);
fclose($fp);
}
输出:
filesize: 1,380,000,023
start: 2010-08-07 09:43:31
........................................................................................................................
#elements: 60000001
end: 2010-08-07 09:43:31
diff: 07:31
(你可以看到,我搞砸了结束时间的输出,但我不想再运行这个脚本7分钟以上;-)
这在你的系统上也有效吗?
filesize: 1.380.000.023
start: 2010-08-07 09:55:24
........................................................................................................................
#elements: 60000001
end: 2010-08-07 09:56:05
diff: 00:41
以及源代码:
using System;
using System.IO;
using System.Xml;
namespace ConsoleApplication1
{
class SOTest
{
delegate void Foo();
const string sourcepath = @"d:\test.xml";
static void timing(Foo bar)
{
DateTime dtStart = DateTime.Now;
System.Console.WriteLine("start: " + dtStart.ToString("yyyy-MM-dd HH:mm:ss"));
bar();
DateTime dtEnd = DateTime.Now;
System.Console.WriteLine("end: " + dtEnd.ToString("yyyy-MM-dd HH:mm:ss"));
TimeSpan s = dtEnd.Subtract(dtStart);
System.Console.WriteLine("diff: {0:00}:{1:00}", s.Minutes, s.Seconds);
}
static void readTest()
{
XmlTextReader reader = new XmlTextReader(sourcepath);
int cnt = 0;
while (reader.Read())
{
if (XmlNodeType.Element == reader.NodeType)
{
if (0 == ++cnt % 500000)
{
System.Console.Write('.');
}
}
}
System.Console.WriteLine("\n#elements: " + cnt + "\n");
}
static void Main()
{
FileInfo f = new FileInfo(sourcepath);
System.Console.WriteLine("filesize: {0:N0}", f.Length);
timing(readTest);
return;
}
}
}
我在解析大型文档时遇到了类似的问题。最后我使用文件系统函数将源数据分成较小的块,然后对这些较小的块进行解析...因此,如果您正在解析一堆<record>
标记,请将它们作为流使用字符串函数进行解析,并且当您在缓冲区中获得完整记录时,请使用xml函数进行解析... 这很糟糕,但它非常有效(并且非常节省内存,因为您在任何时候最多只有1条记录在内存中)...