PHPExcel用完了256,512和1024MB的RAM

时间:2011-01-27 14:35:02

标签: php phpexcel

我不明白。 XSLX表大约3MB,甚至1024MB的RAM还不足以让PHPExcel将其加载到内存中?

我可能在这里做了一些可怕的错误:

function ReadXlsxTableIntoArray($theFilePath)
{
    require_once('PHPExcel/Classes/PHPExcel.php');
    $inputFileType = 'Excel2007';
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objReader->setReadDataOnly(true);
    $objPHPExcel = $objReader->load($theFilePath);
    $rowIterator = $objPHPExcel->getActiveSheet()->getRowIterator();
    $arrayData = $arrayOriginalColumnNames = $arrayColumnNames = array();
    foreach($rowIterator as $row){
        $cellIterator = $row->getCellIterator();
        $cellIterator->setIterateOnlyExistingCells(false); // Loop all cells, even if it is not set
        if(1 == $row->getRowIndex ()) {
            foreach ($cellIterator as $cell) {
                $value = $cell->getCalculatedValue();
                $arrayOriginalColumnNames[] = $value;
                // let's remove the diacritique
                $value = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $value);
                // and white spaces
                $valueExploded = explode(' ', $value);
                $value = '';
                // capitalize the first letter of each word
                foreach ($valueExploded as $word) {
                    $value .= ucfirst($word);
                }
                $arrayColumnNames[] = $value;
            }
            continue;
        } else {
            $rowIndex = $row->getRowIndex();
            reset($arrayColumnNames);
            foreach ($cellIterator as $cell) {
                $arrayData[$rowIndex][current($arrayColumnNames)] = $cell->getCalculatedValue();
                next($arrayColumnNames);
            }
        }
    }
    return array($arrayOriginalColumnNames, $arrayColumnNames, $arrayData);
}

上面的函数将数据从excel表读取到数组。

有什么建议吗?

起初,我允许PHP使用256MB的RAM。这还不够。然后我把数量增加了一倍,然后尝试了1024MB。它仍然因内存不足而出现此错误:

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 50331648 bytes) in D:\data\o\WebLibThirdParty\src\PHPExcel\Classes\PHPExcel\Reader\Excel2007.php on line 688

Fatal error (shutdown): Allowed memory size of 1073741824 bytes exhausted (tried to allocate 50331648 bytes) in D:\data\o\WebLibThirdParty\src\PHPExcel\Classes\PHPExcel\Reader\Excel2007.php on line 688

7 个答案:

答案 0 :(得分:71)

在PHPExcel论坛上有很多关于PHPExcel的内存使用情况的文章;所以阅读之前的一些讨论可能会给你一些想法。 PHPExcel包含电子表格的“内存”表示,并且易受PHP内存限制。

文件的物理大小在很大程度上是无关紧要的...知道它包含多少个单元格(每个工作表上的行*列)更为重要。

我一直使用的“经验法则”平均约为1k / cell,因此5M单元工作簿需要5GB内存。但是,有许多方法可以减少该要求。这些可以组合在一起,具体取决于您需要在工作簿中访问哪些信息,以及您希望如何使用它。

如果您有多个工作表,但不需要加载所有工作表,则可以使用setLoadSheetsOnly()方法限制Reader将加载的工作表。 要加载单个命名工作表:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #2'; 
/**  Create a new Reader of the type defined in $inputFileType  **/
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Advise the Reader of which WorkSheets we want to load  **/ 
$objReader->setLoadSheetsOnly($sheetname); 
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

或者您可以通过传递一组名称来指定几个工作表,其中一次调用setLoadSheetsOnly():

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetnames = array('Data Sheet #1','Data Sheet #3'); 
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/** Advise the Reader of which WorkSheets we want to load **/ 
$objReader->setLoadSheetsOnly($sheetnames); 
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

如果您只需要访问工作表的一部分,那么您可以定义一个读取过滤器来识别您实际想要加载的单元格:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #3'; 

/**  Define a Read Filter class implementing PHPExcel_Reader_IReadFilter  */ 
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
    public function readCell($column, $row, $worksheetName = '') {
        //  Read rows 1 to 7 and columns A to E only 
        if ($row >= 1 && $row <= 7) {
           if (in_array($column,range('A','E'))) { 
              return true;
           }
        } 
        return false;
    }
}

/**  Create an Instance of our Read Filter  **/ 
$filterSubset = new MyReadFilter(); 
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Advise the Reader of which WorkSheets we want to load 
     It's more efficient to limit sheet loading in this manner rather than coding it into a Read Filter  **/ 
$objReader->setLoadSheetsOnly($sheetname); 
echo 'Loading Sheet using filter';
/**  Tell the Reader that we want to use the Read Filter that we've Instantiated  **/ 
$objReader->setReadFilter($filterSubset); 
/**  Load only the rows and columns that match our filter from $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

使用读取过滤器,您还可以在“块”中读取工作簿,以便任何时候只有一个块驻留在内存中:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example2.xls';

/**  Define a Read Filter class implementing PHPExcel_Reader_IReadFilter  */ 
class chunkReadFilter implements PHPExcel_Reader_IReadFilter {
    private $_startRow = 0;
    private $_endRow = 0;

    /**  Set the list of rows that we want to read  */ 
    public function setRows($startRow, $chunkSize) { 
        $this->_startRow    = $startRow; 
        $this->_endRow      = $startRow + $chunkSize;
    } 

    public function readCell($column, $row, $worksheetName = '') {
        //  Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
        if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 
           return true;
        }
        return false;
    } 
}

/**  Create a new Reader of the type defined in $inputFileType  **/
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Define how many rows we want to read for each "chunk"  **/ 
$chunkSize = 20;
/**  Create a new Instance of our Read Filter  **/ 
$chunkFilter = new chunkReadFilter(); 
/**  Tell the Reader that we want to use the Read Filter that we've Instantiated  **/ 
$objReader->setReadFilter($chunkFilter); 

/**  Loop to read our worksheet in "chunk size" blocks  **/ 
/**  $startRow is set to 2 initially because we always read the headings in row #1  **/
for ($startRow = 2; $startRow <= 65536; $startRow += $chunkSize) { 
    /**  Tell the Read Filter, the limits on which rows we want to read this iteration  **/ 
    $chunkFilter->setRows($startRow,$chunkSize); 
    /**  Load only the rows that match our filter from $inputFileName to a PHPExcel Object  **/ 
    $objPHPExcel = $objReader->load($inputFileName); 
    //    Do some processing here 

    //    Free up some of the memory 
    $objPHPExcel->disconnectWorksheets(); 
    unset($objPHPExcel); 
}

如果您不需要加载格式化信息,只需加载工作表数据,那么setReadDataOnly()方法将告诉读者仅加载单元格值,忽略任何单元格格式:

$inputFileType = 'Excel5';
$inputFileName = './sampleData/example1.xls';
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/** Advise the Reader that we only want to load cell data, not formatting **/ 
$objReader->setReadDataOnly(true);
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

使用单元格缓存。这是一种减少每个单元所需的PHP内存的方法,但速度很快。它的工作原理是以压缩格式存储单元格对象,或者存储在PHP内存之外(例如磁盘,APC,内存缓存)......但是保存的内存越多,脚本执行的速度就越慢。但是,您可以将每个单元所需的内存减少到大约300字节,因此假设的5M单元将需要大约1.4GB的PHP内存。

单元缓存在开发人员文档

的4.2.1节中描述

修改

查看代码,您正在使用迭代器,这些迭代器效率不高,并且构建了一组单元数据。您可能希望查看已经内置到PHPExcel中的toArray()方法,并为您执行此操作。另外看一下关于新变种方法rangeToArray()的这个recent discussion来构建一个行数据的关联数组。

答案 1 :(得分:9)

我遇到了与PHPExcel相同的内存问题,实际上是所有其他库。正如Mark Ba​​ker建议的那样可以解决问题(缓存也可以),但事实证明内存问题成了时间问题。读写时间是指数级的,因此对于大型电子表格来说,它并不合适。

PHPExcel和其他人并不打算处理大文件,所以我创建了一个解决这个问题的库。您可以在此处查看:https://github.com/box/spout

希望有所帮助!

答案 2 :(得分:5)

使用PHPExcel时,可以采取许多措施来保留较少的内存。我建议您在修改Apache中的服务器内存限制之前采取以下操作来优化内存使用。

/* Use the setReadDataOnly(true);*/
    $objReader->setReadDataOnly(true);

/*Load only Specific Sheets*/
    $objReader->setLoadSheetsOnly( array("1", "6", "6-1", "6-2", "6-3", "6-4", "6-5", "6-6", "6-7", "6-8") );

/*Free memory when you are done with a file*/
$objPHPExcel->disconnectWorksheets();
   unset($objPHPExcel);

避免使用非常大的Exel文件,请记住文件大小会使进程运行缓慢并崩溃。

避免使用getCalculatedValue();阅读细胞时的功能。

答案 3 :(得分:2)

Ypu可以尝试PHP Excel http://ilia.ws/archives/237-PHP-Excel-Extension-0.9.1.html它是PHP的C扩展,速度非常快。 (也使用比PHP实现更少的内存)

答案 4 :(得分:1)

就我而言,phpexcel总是迭代19999行。无论如何,实际填充了多少行。所以100行数据总是在内存错误中结束。

也许您只需要检查当前行中的单元格是否为空,然后“继续”或者中断循环,迭代行。

答案 5 :(得分:1)

只是从另一个帖子重新发布我的帖子。它描述了服务器端生成或编辑应该考虑的Excel电子表格的不同方法。对于大量数据,我不建议使用像PHPExcel或ApachePOI(用于Java)这样的工具,因为它们有内存要求。将数据注入电子表格还有另一种非常方便(尽管可能有点繁琐)的方法。可以实现Serverside生成或更新Excel电子表格,从而实现简单的XML编辑。您可以将XLSX电子表格放在服务器上,每次从dB收集数据时,都可以使用php解压缩它。然后,您将访问特定的XML文件,这些文件包含需要注入的工作表的内容并手动插入数据。之后,您压缩电子表格文件夹,以便将其作为常规XLSX文件进行分发。整个过程非常快速可靠。显然,与XLSX / Open XML文件的内部组织相关的问题和故障很少(例如,Excel倾向于将所有字符串存储在单独的表中并在工作表文件中使用对该表的引用)。但是当仅注入数字和字符串等数据时,并不难。如果有人有兴趣,我可以提供一些代码。

答案 6 :(得分:1)

我遇到了这个问题,很遗憾没有一个建议的解决方案可以帮助我。我需要PHPExcel提供的功能(公式,条件样式等),因此使用不同的库不是一种选择。

我最终做的是将每个工作表写入单个(临时)文件,然后将这些单独的文件与我编写的一些特殊软件组合在一起。这将我的内存消耗从> 512 Mb减少到远低于100 Mb。如果您遇到同样的问题,请参阅https://github.com/infostreams/excel-merge