Qt Windows 下快速讀寫Excel指南
很多人搜如何讀寫excel都會看到用QAxObject
來進行操作,很多人試了之后都會發現一個問題,就是慢,非常緩慢!因此很多人得出結論是QAxObject
讀寫excel方法不可取,效率低。
后來我曾試過用ODBC等數據庫類型的接口進行讀寫,遇到中文嗝屁不說,超大的excel還是會讀取速度慢。
最后,看了一些開源的代碼后發現,Windows下讀取excel,還是用QAxObject
最快!沒錯,就是用QAxObject
讀寫最快!!!(讀取10萬單元格229ms)
大家以后讀取excel時(win下),不用考慮別的方法,用QAxObject
就行,速度杠杠的,慢是你操作有誤!下面就說說如何能提高其讀取效率。
讀取excel慢的原因
這里不說如何打開或生成excel,着重說說如何快速讀取excel。
網上搜到用Qt操作excel的方法,讀取都是使用類似下面這種方法進行:
1 QVariant ExcelBase::read(int row, int col) 2 { 3 QVariant ret; 4 if (this->sheet != NULL && ! this->sheet->isNull()) 5 { 6 QAxObject* range = this->sheet->querySubObject("Cells(int, int)", row, col); 7 //ret = range->property("Value");
8 ret = range->dynamicCall("Value()"); 9 delete range; 10 } 11 return ret; 12 }
讀取慢的根源就在於sheet->querySubObject("Cells(int, int)", row, col)
試想有10000個單元就得調用10000次querySubObject
,網絡上90%的教程都沒說這個querySubObject
產生的QAxObject*
最好進行手動刪除,雖然在它的父級QAxObject
會管理它的內存,但父級不析構,子對象也不會析構,若調用10000次,就會產生10000個QAxObject
對象
得益於QT快速讀取數據量很大的Excel文件此文,下面總結如何快速讀寫excel
快速讀取excel文件
原則是一次調用querySubObject
把所有數據讀取到內存中
VBA中可以使用UsedRange
把所有用到的單元格范圍返回,並使用屬性Value
把這些單元格的所有值獲取。
這時,獲取到的值是一個table,但Qt把它變為一個變量QVariant來儲存,其實實際是一個QList<QList<QVariant> >
,此時要操作里面的內容,需要把這個QVariant
轉換為QList<QList<QVariant> >
先看看獲取整個單元格的函數示意(這里ExcelBase是一個讀寫excel的類封裝):
1 QVariant ExcelBase::readAll() 2 { 3 QVariant var; 4 if (this->sheet != NULL && ! this->sheet->isNull()) 5 { 6 QAxObject *usedRange = this->sheet->querySubObject("UsedRange"); 7 if(NULL == usedRange || usedRange->isNull()) 8 { 9 return var; 10 } 11 var = usedRange->dynamicCall("Value"); 12 delete usedRange; 13 } 14 return var; 15 }
代碼中this->sheet
是已經打開的一個sheet,再獲取內容時使用this->sheet->querySubObject("UsedRange");
即可把所有范圍都獲取。
下面這個castVariant2ListListVariant函數把QVariant
轉換為QList<QList<QVariant> >
1 ///
2 /// \brief 把QVariant轉為QList<QList<QVariant> > 3 /// \param var 4 /// \param res 5 /// 6 void ExcelBase::castVariant2ListListVariant(const QVariant &var, QList<QList<QVariant> > &res) 7 { 8 QVariantList varRows = var.toList(); 9 if(varRows.isEmpty()) 10 { 11 return; 12 } 13 const int rowCount = varRows.size(); 14 QVariantList rowData; 15 for(int i=0;i<rowCount;++i) 16 { 17 rowData = varRows[i].toList(); 18 res.push_back(rowData); 19 } 20 }
這樣excel的所有內容都轉換為QList<QList<QVariant>>
保存,其中QList<QList<QVariant> >
中QList<QVariant>
為每行的內容,行按順序放入最外圍的QList中。
對於如下如的excel:
讀取后的QList<QList<QVariant> >
結構如下所示:
繼續展開
下面看看此excel的讀取速度有多高
這里有個excel,有1000行,100列,共計十萬單元格,打開使用了一些時間,讀取10萬單元格耗時229毫秒,
讀取的代碼如下:(完整源代碼見后面)
1 void MainWindow::on_action_open_triggered() 2 { 3 QString xlsFile = QFileDialog::getOpenFileName(this,QString(),QString(),"excel(*.xls *.xlsx)"); 4 if(xlsFile.isEmpty()) 5 return; 6 QElapsedTimer timer; 7 timer.start(); 8 if(m_xls.isNull()) 9 m_xls.reset(new ExcelBase); 10 m_xls->open(xlsFile); 11 qDebug()<<"open cost:"<<timer.elapsed()<<"ms";timer.restart(); 12 m_xls->setCurrentSheet(1); 13 m_xls->readAll(m_datas); 14 qDebug()<<"read data cost:"<<timer.elapsed()<<"ms";timer.restart(); 15 QVariantListListModel* md = qobject_cast<QVariantListListModel*>(ui->tableView->model()); 16 if(md) 17 { 18 md->updateData(); 19 } 20 qDebug()<<"show data cost:"<<timer.elapsed()<<"ms";timer.restart(); 21 }
上面的m_xls和m_datas是成員變量:
1 QScopedPointer<ExcelBase> m_xls; 2 QList< QList<QVariant> > m_datas;
讀取的耗時:
1 "D:\czy_blog\czyBlog\04_fastReadExcel\src\fastReadExcelInWindows\excelRWByCztr1988.xls"
2 open cost: 1183 ms 3 read data cost: 229 ms 4 show data cost: 14 ms
10萬個也就0.2秒而已
快速寫入excel文件
同理,能通過QAxObject *usedRange = this->sheet->querySubObject("UsedRange");
實現快速讀取,也可以實現快速寫入
快速寫入前需要些獲取寫入單元格的范圍:Range(const QString&)
如excel的A1為第一行第一列,那么A1:B2就是從第一行第一列到第二行第二列的范圍。
要寫入這個范圍,同樣也是通過一個與之對應的QList<QList<QVariant> >
,具體見下面代碼:
1 ///
2 /// \brief 寫入一個表格內容 3 /// \param cells 4 /// \return 成功寫入返回true 5 /// \see readAllSheet 6 /// 7 bool ExcelBase::writeCurrentSheet(const QList<QList<QVariant> > &cells) 8 { 9 if(cells.size() <= 0) 10 return false; 11 if(NULL == this->sheet || this->sheet->isNull()) 12 return false; 13 int row = cells.size(); 14 int col = cells.at(0).size(); 15 QString rangStr; 16 convertToColName(col,rangStr); 17 rangStr += QString::number(row); 18 rangStr = "A1:" + rangStr; 19 qDebug()<<rangStr; 20 QAxObject *range = this->sheet->querySubObject("Range(const QString&)",rangStr); 21 if(NULL == range || range->isNull()) 22 { 23 return false; 24 } 25 bool succ = false; 26 QVariant var; 27 castListListVariant2Variant(cells,var); 28 succ = range->setProperty("Value", var); 29 delete range; 30 return succ; 31 }
此函數是把數據從A1開始寫
函數中的convertToColName
為把列數,轉換為excel中用字母表示的列數,這個函數是用遞歸來實現的:
1 ///
2 /// \brief 把列數轉換為excel的字母列號 3 /// \param data 大於0的數 4 /// \return 字母列號,如1->A 26->Z 27 AA 5 /// 6 void ExcelBase::convertToColName(int data, QString &res) 7 { 8 Q_ASSERT(data>0 && data<65535); 9 int tempData = data / 26; 10 if(tempData > 0) 11 { 12 int mode = data % 26; 13 convertToColName(mode,res); 14 convertToColName(tempData,res); 15 } 16 else
17 { 18 res=(to26AlphabetString(data)+res); 19 } 20 } 21 ///
22 /// \brief 數字轉換為26字母 23 ///
24 /// 1->A 26->Z 25 /// \param data 26 /// \return 27 /// 28 QString ExcelBase::to26AlphabetString(int data) 29 { 30 QChar ch = data + 0x40;//A對應0x41
31 return QString(ch); 32 }
看看寫excel的耗時:
1 void MainWindow::on_action_write_triggered() 2 { 3 QString xlsFile = QFileDialog::getExistingDirectory(this); 4 if(xlsFile.isEmpty()) 5 return; 6 xlsFile += "/excelRWByCztr1988.xls"; 7 QElapsedTimer timer; 8 timer.start(); 9 if(m_xls.isNull()) 10 m_xls.reset(new ExcelBase); 11 m_xls->create(xlsFile); 12 qDebug()<<"create cost:"<<timer.elapsed()<<"ms";timer.restart(); 13 QList< QList<QVariant> > m_datas; 14 for(int i=0;i<1000;++i) 15 { 16 QList<QVariant> rows; 17 for(int j=0;j<100;++j) 18 { 19 rows.append(i*j); 20 } 21 m_datas.append(rows); 22 } 23 m_xls->setCurrentSheet(1); 24 timer.restart(); 25 m_xls->writeCurrentSheet(m_datas); 26 qDebug()<<"write cost:"<<timer.elapsed()<<"ms";timer.restart(); 27 m_xls->save(); 28 }
輸出:
1 create cost: 814 ms 2 "A1:CV1000"
3 write cost: 262 ms
寫10萬個數據耗時262ms,有木有感覺很快,很強大
結論
- Qt在windows下讀寫excel最快速的方法還是使用QAxObject
- 不要使用類似
sheet->querySubObject("Cells(int, int)", row, col);