2015-05-18 39 views
2

我使用PHPExcel库从Excel文件中读取数据。我的文件大约是5MB,70列和20000行。加载文件的代码是:使用PHPExcel只读一个大的Excel文件的行

 $sheetnames = array('Classification'); 
    $excelFile = Yii::app()->basePath . '/categories/'. $region .'.xlsx'; 
    $objReader = PHPExcel_IOFactory::createReader('Excel2007'); 
    $objReader->setReadDataOnly(true); 
    $objReader->setLoadSheetsOnly($sheetnames); 
    $objPHPExcel = $objReader->load($excelFile); 

Excel文件有以下结构:

Title | Id | Path | Attribute 1 | Attribute 2 | ... | Attribute 65 

该文件的加载持续约6分钟,需要太多的CPU和RAM。 实际上,我需要知道给定ID只有一行的数据。现在我遍历所有行并检查id。这太低效了。

所以我有2个问题:

  1. 是否有办法更快地加载文件? (我不能用这么多时间,CPU和RAM)
  2. 有没有一种方法可以更有效地搜索文件?
+0

小心,PHPExcel是内存贪婪。对于每个单元格,它需要高达1K的内存。所以对于你的20000行(以及68列),你需要高达20 * 68M的空闲RAM ...... – Random

+0

你有没有试过像读取过滤器的东西?你可以设置一个过滤器来只读ID列,在那里搜索,然后只读取匹配的行? –

+0

@Random?每个细胞1Mo?!?!?当然你会开玩笑!我的“经验法则”估计实际上是32位PHP中的1k/cell,64位PHP中的1.6k/cell –

回答

5

开始,通过使用读取滤波器仅加载ID列:

/** Define a Read Filter class implementing PHPExcel_Reader_IReadFilter */ 
class SingleColumnFilter implements PHPExcel_Reader_IReadFilter 
{ 
    private $requestedColumn; 

    public function __construct($column) { 
     $this->requestedColumn = $column; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 
     if ($column == $this->requestedColumn) { 
      return true; 
     } 
     return false; 
    } 
} 

/** Create an Instance of our Read Filter **/ 
$idColumnFilter = new SingleColumnFilter('B'); // Id is column B 

$objReader = PHPExcel_IOFactory::createReader('Excel2007'); 
$objReader->setReadDataOnly(true); 
$objReader->setLoadSheetsOnly($sheetnames); 
/** Tell the Reader that we want to use the Read Filter **/ 
$objReader->setReadFilter($idColumnFilter); 
/** Load only the column that matches our filter to PHPExcel **/ 
$objPHPExcel = $objReader->load($inputFileName); 

然后PHPExcel将在B列细胞仅负载数据。然后,您可以在单元的子集中搜索所需的值(1列和22,000行仅为22,000个单元,所以应该比加载整个文件所需的2.5MB要接近35MB),然后使用类似的根据行号进行过滤,只加载已识别的单个行。

编辑

最新的1.8.1版本PHPExcel也具有columnIterator应该更容易来循环下来寻找特定ID值的列:

$found = false; 
foreach ($objPHPExcel->getActiveSheet()->getColumnIterator('B') as $column) { 
    $cellIterator = $column->getCellIterator(); 
    $cellIterator->setIterateOnlyExistingCells(true); 
    foreach ($cellIterator as $key => $cell) { 
     if ($cell->getValue == 'ABC') { 
      $found = true; 
      $rowId = $cell->getRow() 
      break 2; 
    } 
} 

编辑# 2

一旦你确定了你想要的行,你可以使用第二个过滤器来重新加载Excel文件......但只有那一行:

/** Define a Read Filter class implementing PHPExcel_Reader_IReadFilter */ 
class SingleRowFilter implements PHPExcel_Reader_IReadFilter 
{ 
    private $requestedRow; 

    public function __construct($row) { 
     $this->requestedRow = $row; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 
     if ($row == $this->requestedRow) { 
      return true; 
     } 
     return false; 
    } 
} 

if ($found) { 
    /** Create an Instance of our Read Filter **/ 
    $rowFilter = new SingleRowFilter($rowId); 

    $objReader2 = PHPExcel_IOFactory::createReader('Excel2007'); 
    $objReader2->setReadDataOnly(true); 
    $objReader2->setLoadSheetsOnly($sheetnames); 
    /** Tell the Reader that we want to use the Read Filter **/ 
    $objReader2->setReadFilter($rowFilter); 
    /** Load only the single row that matches our filter to PHPExcel **/ 
    $objPHPExcel2 = $objReader2->load($inputFileName); 
} 
+0

感谢您的好评。无论如何,大约2分钟的时间,但我想这是我能从PHPExcel获得的最好时间。可能我必须更改我的应用程序的业务逻辑以减少PHPExcel的使用。但非常感谢! – Bfcm

+0

高兴我可以告诉你如何使它快3倍 –

2

处理exel文件有点困难。只需使用shell exec将它们转换为CSV,并尽可能多地对这些CSV文件执行任何操作。

$ easy_install xlsx2csv 
$ xlsx2csv file.xlsx newfile.csv 

转换时间不到一秒钟。

+0

性能怎么样? – Bfcm

+0

第二个问题呢? – Bfcm

+0

将其转换为CSV后,无论CSV大小如何,都不会有任何性能问题。搜索将很容易,因为你可以将整个CSV文件作为字符串来处理,并且执行str_pos来找到你需要的行,或者使用php有的csv解析器 – Dimi

0

如果你想加速你的程序,并减少内存消耗,你可以喷看一看:https://github.com/box/spout

所有你需要做的是:

$reader = ReaderFactory::create(Type::CSV); 
$reader->open($filePath); 

while ($reader->hasNextRow()) { 
    $row = $reader->nextRow(); 
    $id = $row[1]; 
    // do stuff with the $id 
} 

$reader->close(); 

它需要花费1到2秒要经过整个文件:)

+0

试过了。没有为yii应用程序工作。看看细节: http://stackoverflow.com/questions/30329170/using-box-spout-3rd-party-library-in-yii-application-command – Bfcm

相关问题