php 如何读大excel

phper需要处理excel的时候基本上都会考虑PhpSpreadsheet,但是用过这个库的人都知道,这个库所需要的内存很大,关键是随着文件的增大,所需要的内存不断增大。测试过,导入2M的文excel文件,get_memory_usage显示消耗了128M。那么对于较大文件的excel该如何处理呢,本文试着给出一个解决方案(前提:你的业务允许你分行处理文件,如果不允许,可以不用往下看了)。

为什么PhpSpreadsheet耗内存

$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filename);

跟过库代码的人都知道,在第一句load的时候就将整个文件的内容都load了内存,后面拿数据的时候就直接从内存中取了,这就是为什么随着文件增大,库所耗内存不断增大。但是对于很多业务,其实都允许读一行处理一行,完全没必要把数据一下子放到内存里面。
库文档里面提到了几个topic来解决这个问题的。

1. memory_saving

将数据的内存放入redis/memcache之类的,而不用本机的内存。
这个方法从stackoverflow几个帖子上看,速度会非常慢,实际几乎不可用。
本人没有测试过,主要是穷,买不起大内存的机器,也买不起云redis,所需要的内存还是会随着文件变大而不断增加,所以就算速度不慢,也不会是我考虑的解决方法。

2. reading-files

这里提供了一个思路,重写 readCell 这个函数,可以控制我们只保存其中的某些行和列。

$inputFileType = 'Xls';
$inputFileName = './sampleData/example2.xls';

class ChunkReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter
{
    private $startRow = 0;
    private $endRow   = 0;
    public function setRows($startRow, $chunkSize) {
        $this->startRow = $startRow;
        $this->endRow   = $startRow + $chunkSize;
    }
    public function readCell($column, $row, $worksheetName = '') {
        if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) {
            return true;
        }
        return false;
    }
}
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
$chunkSize = 2048;
$chunkFilter = new ChunkReadFilter();
$reader->setReadFilter($chunkFilter);
for ($startRow = 2; $startRow <= 65536; $startRow += $chunkSize) {
    $chunkFilter->setRows($startRow,$chunkSize);
    $spreadsheet = $reader->load($inputFileName);
}

这段分片读取数据的代码,可以有效降低内存,获取说对内存占用不随着文件增大而增大,已经满足需求,但是这里有一个很大问题的,每一次的文件load都是从文件头读一遍,这将导致读文件时间不断增大。

一行一行读

1. box/spout


use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;

$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext');

$reader->open($filePath);

foreach ($reader->getSheetIterator() as $sheet) {
    foreach ($sheet->getRowIterator() as $row) {
        // do stuff with the row
        $cells = $row->getCells();
        ...
    }
}

$reader->close();

这个库从提供的函数看,完全可以满足需求,不过它

  • php7.0
  • 不支持xls

首先强烈建议项目升级到php7+,因为我们是老项目,还没有动手改造,所以只能和这个库说再见。

2. csv格式

从这个库Spreadsheet的代码看,csv格式是非常好处理成一行行读的,自己动手改着了一个:

<?php

use PhpOffice\PhpSpreadsheet\Shared\StringHelper;

class Reader_Csv extends  PhpOffice\PhpSpreadsheet\Reader\Csv
{
    public function checkAndOpenFile($filename){
        ini_set('auto_detect_line_endings', true);
        if (!$this->canRead($filename)) {
            throw new Exception($filename . ' is an Invalid Spreadsheet file.');
        }
        $this->openFile($filename);
        $this->skipBOM();
        $this->checkSeparator();
        $this->inferSeparator();
    }
    public function readLine()
    {
        $rowData = fgetcsv($this->fileHandle, 0, $this->getDelimiter(), $this->getEnclosure(), $this->getEscapeCharacter());
        if($rowData === false)
        {
            return false;
        }
        $columnLetter = 'A';
        $arrLineData = array();
        foreach ($rowData as $rowDatum)
        {
            if ($rowDatum != '') {
                if ($this->getInputEncoding() !== 'UTF-8') {
                    $rowDatum = StringHelper::convertEncoding($rowDatum, 'UTF-8', $this->getInputEncoding());
                }
            }
            $arrLineData[$columnLetter] = $rowDatum;
            ++$columnLetter;
        }
        return $arrLineData;
    }

    public function closeFile(){
        fclose($this->fileHandle);
    }
}

所以处理CSV格式的文件可以这么处理了:

 $read = new Reader_Csv();
 $read->checkAndOpenFile($filename);
 while(True)
 {
    $rows ++;
    $arrRowData = $read->readLine();
    if($arrRowData === false){
        break;
    }
    // to the thing you want 
 }
 $read->closeFile();
                

此时,针对csv文件,我们可以读一行,处理,再读一行,速度,内存都不受影响。

3. xls/xlsx文件的处理

这两个文件由于格式比较复杂,自己来解析数据会非常困难。因此可以考虑将文件转化成csv格式再进行处理。把xls/xlsx转化成csv格式有非常多的工具

xlsx2csv

基于python的一个库,只支持xlsx格式
“Handles large XLSX files. Fast and easy to use.”

xls2csv

基于perl的库,只支持xls。
需要自己来判断文件格式,本地随便拿一个excel文件测试,并不能正确解析出对应的时间字段

本来如果用这两个库,可以达到降xls/xlsx转化成csv格式的,且安装比较轻量级。但是xls2csv这个库比较难用,所以考虑一个通用的转化库

Gnumeric

这是一个非常强大的软件,支持命令行ssconvert进行文件转化。
这个库安装很痛苦,所需要依赖的包特别多,因为它是一个桌面软件,但是现在都docker化了,这么麻烦的事情显然不用自己来做了。从hub上找到一个镜像 spoonest/gnumeric_docker,dockerfile特别简单,正式所需要的。
测试了一下这个软件,可以正确将xls/xlsx文件转化成csv格式的。但是对于浮点数,由于精度问题,比如0.01会变成0.010000001,这个对我的应用没有影响,因为对于浮点数,我只要小数点后2位,如果对精度有要求的应用,慎用。

本文提供了一个读大excel的处理思路,但是因为Gnumeric代码黑盒,因此不确定在转化过程中机器内存是否会超(这个后面做了大文件测试再来更新结果)。所以如果你只有xlsx文件的话,比较推荐box/spout & xlsx2csv

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容