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,这是导致低效的更本原因
源代码
http://blog.csdn.net/czyt1988/article/details/52121360
Qt 下快速读写Excel指南(尘中远)的更多相关文章
- Qt下存储读写应用程序设置的三种方法
一.简介 用户对应用程序经常有这样的要求:要求它能记住它的settings,比如窗口大小.位置和密码等等.有三种方法可以实现: 使用注册表: 使用配置文件(.ini): 使用自定义文件(例如.txt) ...
- qt 使用qtxlsx 读写excel
https://github.com/dbzhang800/QtXlsxWriter 下载qtxlsx地址 QtXlsx is a library that can read and write Ex ...
- QJsonDocument实现Qt下JSON文档读写
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QJsonDocument实现Qt下JSON文档读写 本文地址:http://tech ...
- QXmlStreamReader/QXmlStreamWriter实现Qt下xml文件读写
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QXmlStreamReader/QXmlStreamWriter实现Qt下xml文件读写 ...
- 有什么很好的软件是用 Qt 编写的?(尘中远)
作者:尘中远链接:http://www.zhihu.com/question/19630324/answer/19365369来源:知乎 http://www.cnblogs.com/grandyan ...
- Linux下快速静态编译Qt以及Qt动态/静态版本共存
Qt下静态编译Qt,根据我的经验,如果按照Windows下那种直接拿官方sdk安装之后的文件来编译是行不通的,需要直接下载Qt的source包,目前诺基亚的源码叫做qt-everywhere-open ...
- Linux下快速静态编译Qt以及Qt动态/静态版本共存(提供了编译4.6,5.6的精通编译脚本,并且apt-get install 需要的库也全列出来了。还有分析问题的心理过程)good
qt4.6 Linux./configure -static -release -confirm-license -opensource -qt-zlib -qt-libpng -qt-libjpeg ...
- 【原创】.NET读写Excel工具Spire.Xls使用(1)入门介绍
在.NET平台,操作Excel文件是一个非常常用的需求,目前比较常规的方法有以下几种: 1.Office Com组件的方式:这个方式非常累人,微软的东西总是这么的复杂,使用起来可能非常不便,需要安装E ...
- .NET读写Excel工具Spire.Xls使用(1)入门介绍
原文:[原创].NET读写Excel工具Spire.Xls使用(1)入门介绍 在.NET平台,操作Excel文件是一个非常常用的需求,目前比较常规的方法有以下几种: 1.Office Com组件的方式 ...
随机推荐
- 【BZOJ 1025】[SCOI2009]游戏
[题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1025 [题意] [题解] 每一个对应关系,里面其实都会生成大小不一的几个环. 每一个环 ...
- Java实现多文件压缩打包的方法
package com.biao.test; import java.io.File; import java.io.FileInputStream; import java.io.FileOutpu ...
- laravel routes除了默认路由,其他的都无效 解决方案
按照教程.该php升级到5.5,所有是开放的扩展,默认路由进入,证明代码错误,平时不开rewrite铅 假设你其它路由,localhost/文件夹/public/index.php/home能够进去. ...
- QImage学习学习
QImage这个类之前用过,无外乎是加载一个图片文件显示出来,并没有做过多的研究,目前工作中用到了灰度图以及图片的像素操作,重新学习了下,记录记录. 一些基本操作方法 获取图像的首地址: const ...
- IO 字符流学习
import java.awt.Frame; import java.io.*; public class filewriter { /** * @param args */ public stati ...
- R 语言基本操作(基本信息的查看、与本地文件系统交互、编译器版本升级)
0. 信息及路径的查看 getwd() ⇒ 查看路径: setwd() ⇒ 设置路径: .libPaths() ⇒ R 语言库所在的位置: sessionInfo() ⇒ 查询版本及系统和库等信息: ...
- VBS学习日记(二) 基础知识
VBScript 基础知识 一.变量 1.全部单引號后面的内容都被解释为凝视.(在vbsedit中ctrl+m凝视,反凝视ctrl+shift+m) 2.在 VBScript 中,变量的命名规则遵循标 ...
- C#委托五(自定义事件)
事件: "在发生其他类或对象关注的事情时,类或对象可以通过事件通知他们.发送(或引发)事件的类称为"发行者",接受(或处理)事件的类称为"订户".&q ...
- java 压缩以及解压文件,有tar,zip,gz(gizp)和解压
package com.yabsz.decompCompr; import java.io.File; import java.util.ArrayList; import java.util.Lis ...
- Spring boot将配置属性注入到bean 专题
https://blog.csdn.net/wangmx1993328/article/details/81002901 Error starting ApplicationContext. To d ...