【问题标题】:Problem reading files greater than 1GB with XMLReader使用 XMLReader 读取大于 1GB 的文件时出现问题
【发布时间】:2010-08-06 14:41:38
【问题描述】:

XMLReader 可以处理的最大文件大小是否存在?

我正在尝试处理大约 3GB 大的 XML 提要。当然没有 PHP 错误,因为脚本运行良好并在运行后成功加载到数据库。

该脚本在较小的测试源(1GB 及以下)也能正常运行。但是,当处理较大的提要时,脚本会在大约 1GB 后停止读取 XML 文件并继续运行脚本的其余部分。

有没有人遇到过类似的问题?如果是这样,您是如何解决的?

提前致谢。

【问题讨论】:

  • 您是否确定没有生成 PHP 错误?工作与不工作之间的决定因素究竟是什么(据你所知)? “脚本”是什么样的,除了遍历 XML 之外,它还做了什么?
  • 在伪代码中,脚本看起来像这样 $this->downloadFeed();尝试{ $this->writeXMLFeedToCSV(); }catch(e){ //处理异常 } $this->uploadCSVToDatabaseTable();如果脚本由于 PHP 错误而失败,它不会上传到数据库。目前确实如此。 xml 也正确形成,当脚本被分解时,正如 ircmaxell 建议的那样,它工作正常。然而这个过程很乏味,希望能找到解决办法。抱歉,由于信息的性质,我无权分享脚本。
  • 您使用哪个 a) 操作系统 b) 文件系统 c) php 版本 d) 构建的 php 用于测试?

标签: php file size max xmlreader


【解决方案1】:

我最近遇到了同样的问题,想分享一下我的经验。

似乎问题在于 PHP 的编译方式,无论是编译时支持 64 位文件大小/偏移量还是仅支持 32 位。

使用 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。

【讨论】:

    【解决方案2】:

    拆分文件肯定会有所帮助。其他尝试...

    1. 调整 php.ini 中的 memory_limit 变量。 http://php.net/manual/en/ini.core.php
    2. 使用 SAX 重写您的解析器 -- http://php.net/manual/en/book.xml.php。这是一个面向流的解析器,不需要解析整个树。内存效率更高,但更难编程。

    根据您的操作系统,您可以分配的 RAM 块也可能有 2gb 的限制。如果您在 32 位操作系统上运行,则很有可能。

    【讨论】:

    • XMLReader 接口应该像 SAX 解析器一样按顺序处理大型文档,即它不会(必然)将整个文档加载到内存中。
    • 谢谢。已经调整了内存。 VolkerK 也是正确的。 XMLReader 以与 SAX 解析器类似的方式读取。如果一切都失败了,我会用 SAX 尝试它,但不想重写脚本。
    【解决方案3】:

    需要注意的是,PHP 通常有一个最大文件大小。 PHP 不允许使用无符号整数或长整数,这意味着整数的上限为 2^31(或 64 位系统为 2^63)。这很重要,因为 PHP 使用整数作为文件指针(您在通读时在文件中的位置),这意味着它无法处理大小超过 2^31 字节的文件。

    但是,这应该超过 1 GB。我遇到了 2 GB 的问题(正如预期的那样,因为 2^31 大约是 20 亿)。

    【讨论】:

      【解决方案4】:

      我在解析大型文档时遇到了类似的问题。我最终要做的是使用文件系统函数将提要分成更小的块,然后解析这些更小的块......所以如果你有一堆要解析的<record> 标签,用字符串函数将它们解析为流,并且当您在缓冲区中获得完整记录时,使用 xml 函数对其进行解析......这很糟糕,但它工作得很好(并且非常节省内存,因为您在任何时候最多只有 1 条记录在内存中) ...

      【讨论】:

      • 谢谢,是的,我最终也是这样做的。但是正如您所提到的,它很烂:o) 您是否知道 xml 阅读器可以读取的最大文件大小是事实吗?
      • 再次感谢您的建议,我发现了错误的根源和迄今为止一直为我工作的解决方案,并认为您可能能够实施它。事实证明,提要中有一个垂直制表符(^K 或 char 11),它不是无效字符,但对于我使用的文档类型无效。在处理提要之前,我通过 sed 查找和替换运行提要,并且能够解析大于 2gb 的字段。感谢其他人的建议。
      【解决方案5】:

      你有任何错误

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

      什么时候解析器过早停止?

      【讨论】:

      • 不,没有。我正在整理脚本的独立副本,可能会更清楚地说明问题,但我很确定这不是 XML 或 PHP 脚本本身的问题。只要文件小于 1GB,它就可以正常运行。即使更大,它也运行良好,只是不会读取所有 xml。不过感谢您的建议。
      • "但我很确定这不是 XML 或 PHP 脚本本身的问题。" - 只是为了确保:libxml_get_errors() 并不是暗示脚本或 xml 文档有问题。我认为 libxml 可能会抱怨文件查找失败或文本节点大于允许的最大值(默认为 10MB)或类似的东西。如果你在 libxml_get_errors() 没有返回错误的情况下遇到问题,这个想法就死了:(
      • :o) 我知道这就是你的暗示。我不敏感——我不是在防守。对不起,如果我遇到这种情况。
      【解决方案6】:

      使用 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 分钟 ;-))

      这也适用于您的系统吗?


      附带说明:相应的 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;
          }
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-04
        • 2023-01-18
        • 2019-07-14
        • 1970-01-01
        • 2012-01-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多