【问题标题】:Reading very large files in PHP在 PHP 中读取非常大的文件
【发布时间】:2008-10-02 13:11:45
【问题描述】:

当我尝试在PHP 中读取一个大小适中的文件时,fopen 失败。 A 6 meg file 让它窒息,尽管100k 周围的小文件就可以了。我读到有时需要用-D_FILE_OFFSET_BITS=64 标志重新编译PHP 以读取超过20 gig 的文件或一些荒谬的东西,但我不应该对6 meg 文件没有问题吗?最终我们会想要读取大约 100 兆的文件,如果能够打开它们然后使用 fgets 逐行读取它们会很好,就像我可以处理较小的文件一样。

对于PHP 中的超大文件进行读取和操作有哪些技巧/解决方案?

更新:这是一个在我的 6 meg 文件上失败的简单代码块的示例 - PHP 似乎没有抛出错误,它只是返回 false。也许我在做一些非常愚蠢的事情?

$rawfile = "mediumfile.csv";

if($file = fopen($rawfile, "r")){  
  fclose($file);
} else {
  echo "fail!";
}

另一个更新:感谢大家的帮助,事实证明这确实是一件非常愚蠢的事情 - 权限问题。当较大的文件没有时,我的小文件莫名其妙地具有读取权限。呵呵!

【问题讨论】:

  • 您只是想通过文件吗? IE。下载?或者您实际上是否出于某种目的解析文件中的数据?谢谢。
  • 它不应该在不产生警告/错误的情况下失败。请使用 error_reporting(E_ALL) 打开所有错误,并确保将 display_errors 设置为打开以在您的浏览器中显示,或检查您的网络服务器错误日志。

标签: php file large-files


【解决方案1】:

您确定是 fopen 失败而不是脚本的超时设置吗?默认值通常约为 30 秒左右,如果您的文件读取时间超过该时间,则可能会导致问题。

要考虑的另一件事可能是脚本的内存限制 - 将文件读入数组可能会超出此限制,因此请检查错误日志以获取内存警告。

如果以上都不是您的问题,您可能会考虑使用fgets 逐行读取文件,然后进行处理。

$handle = fopen("/tmp/uploadfile.txt", "r") or die("Couldn't get handle");
if ($handle) {
    while (!feof($handle)) {
        $buffer = fgets($handle, 4096);
        // Process buffer here..
    }
    fclose($handle);
}

编辑

PHP 似乎没有抛出错误,它只是返回 false。

$rawfile 的路径相对于脚本运行的位置是否正确?也许尝试在这里为文件名设置一个绝对路径。

【讨论】:

  • 如何打开真正的大文件是唯一可能的解决方案。我正在通过此解决方案处理 1.5GB 文件,没有任何问题。所有其他解决方案,如 file_get_contents of file 都会将整个文件读入内存。这种方法是逐行处理的。
  • 为什么4096表示一行?
  • @Phoenix 4096 表示,如果没有遇到换行符,最多读取 4096 - 1 个字节。查看手册。
  • 对我来说,stream_get_linefgets 更快,看看这个比较 gist.github.com/joseluisq/6ee3876dc64561ffa14b
【解决方案2】:

使用 1.3GB 文件和 9.5GB 文件进行了 2 次测试。

1.3 GB

使用fopen()

此过程使用 15555 毫秒进行计算。

它在系统调用中花费了 169 毫秒。

使用file()

此过程使用 6983 毫秒进行计算。

它在系统调用中花费了 4469 毫秒。

9.5 GB

使用fopen()

此过程使用 113559 毫秒进行计算。

它在系统调用中花费了 2532 毫秒。

使用file()

此过程使用 8221 毫秒进行计算。

系统调用花费了 7998 毫秒。

似乎file() 更快。

【讨论】:

    【解决方案3】:

    fgets() 函数在文本文件超过 20 MBytes 之前可以正常工作,并且解析速度会大大降低。

    file_ get_contents() 函数在 40 MBytes 之前给出了良好的结果,在 100 MBytes 之前给出了可接受的结果,但是 file_get_contents() 将整个文件加载到内存中,因此它不可扩展。

    file() 函数对于大文本文件是灾难性的,因为该函数创建一个包含每一行文本的数组,因此该数组存储在内存中,并且使用的内存更大。
    实际上,我只能设法解析一个 200 MB 的文件,并将 memory_limit 设置为 2 GB,这不适合我打算解析的 1+ GB 文件。

    当你要解析大于 1 GB 的文件,并且解析时间超过 15 秒,并且你想避免将整个文件加载到内存中时,你必须另辟蹊径。

    我的解决方案是以任意小块解析数据。代码是:

    $filesize = get_file_size($file);
    $fp = @fopen($file, "r");
    $chunk_size = (1<<24); // 16MB arbitrary
    $position = 0;
    
    // if handle $fp to file was created, go ahead
    if ($fp) {
       while(!feof($fp)){
          // move pointer to $position in file
          fseek($fp, $position);
    
          // take a slice of $chunk_size bytes
          $chunk = fread($fp,$chunk_size);
    
          // searching the end of last full text line (or get remaining chunk)
          if ( !($last_lf_pos = strrpos($chunk, "\n")) ) $last_lf_pos = mb_strlen($chunk);
    
          // $buffer will contain full lines of text
          // starting from $position to $last_lf_pos
          $buffer = mb_substr($chunk,0,$last_lf_pos);
          
          ////////////////////////////////////////////////////
          //// ... DO SOMETHING WITH THIS BUFFER HERE ... ////
          ////////////////////////////////////////////////////
    
          // Move $position
          $position += $last_lf_pos;
    
          // if remaining is less than $chunk_size, make $chunk_size equal remaining
          if(($position+$chunk_size) > $filesize) $chunk_size = $filesize-$position;
          $buffer = NULL;
       }
       fclose($fp);
    }
    

    使用的内存只有$chunk_size,速度略低于file_ get_contents()。我认为 PHP Group 应该使用我的方法来优化它的解析功能。

    *) 找到get_file_size()函数here

    【讨论】:

    • 这是不完整的,fread 移动文件指针。通过不重置位置,您丢失了第一个块,也很大.. 16mb。先测试
    • 感谢 Ionut,您的有用观察。代码已更新。
    • 我用一个大文件(ca 256MB)尝试了这个,但循环似乎卡在缓冲区的最后一部分。缓冲区在最后一个
    【解决方案4】:

    如果你只是想输出文件,你可以尝试使用 readfile 函数。

    如果不是这样——也许你应该考虑一下应用程序的设计,你为什么要在网络请求上打开这么大的文件?

    【讨论】:

    • 我们必须自动添加大型数据集,因此用户可以上传大型 CSV 文件,并由应用程序解析并集成到数据库中。如果您认为使用 PHP 读取和解析上传的文件不是最好的方法,我会喜欢其他方法建议。
    • 我认为 PHP 不会对 6MB 的 csv 文件有问题吗?似乎是一个足够小的文件来处理。根据上面的 cmets,请发布确切的错误/和或代码。你的打击可能是记忆错误吗?还是 max_execution_time?我们需要更多信息来提供帮助。
    【解决方案5】:

    我使用 fopen 打开视频文件进行流式传输,使用 php 脚本作为视频流式服务器,我对大小超过 50/60 MB 的文件没有任何问题。

    【讨论】:

      【解决方案6】:

      如果问题是由于达到内存限制引起的,您可以尝试将其设置为更高的值(这取决于 php 的配置是否有效)。

      这会将内存限制设置为 12 Mb

      ini\_set("memory_limit","12M");
      

      【讨论】:

      • 注意:虽然这可能会有所帮助,但它只会推迟问题:一旦 15 MB 文件进入,问题就会再次出现。 (如果您的文件永远不会超过某个限制,这可能会使问题消失。)
      【解决方案7】:

      对我来说,fopen() 对于超过 1mb 的文件非常慢,file() 要快得多。

      尝试一次读取第 100 行并创建批量插入,fopen() 需要 37 秒,而file() 需要 4 秒。一定是string-&gt;array内置在file()中的那个步骤

      我会尝试所有文件处理选项,看看哪个最适合您的应用程序。

      【讨论】:

        【解决方案8】:

        【讨论】:

        • 小心使用 file_get_contents() 处理大文件。虽然 6 兆应该没问题,但流式传输要好得多,因为它不会先将整个文件读入内存。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-25
        • 2015-09-17
        • 1970-01-01
        • 2019-04-30
        • 2016-05-27
        • 2017-12-21
        • 1970-01-01
        相关资源
        最近更新 更多