【问题标题】:Least memory intensive way to read a file in PHP在 PHP 中读取文件的最小内存占用方式
【发布时间】:2010-07-03 10:17:56
【问题描述】:

我正在使用 PHP 中的 file() 函数读取一个包含大约 50k 行的文件。但是,由于文件的内容作为数组存储在内存中,因此会出现内存不足错误。有没有其他办法?

此外,存储的行的长度是可变的。

这是代码。该文件也是 700kB 而不是 mB。

private static function readScoreFile($scoreFile)
{
    $file = file($scoreFile);
    $relations = array();

    for($i = 1; $i < count($file); $i++)
    {
        $relation = explode("\t",trim($file[$i]));
        $relation = array(
                        'pwId_1' => $relation[0],
                        'pwId_2' => $relation[1],
                        'score' => $relation[2],
                        );
        if($relation['score'] > 0)
        {
            $relations[] = $relation;
        }
    }

    unset($file);
    return $relations;
}

【问题讨论】:

  • 我知道这个问题很老,但这里有两件事。 1.逐行读取文件。 2. 内存不足错误可能是您将所有内容也存储在数组中,如果没有某种控制和您拥有的内存知识,通常不是一个好主意

标签: php file


【解决方案1】:

使用fopenfreadfclose依次读取文件:

$handle = fopen($filename, 'r');
if ($handle) {
    while (!feof($handle)) {
        echo fread($handle, 8192);
    }
    fclose($handle);
}

【讨论】:

  • 这不起作用,我想逐行阅读。它在每个 fread 上返回多行(我猜是 8192 字节)
  • 用“fgets”替换 fread:fgets — 从文件指针获取行
  • 可以使用中间变量$line来存储每一行​​的字节数,然后回显$line。 fread 可能是流式传输文件的最有效方法之一,因此请阅读 fread(并附加到 $line)的结果,直到找到换行符。然后对该行做任何你想做的事情,然后设置 $line="",然后继续将 fread 的结果附加到 $line。
  • 问题是,行的长度是可变的。所以,在某些地方我得到了一半的线条
  • IMO 由于文件中行的长度不同,因此使用 fread 不是一个好方法
【解决方案2】:

EDIT更新问题后和comments to answer of fabjoa:

如果一个 700kb 的文件使用您提供的代码占用 140MB 的内存,那肯定有问题(尽管您可以在每次迭代结束时 unset$relation)。考虑使用调试器单步执行它以查看会发生什么。您可能还想考虑重写代码以同时使用SplFileObject's CSV functions (or their procedural cousins)

SplFileObject::setCsvControl example

$file = new SplFileObject("data.csv");
$file->setFlags(SplFileObject::READ_CSV);
$file->setCsvControl('|');
foreach ($file as $row) {
    list ($fruit, $quantity) = $row;
    // Do something with values
}

对于迭代文件的 OOP 方法,请尝试 SplFileObject

SplFileObject::fgets example

$file = new SplFileObject("file.txt");
while (!$file->eof()) {
    echo $file->fgets();
}

SplFileObject::next example

// Read through file line by line
$file = new SplFileObject("misc.txt");
while (!$file->eof()) {
    echo $file->current();
    $file->next();
}

甚至

foreach(new SplFileObject("misc.txt") as $line) {
    echo $line;
}

非常相关(如果不重复):

【讨论】:

  • 我认为这仍然可能使用大量内存,因为我认为它会继续读取直到找到行尾。
  • @Artefacto 好吧,如果有问题,您仍然可以使用 SplFileObject::setMaxLineLen
  • @Gordon 对。我发现我对 SplFileObject 的熟悉程度可以提高:p
  • @Gordon 那为什么不 foreach 呢? foreach (new SplFileObject("misc.txt") as $line) { ... }
  • @Artefacto 因为我懒惰地从 PHP 手册中复制示例;)
【解决方案3】:

如果您不知道最大行长度,并且您不习惯使用幻数作为最大行长度,那么您需要对文件进行初始扫描并确定最大行长度。

除此之外,以下代码应该可以帮助您:

    // length is a large number or calculated from an initial file scan
    while (!feof($handle)) {
        $buffer = fgets($handle, $length);
        echo $buffer;
    }

【讨论】:

    【解决方案4】:

    老问题,但由于我没有看到有人提到它,PHP generators 是减少节省内存消耗的好方法。

    例如:

    function read($fileName)
    {
        $fileHandler = fopen($fileName, 'rb');
    
        while(($line = fgets($fileHandler)) !== false) {
            yield rtrim($line, "\r\n");
        }
    
        fclose($fileHandler);
    }
    
    foreach(read(__DIR__ . '/filenameHere') as $line) {
        echo $line;
    }
    

    【讨论】:

      【解决方案5】:

      在操作过程中分配更多内存,可能类似于 ini_set('memory_limit', '16M');。操作完成后不要忘记返回初始内存分配

      【讨论】:

      • 我很确定你在操作后不必重新设置内存限制,它只适用于当前正在运行的脚本。
      • 我已经在使用 140MB 的内存(读取文件有很多事情要做)
      • @Chetan 这对我来说听起来很可疑。 50k 行不是那么多。 King James Bible 有大约 20k 行,纯文本为 1MB,使用 file() 读入时仅占用约 3MB。您的文件的总大小(以字节为单位)是多少?
      • @Gordon 该文件大约为 700 MB,但它是一个 TSV 文件,在读取文件后,我将每一行拆分并将其存储到一个数组中。所以这就像一个 30k X 5 的数组,我猜这就是它占用这么多内存的原因
      • @Chetan 你确定你没有在某处泄漏任何内存吗?尝试取消设置未使用的变量,尤其是在循环时。也许您可以发布一些代码供我们查看。
      猜你喜欢
      • 2014-02-05
      • 1970-01-01
      • 2012-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-17
      • 1970-01-01
      相关资源
      最近更新 更多