web-dev-qa-db-ja.com

PHPExcelは、256、512、および1024MBのRAM

分かりません。 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を使用することを許可しました。それでは十分ではありませんでした。

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
23
Richard Knop

PHPExcelフォーラムでは、PHPExcelのメモリ使用量について多くのことが書かれています。したがって、これらの以前の議論のいくつかを読むと、いくつかのアイデアが得られるかもしれません。 PHPExcelはスプレッドシートの「メモリ内」表現を保持しており、PHPメモリ制限の影響を受けやすい。

ファイルの物理的なサイズはほとんど無関係です...ファイルに含まれるセル(各ワークシートの行*列)の数を知ることは非常に重要です。

私がいつも使用していた「経験則」は、平均で約1k /セルなので、5Mセルのブックには5GBのメモリが必要になります。ただし、その要件を軽減する方法はいくつかあります。これらは、ワークブック内でアクセスする必要がある情報と、ワークブックで何をしたいかに応じて、組み合わせることができます。

複数のワークシートがあり、それらすべてをロードする必要がない場合は、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()の1回の呼び出しで複数のワークシートを指定できます。

$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);

読み取りフィルターを使用すると、「チャンク」でワークブックを読み取ることもできるため、一度に1つのチャンクのみがメモリ常駐します。

$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 GBのPHP=メモリ。

セルキャッシングについては、開発者向けドキュメントのセクション4.2.1で説明しています。

[〜#〜] edit [〜#〜]

コードを見ると、特に効率的ではないイテレーターを使用して、セルデータの配列を構築しています。すでにPHPExcelに組み込まれているtoArray()メソッドを見てみたいと思うかもしれません。また、これを見てください 最近の議論 on SO行データの連想配列を構築する新しいバリアントメソッドrangeToArray()について).

72
Mark Baker

PHPExcelと実際には他のすべてのライブラリでも同じメモリ問題が発生しました。 Mark Ba​​kerが示唆したように、データをチャンクで読み取ることで問題を解決できました(キャッシュも機能します)が、メモリの問題が時間の問題になったことが判明しました。読み取りと書き込みの時間は指数関数的であったため、大きなスプレッドシートの場合、適切ではありませんでした。

PHPExcelなどは大きなファイルを処理するためのものではないため、この問題を解決するライブラリを作成しました。こちらで確認できます: https://github.com/box/spout

お役に立てば幸いです!

9
Adrien

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()を使用しないでください。セルを読み取るときに機能します。

5
pancy1

Ypuは試すことができますPHP Excel http://ilia.ws/archives/237-PHP-Excel-Extension-0.9.1.html PHPのC拡張そして非常に高速です(また、PHP実装よりも少ないメモリを使用します)

2
osm

別のスレッドから投稿を再投稿するだけです。考慮すべきExcelスプレッドシートのサーバー側での生成または編集へのさまざまなアプローチについて説明します。大量のデータの場合、メモリ要件のため、PHPExcelやApachePOI(Java用)などのツールはお勧めしません。スプレッドシートにデータを挿入する別の非常に便利な方法(少し厄介かもしれませんが)があります。 Excelスプレッドシートのサーバーサイドでの生成または更新は、XMLの簡単な編集で実現できます。 XLSXスプレッドシートをサーバー上に配置し、dBからデータを収集するたびに、phpを使用して解凍します。次に、挿入する必要があるワークシートの内容を保持している特定のXMLファイルにアクセスし、データを手動で挿入します。その後、スプレッドシートフォルダーを圧縮して、通常のXLSXファイルとして配布します。プロセス全体が非常に高速で信頼性があります。明らかに、XLSX/Open XMLファイルの内部構成に関連する問題や不具合はほとんどありません(たとえば、Excelはすべての文字列を別々のテーブルに保存し、ワークシートファイルでこのテーブルへの参照を使用する傾向があります)。しかし、数字や文字列などのデータのみを注入する場合、それほど難しくありません。誰かが興味を持っているなら、私はいくつかのコードを提供できます。

1
bazinac

私の場合、phpexcelは常に19999行を繰り返し処理しました。関係なく、実際に満たされた行数。そのため、100行のデータは常にメモリエラーになりました。

おそらく、現在の行のセルが空で、ループを「継続」するかどうかをチェックするだけで、行が繰り返されます。

1
Robert

私はこの問題に遭遇しましたが、残念ながら提案された解決策はどれも私を助けられませんでした。 PHPExcelが提供する機能(式、条件付きスタイリングなど)が必要なので、別のライブラリを使用することは選択肢ではありませんでした。

最終的には、各ワークシートを個々の(一時的な)ファイルに書き込んでから、これらの個別のファイルを、作成したいくつかの特別なソフトウェアと組み合わせていました。これにより、メモリ消費が512 Mbを超えて100 Mbを大幅に下回りました。同じ問題がある場合は、 https://github.com/infostreams/Excel-merge を参照してください。

1
Edward