Проблема с чтением файлов размером более 1 ГБ с помощью XMLReader

Есть ли максимальный размер файла, который может обрабатывать XMLReader?

Я пытаюсь обработать XML-канал размером около 3 ГБ. Конечно, нет ошибок PHP, поскольку сценарий работает нормально и успешно загружается в базу данных после ее запуска.

Сценарий также отлично работает с меньшими тестовыми каналами – 1 ГБ и ниже. Однако при обработке больших файлов сценарий перестает читать XML-файл примерно через 1 ГБ и продолжает выполнение остальной части скрипта.

Кто-нибудь испытывал подобную проблему? и если да, то как вы обходились вокруг?

Заранее спасибо.

Разделение файла обязательно поможет. Другие вещи, чтобы попробовать …

  1. отрегулируйте переменную memory_limit в php.ini. http://php.net/manual/en/ini.core.php
  2. перепишите свой парсер с помощью SAX – http://php.net/manual/en/book.xml.php . Это ориентированный на поток парсер, который не нуждается в анализе всего дерева. Гораздо больше памяти, но немного сложнее программировать.

В зависимости от вашей ОС также может быть ограничение на 2 гб на блок RAM, который вы можете выделить. Очень возможно, если вы работаете на 32-разрядной ОС.

Следует отметить, что PHP в целом имеет максимальный размер файла. PHP не допускает целых чисел без знака или длинных целых чисел, что означает, что у вас ограничено 2 ^ 31 (или 2 ^ 63 для 64-битных систем) для целых чисел. Это важно, потому что PHP использует целое число для указателя файла (ваша позиция в файле при чтении), то есть он не может обрабатывать файл размером более 2 ^ 31 байта.

Однако это должно быть более 1 гигабайта. Я столкнулся с проблемами с двумя гигабайтами (как и ожидалось, поскольку 2 ^ 31 составляет примерно 2 миллиарда).

Недавно у меня была такая же проблема, и я решил поделиться своим опытом.

Похоже, что проблема заключается в том, как скомпилирован PHP, был ли он скомпилирован с поддержкой 64-битных размеров / смещений файлов или только с 32-разрядным.

С 32 бит вы можете обращаться только к 4 ГБ данных. Вы можете найти немного запутанное, но хорошее объяснение здесь: 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 на все элементы. К сожалению, это не очень быстро.

Мне нужно было сделать это только один раз, и это соответствовало моим потребностям, но я бы не рекомендовал его повторное использование. После разделения я использовал XMLReader для небольших файлов размером около 1GB .

Я столкнулся с подобной проблемой при анализе больших документов. То, что я закончил, разбивает фид на более мелкие куски, используя функции файловой системы, а затем анализирует эти меньшие фрагменты … Итак, если у вас есть куча тэгов <record> которые вы анализируете, проанализируйте их со строковыми функциями как потоком, и когда вы получите полную запись в буфере, проанализируйте, что с помощью функций xml … Это отстой, но он работает достаточно хорошо (и очень эффективен с точки зрения памяти, так как у вас есть только 1 запись в памяти в любой момент) …

Вы получаете какие-либо ошибки с

 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('Ymd H:i:s'), "\n"; $fn(); $end = new DateTime(); echo 'end: ', $start->format('Ymd 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+ минут ;-))

Это также работает на вашей системе?


В качестве примечания: соответствующее тестовое приложение C # заняло всего 41 секунду вместо 7,5 минут. И мой медленный жесткий диск, возможно, был одним из ограничивающих факторов в этом случае.

 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; } } }