高效读取大型文本文件

5

我有两个巨大的文件(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]完全匹配的关键字,并且我只需要读取那个数组/行。


导入到数据库中。 - user557846
这些文件每天都会更新,有时候列也会被移动,所以使用数据库并不理想... 至少现在不是。 - eozzy
2
该文件目前是逐行读取的。问题在于数组本身导致的内存使用,而不是“读取”文件的问题。您需要改变逻辑,使其不需要一次性加载所有数据,获取具有更大内存限制的主机,或者使用二级存储(如DB、外部进程缓存或其他文件)来处理临时数据等。 - user2864740
所以,如果您已经有了想要保存一行的条件,那么您不能只使用它来确定是否将其添加到数组中吗? - scrowler
1
mysql的LOAD DATA INFILE非常高效,仍然可能是一个解决方案。 - user557846
显示剩余3条评论
5个回答

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

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Eborbob
是的,@Eborbob,如果我们确定只有一行代码适用于我们,那么我们肯定可以跳出 while 循环。 - Ugur

2
你可以尝试这个(我没有检查它是否正常工作)
$data = explode("\n", shell_exec('cat filename.csv | grep KEYWORD'));

你将获得包含关键字的所有行,每行作为数组的一个元素。
如果有帮助,请告诉我。

确实是一个非常好的解决方案。如果您将shell_exec与trim一起使用,它会返回一个空格(PHP 5.5.9,Linux Mint),看起来更好。$data = explode("\n", trim(shell_exec('cat filename.csv | grep KEYWORD')));但是也有一个缺点,大多数托管公司不允许在共享环境中运行shell_exec。 - Ugur

2

我加入了用户user2864740的看法:“问题在于数组本身导致的内存使用,而不是“读取”文件的问题”。

我的解决方案是:

  • 拆分您的`$priceList`数组
  • 一次只加载一个被拆分的数组到内存中
  • 将其他拆分的数组保留在一个中间文件中

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

0

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

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

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


0

你可以删除

if ($line) {

这只是从循环条件中重复检查。如果您的文件大小为54MB,并且您要保留文件中的每一行作为数组,以及第3列中的键(用于查找哈希)...我可以看到需要75-85MB将其全部存储在内存中。这不算多。大多数使用小部件的WordPress或Magento页面运行150-200MB。但是,如果您的主机设置较低,则可能会出现问题。

您可以尝试通过将if($line)更改为if($line [1] =='book')来过滤掉一些行,以减少存储量。但是,在内存中处理如此多的内容的唯一可靠方法是使脚本具有足够的内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接