关于php:PHPExcel非常慢-改进方法?

PHPExcel very slow - ways to improve?

我正在使用PHPExcel在.xlsx中生成报告。在最初的测试阶段,使用较小的数据集(数十行,3张纸)就可以了,但是现在当在每张纸上有500行以上的实际生产数据上使用它时,它会变得异常缓慢。 48秒生成一个文件,并且在运行包含更多信息的报表时,整个操作失败,并显示Fatal error: Maximum execution time of 30 seconds exceeded in PHPExcel/Worksheet.php on line 1041。有时它在另一个PHPExcel文件中,因此我怀疑确切的位置是否相关。

理想情况下,如果可能的话,我想以某种方式加快速度。如果不是,则至少增加此脚本的执行限制。

到目前为止,我唯一看到的建议是设置范围而不是单个单元格的样式。不幸的是,我已经在范围内进行样式设置,而且样式也很小。还有其他建议吗?


是否正在填充工作表?还是省钱?觉得太慢?

如何用数据填充电子表格?

  • 使用fromArray()方法比填充每个单个单元格更为有效,尤其是在使用Advanced Value Binder自动设置单元格数据类型的情况下。
  • 如果要使用

    为工作表中的每个单元格设置值

    1
    2
    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x);
    $objPHPExcel->getActiveSheet()->setCellValue('B1',$y);

    使用

    1
    2
    3
    $sheet = $objPHPExcel->getActiveSheet();
    $sheet->setCellValue('A1',$x);
    $sheet->setCellValue('B1',$y);

    ,因此您只需访问一次getActiveSheet()方法;
    或利用流利的界面仅通过一次调用$objPHPExcel->getActiveSheet()

    即可设置多个单元

    1
    2
    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x)
                                  ->setCellValue('B1',$y);

您已评论过将样式应用于单元格区域:

  • 您还可以选择使用applyFromArray()一次设置各种样式设置。
  • 如果您可以将样式应用于列或行而不是简单地应用于范围,则效率会大大提高

如果您在工作簿中使用公式,则在保存时:

  • 使用

    1
    $objWriter->setPreCalculateFormulas(false)

    禁止在PHPExcel本身中计算公式。

这些只是一些有助于提高性能的提示,并且在论坛主题中还有很多建议。它们不一定都可以提供帮助,太多取决于您的特定工作簿来提供绝对值,但是您应该能够提高该速度。即使是我用于开发的小笔记本,也可以比生产服务器更快地编写3个工作表,20列,2,000行的Excel 2007文件。

编辑

如果可以简单地提高PHPExcel本身的速度,那么我早就做了。实际上,我一直在进行性能测试,以了解如何提高其速度。如果您想要更快的速度而不是PHPExcel本身可以提供的速度,那么这里有替代库的列表。


我也遇到了这个问题。以为我会投入两分钱,因为这个问题得到了太多的关注。

设置单元格值

使用fromArray()方法,而不是分别为每个单元格设置值。摘自Wiki。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$arrayData = array(
array(NULL, 2010, 2011, 2012),
array('Q1',   12,   15,   21),
array('Q2',   56,   73,   86),
array('Q3',   52,   61,   69),
array('Q4',   30,   32,    0),
);

$as = $objPHPExcel->getActiveSheet();

$as->fromArray(
    $arrayData,  // The data to set
    NULL,        // Array values with this value will not be set
    'C3'         // Top left coordinate of the worksheet range where
                 //    we want to set these values (default is A1)
);

样式单元格

静态

将样式应用于某个范围,比分别为每个单元格设置样式(注意模式?)要快得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$default_style = array(
    'font' => array(
        'name' => 'Verdana',
        'color' => array('rgb' => '000000'),
        'size' => 11
    ),
    'alignment' => array(
        'horizontal' => \\PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
        'vertical' => \\PHPExcel_Style_Alignment::VERTICAL_CENTER
    ),
    'borders' => array(
        'allborders' => array(
            'style' => \\PHPExcel_Style_Border::BORDER_THIN,
            'color' => array('rgb' => 'AAAAAA')
        )
    )
);

// Apply default style to whole sheet
$as->getDefaultStyle()->applyFromArray($default_style);

$titles = array(
    'Name',
    'Number',
    'Address',
    'Telephone'
);

$title_style = array(
    'font' => array(
        'bold' => true
    ),
    'fill' => array(
        'type' => \\PHPExcel_Style_Fill::FILL_SOLID,
        'startcolor' => array('rgb' => '5CACEE')
    ),
    'alignment' => array(
        'wrap' => true
    )
);

$as->fromArray($titles, null, 'A1'); // Add titles

$last_col = $as->getHighestColumn(); // Get last column, as a letter

// Apply title style to titles
$as->getStyle('A1:'.$last_col.'1')->applyFromArray($title_style);

动态

我使用PHPExcel来检查电子表格中给出的数据与数据库中的当前数据。由于每个单元格都是单独检查的,因此我将样式放在一个数组中(无样式时为null),并使用下面的循环来获取要应用样式的单元格范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
 * $row is previously set in a loop iterating through each
 *     row from the DB, which is equal to a spreadsheet row.
 * $styles = array(0 => 'error', 1 => 'error', 2 => null, 3 => 'changed', ...);
 */

$start = $end = $style = null;
foreach ($styles as $col => $s) {
    if (!$style && !$s) continue;
    if ($style === $s) {
        $end = $col;
    } else {
        if ($style) {
            $array = null;
            switch ($style) {
                case 'changed':
                    $array = $this->changed_style;
                    break;
                case 'error':
                    $array = $this->error_style;
                    break;
                case 'ignored':
                    $array = $this->ignored_style;
                    break;
            }
            if ($array) {
                $start = \\PHPExcel_Cell::stringFromColumnIndex($start);
                $end = \\PHPExcel_Cell::stringFromColumnIndex($end);
                $as->getStyle($start.$row.':'.$end.$row)->applyFromArray($array);
            }
        }
        $start = $end = $col;
        $style = $s;
    }
}


我遇到了同样的问题-我试图写入的数据大约有450行,其中包含11列数据,并且我一直在30秒超时的情况下运行。通过批量添加所有新行,然后经过并设置事实后的单元格内容,我可以将执行时间降低到2秒或更短。换句话说,我在一次对insertNewRowBefore()的调用中插入了450行,然后循环遍历并稍后在这些行中设置内容。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
$num_rows = count($output_rows);
$last_row = $sheet->getHighestRow();
$row = $last_row + 1;
$sheet->insertNewRowBefore($row, $num_rows);
// Now add all of the rows to the spreadsheet
foreach($output_rows as $line) {
    $i = 0;
    foreach($line as $val) {
        // Do your setCellValue() or setCellValueByColumnAndRow() here
        $i++;
    }
    $row++;
}


对于具有a-amj列(?800)并且只有约50行的XLSX导出,我也遇到了30秒的边界。为了测试我的程序,我将处理的行数限制为仅7个,这在25秒内起作用。

  • 从单个$ objPHPExcel-> getActiveSheet()到$ sheet(第一个建议),实际上将有限行的时间从25秒增加到26秒。

  • 真正帮助我的是用一个在PHP中递增的简单$ column_nr变量替换了所有的getHighestDataColumn(),我从26秒缩短到了7秒。

  • 此后,我能够在11秒内处理所有50行。


    我绝不是使用PHPExcel的专家,但是OfficeOpenXML格式(* .xlsx文件的格式)本身就是一组XML文件,打包在ZIP存档中,扩展名为* .xlsx。如果您重视性能并知道将要传递的数据类型,则最好是构建自己的XLSX生成器,简化为最重要的功能,也许在数据库层上进行一些计算等,而不是解析整个数据。文档。

    为此,您可以首先分析使用较小数据集生成的文件(通过将扩展名从* .xlsx更改为* .zip,将其解压缩并浏览单个文件的内容)。这样,您可以确定真正需要的内容并自己生成(通过创建适当的XML文件并将其打包到ZIP存档中,然后重命名为* .xlsx扩展名)。

    还有OfficeOpenXML的规范,该规范很大(成千上万页),因此除非您确实愿意,否则我不建议阅读它。创建文件以匹配PHPExcel生成文件的方式就足够了。

    上面提到的解决方案不包含任何与PHPExcel有关的技巧,因为我不是该领域的专家。但是,我以前对OOXML标准化过程很感兴趣,如果对这一标准的了解可以帮助您解决问题,我将感到非常高兴。


    我之前从未见过的一个性能提示与添加工作表有关,或者更具体地说,是设置工作表的标题。如果添加许多工作表,则操作顺序可能会产生巨大的影响。对于以下测试,我使用了包含120个填充工作表的电子表格,并计时了创建另外120个空工作表所花费的时间。

    首先,使用文档中显示的步骤:

    1
    2
    3
    4
    5
    for ($i = 0; $i < 120; $i++) {
        $sheet = $spreadsheet->createSheet();
        $sheet->setTitle('Sheet Title' . $i);
    }
    // Time: 12.5605s

    文档中的第二种替代方法:

    1
    2
    3
    4
    5
    for ($i = 0; $i < 120; $i++) {
        $sheet = new Worksheet($spreadsheet, 'Sheet Title' . $i);
        $spreadsheet->addSheet($sheet);
    }
    // Time: 0.0266s

    可以通过使用setTitle的第二个参数来弥补上述两种方法之间的大部分性能差距(如果可以的话,请这样做);

    1
    2
    3
    4
    5
    for ($i = 0; $i < 120; $i++) {
        $sheet = $spreadsheet->createSheet();
        $sheet->setTitle('Sheet Title' . $i, false);
    }
    // Time: 0.5793s

    就我而言,我通过将缓存存储方法更改为内存中的gzip cache_in_memory_gzip

    来提高了性能

    1
    2
    $cm = \\PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
    \\PHPExcel_Settings::setCacheStorageMethod($cm);

    我遇到了完全相同的问题。得到了一个5000行,32列的CSV文件,该文件花了很多时间才能处理。事实证明,几乎所有用于"处理"的时间实际上都是字符编码,默认情况下,该字符编码设置为将所有内容编码为UTF8。因此,如果进入config \\\\ excel.php文件并向下滚动至编码,只需将其设置为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    |--------------------------------------------------------------------------
    | Import encoding
    |--------------------------------------------------------------------------
    */

        'encoding' => array(

            'input'  => '',
            'output' => ''

        ),

    仅此一项-上述文件需要大约8秒钟的时间来处理。不过,您可能要警告客户端正确保存CSV。