QT中使用QAxObject读取EXCEL大量数据时速度慢的原因及解决方案

读取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文件

同理,能通过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. }

 

 

  1. void CMainWindow::openExcel(QString fileName)

  2. {

  3. QAxObject excel("Excel.Application");

  4. excel.setProperty("Visible", false);

  5. QAxObject *work_books = excel.querySubObject("WorkBooks");

  6. work_books->dynamicCall("Open(const QString&)", fileName);

  7.  
  8. QAxObject *work_book = excel.querySubObject("ActiveWorkBook");

  9. QAxObject *work_sheets = work_book->querySubObject("Sheets"); //Sheets也可换用WorkSheets

  10.  
  11. int sheet_count = work_sheets->property("Count").toInt(); //获取工作表数目

  12. if (sheet_count > 0)

  13. {

  14. QAxObject *work_sheet = work_book->querySubObject("Sheets(int)", 1);

  15.  
  16. ui.label->setText("文件数据读取中...");

  17. QVariant var = readAll(work_sheet);

  18. castVariant2ListListVariant(var);

  19. }

  20.  
  21. work_book->dynamicCall("Close(Boolean)", false); //关闭文件

  22. excel.dynamicCall("Quit(void)"); //退出

  23. }

  24.  
  25. QVariant CMainWindow::readAll(QAxObject *sheet)

  26. {

  27. QVariant var;

  28. if (sheet != NULL && !sheet->isNull())

  29. {

  30. QAxObject *usedRange = sheet->querySubObject("UsedRange");

  31. if (NULL == usedRange || usedRange->isNull())

  32. {

  33. return var;

  34. }

  35. var = usedRange->dynamicCall("Value");

  36. delete usedRange;

  37. }

  38. return var;

  39. }

  40.  
  41. void CMainWindow::castVariant2ListListVariant(const QVariant &var)

  42. {

  43. QVariantList varRows = var.toList();

  44. if (varRows.isEmpty())

  45. {

  46. return;

  47. }

  48. const int rowCount = varRows.size();

  49. QVariantList rowData;

  50. for (int i = 0; i < rowCount; ++i)

  51. {

  52. rowData = varRows[i].toList();

  53.  
  54. if (i == 0)

  55. {

  56. QStringList headers;

  57. for each (auto item in rowData)

  58. {

  59. QString value = item.toString();

  60. headers.append(value);

  61. }

  62. ui.tableWidget->setColumnCount(headers.size()); //设置列数

  63. ui.tableWidget->setHorizontalHeaderLabels(headers);

  64. }

  65. else

  66. {

  67. int row = ui.tableWidget->rowCount();

  68. ui.tableWidget->setRowCount(row + 1);

  69. for (int j = 0; j < rowData.size(); j++)

  70. {

  71. QString value = rowData[j].toString();

  72. QTableWidgetItem *item = new QTableWidgetItem(value);

  73. ui.tableWidget->setItem(row, j, item);

  74. }

  75. }

  76. }

  77. }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值