1. 程式人生 > >Qt Windows 下快速讀寫Excel指南

Qt Windows 下快速讀寫Excel指南

另一篇文章:Qt中使用QAxObject操作Excel

 

 

轉載:https://blog.csdn.net/czyt1988/article/details/52121360

原始檔:github 或者本地資料夾為名:czyBlog-master.zip .

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;
}

讀取慢的根源就在於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;
}

程式碼中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);
    }
}

這樣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();
}

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

QScopedPointer<ExcelBase> m_xls;
QList< QList<QVariant> > m_datas;

讀取的耗時:

"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;
}

此函式是把資料從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);
}

看看寫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();
}

輸出:

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

--------------------- 作者:塵中遠 來源:CSDN 原文:https://blog.csdn.net/czyt1988/article/details/52121360?utm_source=copy 版權宣告:本文為博主原創文章,轉載請附上博文連結!