[我正在使用Box/Spout库,似乎与使用自定义十六进制颜色(例如,蓝色为0000FF)一起使用StyleBuilder相比,使用诸如Color :: BLUE之类的预定义颜色会占用大量内存。为什么会这样?
相关代码段:
//LOW MEMORY
$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor(Color::BLUE)->build());
//HIGH MEMORY
$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor($colorHex)->build());
输出:
setFontColor(Color :: BLUE):Peak memory usage: 1666 KB
setFontColor($ colorHex):Peak memory usage: 189436 KB
完整代码:
(出于演示目的,我正在加载一个小的250x150图像以提供多种颜色值)
<?php
require_once 'Spout/Autoloader/autoload.php';
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
//load an image
$img = imagecreatefrompng('input/test250x150.png');
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToFile('output/MyExcel.xlsx');
//height of the image
for($y=0; $y<150; $y++) {
//create or reset array to hold this row's cells
$row = [];
//width of the image
for($x=0; $x<250; $x++) {
//gets the pixel color
$index = imagecolorat($img, $x, $y);
$colorRGBArr = imagecolorsforindex($img, $index);
$colorHex = sprintf("%02x%02x%02x", $colorRGBArr['red'], $colorRGBArr['green'], $colorRGBArr['blue']);
//LOW MEMORY
//$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor(Color::BLUE)->build());
//HIGH MEMORY
$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor($colorHex)->build());
}
$writer->addRow(WriterEntityFactory::createRow($row));
}
$writer->close();
echo 'Peak memory usage: '.round(memory_get_peak_usage() / 1024).' KB';
?>
虽然Sprout
可以改进,Excel isn't designed for large quantities of styles,所以这并不是库的缺陷(您可能想要rescind the issue)
[好吧,这里有一些事情在起作用。.我用来测试的代码在我的帖子的底部-与颜色相关的关键功能是jonEg
和jonEgQuant
(这是其中的$by
变量可以调整)
Excel中的样式有点像HTML + CSS。样式最初是在文件中(在标题块中)定义的,然后在工作表中按名称(类)进行引用。从根本上讲,这表明Excel存储格式不是为多种样式设计的,而只是少数在许多单元格上可重复使用的样式]]
[Sprout
向用户隐藏了样式定义的复杂性-如果已经定义了样式(即已经存在确切的样式
Sprout
通过样式对象serializing构建所有样式的索引,并将其存储在数组中。每种样式大约1749字节,此索引消耗内存(除了它在内存中用于生成文件的实际样式对象之外)-可以在Sprout
使用此代码,我保证每个单元格都有不同的样式(使用行/列位置定义颜色的红色和绿色分量)-比您的图像颜色选择要极端,但可能不多
(没有示例图像我无法分辨)。 RGB三联体有1600万种颜色可供选择。.但我们的眼睛无法始终察觉到几个点的差异。例如,将在红色渐变中清楚地看到255,254,253 ... 128看起来很平滑,但是随机分布的255,254,253的单个块可能看起来像是单一颜色。对于除原色以外的任何颜色,此差异现在适用于三个维度(r,g和b)。 JPEG format利用了这一点(重建过程中压缩和噪声的组合)-因此每次看起来像统一块的颜色选择仍将返回略有不同的值当您使用jonEg
运行我的代码时,您将获得(100 * 150 + 1)15001样式,仅Sprout
中的索引就需要(15001 * 1749/1024/1024)〜25MB的内存。再次需要该索引来防止Excel中的每个单元格都有其自己的样式(但是,当然,在这个人为设计的示例中,我已确保它毫无意义-每个单元格都具有其自己的样式)-此使用〜100MB
当您使用jonEgQuant
运行我的代码(离开$by=16;
时)仅需要71种样式(但这是非常极端的舍入,总共只允许4096种颜色),-使用〜2MB
当您设置$by=4
(在ColorBuilder::jonEgQuant
内部)时-您现在有多达一百万种颜色,需要使用989种样式,并使用〜7MB
jonEg
)[Sprout
包括一种将RGB十进制值转换为字符串Color::rgb($r,$g,$b)
的方法-但这不会改变结果(sproutDoc
方法)
样式的尺寸远不止于此-我使用了背景色,但有前景(您的示例),下划线,粗体,斜体,字体,字体大小-所有这些都会使样式数量相乘(减少此处的色彩空间解决了您记录的问题,但是可以通过更改其他样式组件来撤消)
减少您使用的样式数量-Excel期望数量不多,Sprout
同样没有为此优化。压缩颜色(通过与jonEgQuant
一样四舍五入的值)是一种途径
改进Sprout
样式索引机制,但请注意,索引仅消耗了部分内存-这不小,但不是唯一的贡献者-每个样式对象需要
我在测试时放弃了图像读取/色彩收集,因为它可能会增加内存使用量,但不会增加问题的实质性
使用$colCount=250;
(在您的示例中),您超出了PHP中128MB的默认内存限制,最好将其设置为$colCount=100;
,并且可以运行所有测试而无需更改该设置
我切换为设置背景色,在Excel中打开时更容易看到差异
对于每个颜色生成器(jonEg
,sproutDoc
,fixedEg
,jonEgQuant
),都会生成一个不同的XLSX(有助于查看文件大小和渲染差异)
文件大小与Excel复杂度相匹配-jonEg
是179KB的文件(并且Excel很难打开),而jonEgQuant
是43KB
<?php require_once 'Spout/Autoloader/autoload.php'; use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Common\Entity\Style\Color; use Box\Spout\Writer\Common\Creator\Style\StyleBuilder; // -- -- Set this to one of the method names on ColorBuilder (that isn't a helper) $choice='jonEg'; // -- -- class ColorBuilder { static $defaultBlue=255; static function jonEg($x,$y) {return sprintf("%02x%02x%02x", $x, $y, static::$defaultBlue);} static function sproutDoc($x,$y) {return Color::rgb($x, $y, static::$defaultBlue);} static function fixedEg($x,$y) {return Color::BLUE;} static function jonEgQuant($x,$y) {$by=16;return sprintf("%02x%02x%02x", static::_quantize($x,$by),static::_quantize($y,$by), static::_quantize(static::$defaultBlue,$by));} //Helpers - don't use these for choice static function validate(string $name):bool { if ($name==null) return false;//Null or empty if (substr($name,0,1)=='_') return false;//Private by convention if ($name==='validate') return false;//Not the function you seek return method_exists('ColorBuilder',$name); } private static function _quantize(int $i,int $by=16):int {return round($i/$by)*$by;} } function createRow($y,$color) { $colCount=100; $row = []; for($x=0; $x<$colCount; $x++) { $row[] = WriterEntityFactory::createCell('*', (new StyleBuilder())->setBackgroundColor(ColorBuilder::$color($x,$y))->build()); } return $row; } function buildSheet($name) { if (!ColorBuilder::validate($name)) {throw new Error('Invalid color provider');} $writer = WriterEntityFactory::createXLSXWriter(); $writer->openToFile('output/'.$name.'.xlsx'); for($y=0; $y<150; $y++) { $writer->addRow(WriterEntityFactory::createRow(createRow($y,$name))); } $writer->close(); } buildSheet($choice); echo 'Peak memory usage: '.round(memory_get_peak_usage() / 1024).' KB';
Tech:
PHP 7.4.2 CLI, Sprout: 3.1.0, Win: 7 x64 (I know), Coffee: Venti Dark