PHPExcel работает 256, 512, а также 1024 МБ оперативной памяти - PullRequest
23 голосов
/ 27 января 2011

Я не понимаю этого. Таблица XSLX имеет размер около 3 МБ, но даже 1024 МБ ОЗУ недостаточно для того, чтобы 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 использовать 256 МБ оперативной памяти. Этого было недостаточно. Затем я удвоил сумму, а затем также попробовал 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 ]

72 голосов
/ 27 января 2011

Много было написано об использовании памяти PHPExcel на форуме PHPExcel; так что чтение некоторых из этих предыдущих обсуждений может дать вам несколько идей. PHPExcel хранит представление электронной таблицы в памяти и подвержено ограничениям памяти PHP.

Физический размер файла в значительной степени не имеет значения ... гораздо важнее знать, сколько ячеек (строк * столбцов на каждом листе) он содержит.

«Эмпирическое правило», которое я всегда использовал, составляет в среднем около 1 КБ / ячейка, поэтому для книги на 5 МБ требуется 5 ГБ памяти. Тем не менее, существует ряд способов уменьшить это требование. Их можно комбинировать, в зависимости от того, какая именно информация вам нужна в вашей рабочей книге, и что вы хотите с ней делать.

Если у вас есть несколько рабочих листов, но вам не нужно загружать их все, вы можете ограничить рабочие листы, которые Reader будет загружать, используя метод setLoadSheetsOnly (). Чтобы загрузить один именованный рабочий лист:

$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, memcache) ... но чем больше памяти вы экономите, тем медленнее будут выполняться ваши скрипты. Однако вы можете уменьшить объем памяти, необходимый каждой ячейке, примерно до 300 байт, поэтому для гипотетических ячеек 5M потребуется около 1,4 ГБ памяти PHP.

Кэширование ячеек описано в разделе 4.2.1 документации разработчика

EDIT

Глядя на свой код, вы используете итераторы, которые не особенно эффективны, и создаете массив данных ячейки. Возможно, вы захотите взглянуть на метод toArray (), который уже встроен в PHPExcel и делает это для вас. Также взгляните на это недавнее обсуждение на SO о новом варианте метода rangeToArray () для построения ассоциативного массива данных строки.

9 голосов
/ 27 января 2015

У меня была та же проблема с памятью в PHPExcel и фактически во всех других библиотеках.Чтение данных по частям, как предположил Марк Бейкер, могло бы решить проблему (кеширование тоже работает), но оказалось, что проблема с памятью стала проблемой времени.Время чтения и записи было экспоненциальным, поэтому для больших электронных таблиц оно не подходило.

PHPExcel и другие не предназначены для обработки больших файлов, поэтому я создал библиотеку, решающую эту проблему.Вы можете проверить это здесь: https://github.com/box/spout

Надеюсь, это поможет!

5 голосов
/ 22 июня 2015

Существует множество мер, которые вы можете предпринять, чтобы зарезервировать меньше памяти при работе с 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, помните, что именно размер файла заставляет процесс работать медленно иcrash.

Избегайте использования getCalculatedValue ();функция при чтении ячеек.

2 голосов
/ 27 января 2011

Ypu может попробовать PHP Excel http://ilia.ws/archives/237-PHP-Excel-Extension-0.9.1.html Это расширение C для php и очень быстрое.(Также использует меньше памяти, чем реализации PHP)

1 голос
/ 15 февраля 2016

Я столкнулся с этой проблемой, и, к сожалению, ни одно из предложенных решений не помогло мне. Мне нужна функциональность, предоставляемая PHPExcel (формулы, условные стили и т. Д.), Поэтому использование другой библиотеки не было возможным.

В конечном итоге я записал каждый лист в отдельный (временный) файл, а затем соединил эти отдельные файлы со специальным программным обеспечением, которое я написал. Это уменьшило потребление памяти с> 512 Мб до 100 Мб. См. https://github.com/infostreams/excel-merge, если у вас такая же проблема.

1 голос
/ 31 мая 2012

Просто перепостил мой пост из другой ветки. В нем описывается другой подход к созданию и редактированию электронных таблиц Excel на стороне сервера, который следует учитывать. Для большого объема данных я бы не рекомендовал такие инструменты, как PHPExcel или ApachePOI (для Java) из-за их требований к памяти. Существует еще один довольно удобный (хотя, может быть, немного сложный) способ ввода данных в электронные таблицы. Создание или обновление электронных таблиц Excel на стороне сервера может быть достигнуто, таким образом, простым редактированием XML. Вы можете разместить электронную таблицу XLSX на сервере, и каждый раз, когда данные собираются из дБ, вы распаковываете их с помощью php. Затем вы получаете доступ к определенным XML-файлам, которые содержат содержимое таблиц, которые необходимо ввести, и вставляете данные вручную. После этого вы сжимаете папку электронных таблиц, чтобы распространять ее как обычный файл XLSX. Весь процесс довольно быстрый и надежный. Очевидно, что существует несколько проблем и сбоев, связанных с внутренней организацией файла XLSX / Open XML (например, Excel стремится хранить все строки в отдельной таблице и использовать ссылки на эту таблицу в файлах рабочих таблиц). Но когда вводятся только данные, такие как числа и строки, это не так сложно. Если кому-то интересно, могу предоставить код.

1 голос
/ 08 апреля 2012

В моем случае phpexcel всегда перебирает 19999 строк. независимо от того, сколько строк на самом деле было заполнено. Поэтому 100 строк данных всегда приводили к ошибке памяти.

Возможно, вам просто нужно проверить, пустые ли ячейки в текущей строке, а затем "продолжить" или прервать цикл, который выполняет итерацию строк.

...