Qt 下快速讀寫Excel指南(塵中遠)


Qt Windows 下快速讀寫Excel指南

很多人搜如何讀寫excel都會看到用QAxObject來進行操作,很多人試了之后都會發現一個問題,就是慢,非常緩慢!因此很多人得出結論是QAxObject讀寫excel方法不可取,效率低。 
后來我曾試過用ODBC等數據庫類型的接口進行讀寫,遇到中文嗝屁不說,超大的excel還是會讀取速度慢。 
最后,看了一些開源的代碼后發現,Windows下讀取excel,還是用QAxObject最快!沒錯,就是用QAxObject讀寫最快!!!(讀取10萬單元格229ms) 
大家以后讀取excel時(win下),不用考慮別的方法,用QAxObject就行,速度杠杠的,慢是你操作有誤!下面就說說如何能提高其讀取效率。

讀取excel慢的原因

這里不說如何打開或生成excel,着重說說如何快速讀取excel。 
網上搜到用Qt操作excel的方法,讀取都是使用類似下面這種方法進行:

QVariant ExcelBase::read(int row, int col) { QVariant ret; if (this->sheet != NULL && ! this->sheet->isNull()) { QAxObject* range = this->sheet->querySubObject("Cells(int, int)", row, col); //ret = range->property("Value"); ret = range->dynamicCall("Value()"); delete range; } return ret; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 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的類封裝):

QVariant ExcelBase::readAll() { QVariant var; if (this->sheet != NULL && ! this->sheet->isNull()) { QAxObject *usedRange = this->sheet->querySubObject("UsedRange"); if(NULL == usedRange || usedRange->isNull()) { return var; } var = usedRange->dynamicCall("Value"); delete usedRange; } return var; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

代碼中this->sheet是已經打開的一個sheet,再獲取內容時使用this->sheet->querySubObject("UsedRange");即可把所有范圍都獲取。

下面這個castVariant2ListListVariant函數把QVariant轉換為QList<QList<QVariant> >

/// /// \brief 把QVariant轉為QList<QList<QVariant> > /// \param var /// \param res /// void ExcelBase::castVariant2ListListVariant(const QVariant &var, QList<QList<QVariant> > &res) { QVariantList varRows = var.toList(); if(varRows.isEmpty()) { return; } const int rowCount = varRows.size(); QVariantList rowData; for(int i=0;i<rowCount;++i) { rowData = varRows[i].toList(); res.push_back(rowData); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

這樣excel的所有內容都轉換為QList<QList<QVariant>>保存,其中QList<QList<QVariant> >QList<QVariant>為每行的內容,行按順序放入最外圍的QList中。

對於如下如的excel:

這里寫圖片描述

讀取后的QList<QList<QVariant> >結構如下所示:

這里寫圖片描述

繼續展開

這里寫圖片描述

下面看看此excel的讀取速度有多高 
這里有個excel,有1000行,100列,共計十萬單元格,打開使用了一些時間,讀取10萬單元格耗時229毫秒, 
讀取的代碼如下:(完整源代碼見后面)

void MainWindow::on_action_open_triggered()
{
    QString xlsFile = QFileDialog::getOpenFileName(this,QString(),QString(),"excel(*.xls *.xlsx)"); if(xlsFile.isEmpty()) return; QElapsedTimer timer; timer.start(); if(m_xls.isNull()) m_xls.reset(new ExcelBase); m_xls->open(xlsFile); qDebug()<<"open cost:"<<timer.elapsed()<<"ms";timer.restart(); m_xls->setCurrentSheet(1); m_xls->readAll(m_datas); qDebug()<<"read data cost:"<<timer.elapsed()<<"ms";timer.restart(); QVariantListListModel* md = qobject_cast<QVariantListListModel*>(ui->tableView->model()); if(md) { md->updateData(); } qDebug()<<"show data cost:"<<timer.elapsed()<<"ms";timer.restart(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面的m_xls和m_datas是成員變量:

QScopedPointer<ExcelBase> m_xls; QList< QList<QVariant> > m_datas;
  • 1
  • 2

讀取的耗時:

"D:\czy_blog\czyBlog\04_fastReadExcel\src\fastReadExcelInWindows\excelRWByCztr1988.xls"
open cost: 1183 ms
read data cost: 229 ms
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> >,具體見下面代碼:

/// /// \brief 寫入一個表格內容 /// \param cells /// \return 成功寫入返回true /// \see readAllSheet /// bool ExcelBase::writeCurrentSheet(const QList<QList<QVariant> > &cells) { if(cells.size() <= 0) return false; if(NULL == this->sheet || this->sheet->isNull()) return false; int row = cells.size(); int col = cells.at(0).size(); QString rangStr; convertToColName(col,rangStr); rangStr += QString::number(row); rangStr = "A1:" + rangStr; qDebug()<<rangStr; QAxObject *range = this->sheet->querySubObject("Range(const QString&)",rangStr); if(NULL == range || range->isNull()) { return false; } bool succ = false; QVariant var; castListListVariant2Variant(cells,var); succ = range->setProperty("Value", var); delete range; return succ; }
  • 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

此函數是把數據從A1開始寫

函數中的convertToColName為把列數,轉換為excel中用字母表示的列數,這個函數是用遞歸來實現的:

/// /// \brief 把列數轉換為excel的字母列號 /// \param data 大於0的數 /// \return 字母列號,如1->A 26->Z 27 AA /// void ExcelBase::convertToColName(int data, QString &res) { Q_ASSERT(data>0 && data<65535); int tempData = data / 26; if(tempData > 0) { int mode = data % 26; convertToColName(mode,res); convertToColName(tempData,res); } else { res=(to26AlphabetString(data)+res); } } /// /// \brief 數字轉換為26字母 /// /// 1->A 26->Z /// \param data /// \return /// QString ExcelBase::to26AlphabetString(int data) { QChar ch = data + 0x40;//A對應0x41 return QString(ch); }
  • 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

看看寫excel的耗時:

void MainWindow::on_action_write_triggered()
{
    QString xlsFile = QFileDialog::getExistingDirectory(this);
    if(xlsFile.isEmpty()) return; xlsFile += "/excelRWByCztr1988.xls"; QElapsedTimer timer; timer.start(); if(m_xls.isNull()) m_xls.reset(new ExcelBase); m_xls->create(xlsFile); qDebug()<<"create cost:"<<timer.elapsed()<<"ms";timer.restart(); QList< QList<QVariant> > m_datas; for(int i=0;i<1000;++i) { QList<QVariant> rows; for(int j=0;j<100;++j) { rows.append(i*j); } m_datas.append(rows); } m_xls->setCurrentSheet(1); timer.restart(); m_xls->writeCurrentSheet(m_datas); qDebug()<<"write cost:"<<timer.elapsed()<<"ms";timer.restart(); m_xls->save(); }
  • 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

輸出:

create cost: 814 ms 
"A1:CV1000" 
write cost: 262 ms 

寫10萬個數據耗時262ms,有木有感覺很快,很強大

結論

  • Qt在windows下讀寫excel最快速的方法還是使用QAxObject
  • 不要使用類似sheet->querySubObject("Cells(int, int)", row, col);的方式讀寫excel,這是導致低效的更本原因

源代碼

這里寫圖片描述

–> 見 github

 
 

http://blog.csdn.net/czyt1988/article/details/52121360


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM