有效地读取大文本文件

时间:2015-03-26 20:28:34

标签: php performance file

我有几个巨大的(11mb54mb)文件,我需要阅读这些文件来处理脚本的其余部分。目前我正在读取文件并将它们存储在如下数组中:

$pricelist = array();
$fp = fopen($DIR.'datafeeds/pricelist.csv','r');
while (($line = fgetcsv($fp, 0, ",")) !== FALSE) { 
    if ($line) { 
        $pricelist[$line[2]] = $line;
    }
}
fclose($fp);

..但我经常从我的虚拟主机获取内存过载消息。我如何更有效地阅读它?

我不需要存储所有内容,我已经拥有与数组键$line[2]完全匹配的关键字,我只需要读取一个数组/行。

5 个答案:

答案 0 :(得分:5)

如果您知道密钥,为什么不通过密钥过滤掉?您可以使用memory_get_usage()函数检查内存使用情况,以查看填充$ pricelist数组后分配的内存量。

echo memory_get_usage() . "\n";
$yourKey = 'some_key';
$pricelist = array();
$fp = fopen($DIR.'datafeeds/pricelist.csv','r');
while (($line = fgetcsv($fp, 0, ",")) !== FALSE) { 
    if (isset($line[2]) && $line[2] == $yourKey) { 
        $pricelist[$line[2]] = $line;
        break;
        /* If there is a possiblity to have multiple lines
        we can store each line in a separate array element
        $pricelist[$line[2]][] = $line;
        */
    }
}
fclose($fp);
echo memory_get_usage() . "\n";

答案 1 :(得分:2)

你可以尝试这个(我还没检查它是否正常工作)

$data = explode("\n", shell_exec('cat filename.csv | grep KEYWORD'));

您将获得包含关键字的所有行,每行作为数组的元素。

如果有帮助,请告诉我。

答案 2 :(得分:2)

我加入了什么 user2864740 说:“问题是数组本身导致的内存使用情况,而不是”读取“文件

我的解决方案是:

  • 拆分你的`$ priceList`数组
  • 在内存中分割数组时仅加载1
  • 将其他拆分的数组保留在中间文件中

N.B:我没有测试我写的是什么

<?php
    define ("MAX_LINE",         10000)  ;
    define ("CSV_SEPERATOR",    ',')    ;

    function intermediateBuilder ($csvFile, $intermediateCsvFile) {
        $pricelist              = array ();
        $currentLine            = 0;
        $totalSerializedArray   = 0;

        if (!is_file()) {
            throw new Exception ("this is not a regular file: " . $csv);
        }
        $fp = fopen ($csvFile, 'r');
        if (!$fp) {
            throw new Exception ("can not read this file: " . $csv);
        }

        while (($line = fgetcsv($fp, 0, CSV_SEPERATOR)) !== FALSE) { 
            if ($line) { 
                $pricelist[$line[2]] = $line;
            }
            if (++$currentLine == MAX_LINE) {
                $fp2    = fopen ($intermediateCsvFile, 'a');
                if (!$fp) throw new Exception ("can not write in this intermediate csv file: " . $intermediateCsvFile);

                fputs  ($fp2, serialize ($pricelist) . "\n");
                fclose ($fp2);

                unset ($pricelist);
                $pricelist      = array ();
                $currentLine    = 0;
                $totalSerializedArray++;
            }
        }
        fclose($fp);
        return $totalSerializedArray;
    }

    /**
      * @param array   : by reference unserialized array 
      * @param integer : the array number to read from the intermediate csv file; start from index 1
      * @param string  : the (relative|absolute) path/name of the intermediate csv file
      * @throw Exception
      */
    function loadArray (&$array, $arrayNumber, $intermediateCsvFile) {
        $currentLine    = 0;
        $fp             = fopen ($intermediateCsvFile, 'r');
        if (!$fp) {
            throw new Exception ("can not read this intermediate csv file: " . $intermediateCsvFile);
        }
        while (($line = fgetcsv($fp, 0, CSV_SEPERATOR)) !== FALSE) { 
            if (++$currentLine == $arrayNumber) {
                fclose ($fp);
                $array  = unserialize ($line);
                return;
            }
        }
        throw new Exception ("the array number argument [" . $arrayNumber . "] is invalid (out of bounds)");
    }

使用示例

try {
    $totalSerializedArray = intermediateBuilder ($DIR . 'datafeeds/pricelist.csv', 
                                                 $DIR . 'datafeeds/intermediatePricelist.csv');

    $priceList   = array () ;
    $arrayNumber = 1;

    loadArray ($priceList, 
               $arrayNumber, 
               $DIR . 'datafeeds/intermediatePricelist.csv');
    if (!array_key_exists ($key, $priceList)) {
        if (++$arrayNumber > $totalSerializedArray) $arrayNumber = 1;
        loadArray ($priceList, 
                   $arrayNumber, 
                   $DIR . 'datafeeds/intermediatePricelist.csv');
    }
    catch (Exception $e) {
        // TODO : log the error ...
    }

答案 3 :(得分:0)

你可以放弃

if ($line) {

只重复循环条件的检查。如果您的文件是54MB,并且您要保留文件中的每一行,作为一个数组,加上第3列的密钥(用于查找的哈希值)...我可以看到需要75-85MB才能存储它在记忆中。这不是很多。大多数使用小部件的wordpress或magento页面运行150-200MB。但如果您的主机设置为低,则可能是一个问题。

您可以尝试通过将if($ line)更改为if($ line [1] ==&#39; book&#39;)来过滤掉一些行,以减少存储的数量。但处理在内存中存储大量内容的唯一可靠方法是为脚本提供大量内存。

答案 4 :(得分:0)

您可以尝试使用此设置更大的内存。您可以根据需要更改限制。

ini_set('memory_limit', '2048M');

但也取决于你希望该脚本如何使用。