与许多其他文件一样,读取文件(将其转换为MySQL)时,我一直在努力使用PHPExcel内存.
当然,我已经尝试了在许多地方提到的通常的事情,并且能够将内存效率提高至少40%.这包括使用自定义的分块阅读器类,将分块阅读器实例移动到读取循环之外等.
我的测试服务器上有16G的RAM,并在PHP中分配了2G的最大内存使用量.对于大约200K行以下的文件,PHPExcel将起作用(缓慢但肯定地).一旦超过一定大小,脚本将仅向外壳输出“杀死”,而失败.日志显示内核杀死了PHP,因为它使用了过多的内存.在使用top命令查看CPU和内存使用情况时,我可以看到内存使用和交换使用猛增,而内存使用和交换使用则直线下降.
阅读了大量有关PHPExcel的文档并查看了一些源文件后,我得出的结论是,仅处理文本时,每个单元格都存储了很多数据.使用方法:
$objReader->setReadDataOnly(true);
有点帮助,但实际上并没有做太多…但是,使用分块读取器并将块大小设置为较小值,然后使用unset()清理大变量在理论上应该可行.我知道PHPExcel每次都必须读取整个文件,但是它不应该将其存储在内存中吗?
这是我当前正在使用的代码:
<?php
date_default_timezone_set("America/New_York");
set_time_limit(7200);
ini_set('memory_limit', '2048M');
include_once("classes/PHPExcel/PHPExcel/IOFactory.php");
$inputFileName = "/PATH/TO/FILE.xlsx";
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$worksheetName = "Sheet1";
class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0;
private $_endRow = 0;
public function __construct($startRow, $chunkSize)
{
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function setRows($startRow, $chunkSize)
{
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = '')
{
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow))
{
return true;
}
return false;
}
}
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(true);
$chunkSize = 1000;
echo "Got here 1\n";
$chunkFilter = new chunkReadFilter(2,$chunkSize);
for ($startRow = 2; $startRow <= 378767; $startRow += $chunkSize)
{
$chunkFilter->setRows($startRow, $chunkSize);
$objReader->setReadFilter($chunkFilter);
echo "Got here 2\n";
$objPHPExcel = $objReader->load($inputFileName);
echo "Got here 3\n";
$sheet = $objPHPExcel->getSheetByName($worksheetName);
echo "Got here 4\n";
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
echo "Got here 5\n";
$sheetData = $sheet->rangeToArray("A".$startRow.":".$highestColumn.$highestRow, NULL, TRUE, FALSE);
print_r($sheetData);
echo "\n\n";
}
?>
哪个输出:
[USER@BOX Directory]# php PhpExcelBigFileTest.php
Got here 1
Got here 2
Killed
这导致了一个问题:PHPExcel是否试图将整个文件加载到内存中,而不管我的筛选器如何?如果是这样,为什么PHP不能在2G内存使用时停止它,而是让它变得如此糟糕以至于内核不得不杀死PHP?
解决方法:
PHPExcel当前使用SimpleXML读取基于XML的格式,例如OfficeOpenXML(xlsx),OASIS(.odc)和Gnumeric,而不是提高内存效率的XMLReader.这意味着压缩存档中的每个文件XML文件都直接加载到PHP内存中进行解析,并构建PHPExcel对象.虽然单元分块通过将其持有的单元数减少到读取过滤器定义的单元数来减少PHPExcel对象使用的内存,但它仍需要将整个文件加载到内存中,以便SimpleXML对其进行解析.
开发团队已经研究了将数据直接从压缩存档流式传输到PHP的pull-parser XMLReader的情况,最初的实验表明这具有很高的内存效率.但是这也是重写电子表格阅读器使用此方法的主要代码.而且,由于开发资源有限,可用的工作时间有限,这不是轻而易举的任务.
除了仅通过将一部分单元格加载到PHPExcel对象中来减少内存外,您可能还需要查看单元格缓存.这在documentation中进行了描述,并允许以减少它们占用的内存的方式存储单元对象.提供了不同的方法来适应不同的系统,并且节省的内存量将取决于PHP版本和配置,因此您需要确定哪种方法最适合您自己的系统.使用单元缓存还需要付出一定的速度.通常,SQLite是内存效率最高的方法,但也是最慢的方法之一.