Подтвердить что ты не робот

PHPExcel заканчивается из 256, 512 и также 1024 МБ ОЗУ

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

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
4b9b3361

Ответ 1

Было много написано об использовании памяти 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 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 Документации разработчика

ИЗМЕНИТЬ

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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