Qt 快速读写Excel指南

news2024/12/27 11:53:07

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

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

快速读取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);的方式读写excel,这是导致低效的更本原因

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络攻击损失不容小觑

据研究数据显示&#xff0c;2022年数据泄露给企业造成的平均损失为435万美元&#xff1b;如果是勒索软件攻击&#xff0c;损失将增加到454万美元。当然这只是估计&#xff0c;在某些国家或地区平均损失更高&#xff0c;比如在美国&#xff0c;损失接近1000万美元。 此外&#x…

如何制作并使用动静态库

制作并使用动静态库&#x1f3de;️1. 动静态库概念&#x1f301;2. 如何制作动静态库&#x1f4d6;2.1 制作静态库&#x1f4d6;2.2 制作动态库&#x1f320;3. 如何使用动静态库&#x1f4d6;3.1 使用静态库&#x1f4d6;3.2 使用动态库&#x1f341;4. 动态库的加载原理&…

Mybatis-plus实战项目演示+自定义元数据对象处理器+ws

目录 一&#xff0c;mybatis-plus实战一 1.导入依赖 2.application.yml配置 3.实体类 4.mapper 5.service层 6.启动类上配置 二&#xff0c;mybatis-plus实战二 1.导入依赖 2.application.yml配置 3.实体类 2.mapper 3.service 4.分页配置 5.放一个在controller层…

jeecg-boot中实现跳过登录验证访问其他vue页面

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff0c;雄雄的小课堂。 前言 大家都知道&#xff0c;我们正常的项目&#xff0c;都做了访问限制&#xff0c;也就是&#xff0c;只有当用户登录本平台后&#xff0c;才能让用户访问别的页面&#xff0c;否则默认…

【Vue】Vue中mixins的使用方法及实际项目应用详解

文章目录&#xff08;1&#xff09;mixin基础&#xff08;2&#xff09;mixin特点1.选项合并2.方法和参数在各组件中不共享&#xff08;3&#xff09;mixin与vuex的区别&#xff08;4&#xff09;mixin与公共组件的区别&#xff08;5&#xff09;项目实践&#xff08;6&#xf…

Session | 基于Session改造oa项目的登录功能

目录 一&#xff1a;总结域对象 二&#xff1a;基于Session改造oa项目的登录功能 三&#xff1a;oa项目的安全退出系统 一&#xff1a;总结域对象 &#xff08;1&#xff09;request&#xff08;对应的类名&#xff1a;HttpServletRequest&#xff09; 请求域&#xff08;请…

【机器学习】机器学习30个笔试题

机器学习试题 在回归模型中&#xff0c;下列哪一项在权衡欠拟合&#xff08;under-fitting&#xff09;和过拟合&#xff08;over-fitting&#xff09;中影响最大&#xff1f;&#xff08;A&#xff09; A. 多项式阶数 B. 更新权重 w 时&#xff0c;使用的是矩阵求逆还是梯度下…

几款超好用的内网穿透神器,总有一款适合你!

本文以渗透的视角&#xff0c;总结几种个人常用的内网穿透&#xff0c;内网代理工具&#xff0c;介绍其简单原理和使用方法。 0x01 nps-npc 1.1 简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协…

前端js长整型精度丢失处理

一、现象 在分页查询时&#xff0c;服务端会将返回的对象进行json序列化&#xff0c;转换为json格式的数据&#xff0c;而用户的ID是一个Long类型的数据&#xff0c;而且是一个长度为 19 位的长整型数据&#xff0c; 该数据返回给前端是没有问题的。 问题实际上&#xff0c; …

适用于Unity的 Google Cardboard XR Plugin快速入门

本指南向您展示如何使用 Google Cardboard XR Plugin for Unity 创建您自己的虚拟现实 (VR) 体验。 您可以使用 Cardboard SDK 将移动设备变成 VR 平台。移动设备可以显示具有立体渲染的 3D 场景&#xff0c;跟踪头部运动并对其做出反应&#xff0c;并通过检测用户何时按下查看…

MySQL篇【5】——约束

目录表的约束空属性默认值列描述zerofill主键自增长唯一键外键如何理解外键约束在数据库的实际操作中难免会有一些操作不规范的地方&#xff0c;比方说在一张学生成绩表中&#xff0c;成绩的最高值为100&#xff0c;那么如果在这张表中如果出现了超过100分的成绩那显然是不合理…

单片机定时器

定时器 STM32有11个定时器&#xff0c;2个高级定时器、4个通用定时器&#xff0c;2个基本定时器&#xff0c;1个系统嘀嗒定时器&#xff08;systick&#xff09;&#xff0c;2个看门狗定时器。 定时器计算&#xff1a; Tout((per)psc)/Tclk t&#xff08;f/pre&#xff09;per…

Spire.Office for Java 7.12.2 迎接新年/圣诞!!!

谷歌找破解版Spire.Office for Java is a combination of Enterprise-Level Office Java APIs offered by E-iceblue. It includes Spire.Doc for Java, Spire.XLS for Java, Spire.Presentation for Java, Spire.PDF for Java and Spire.Barcode for Java.内容及配图来自网络 …

cv2.findContours() 图像的轮廓

cv2.findContours(image, mode, method, contoursNone, hierarchyNone, offsetNone)参数&#xff1a; image&#xff1a;寻找轮廓的图像&#xff0c;注意输入的图片必须为二值图片。若输入的图片为彩色图片&#xff0c;必须先进行灰度化和二值化 mode&#xff1a;轮廓的检索模…

高分子PEG: mPEG-Phosphate MV 1K 2K 3.4K 5K 10K 20K 特点分享

【中文名称】甲氧基-聚乙二醇-磷酸盐 【英文名称】 mPEG-Phosphate 【结 构 式】 【CAS号】N/A 【分子式】N/A 【MV】1000、2000、3400、5000、10000、20000 【基团部分】Phosphate PEG&#xff0c;磷酸盐PEG 【纯度标准】95% 【包装规格】1g&#xff0c;5g&#xff0c;10g&…

java基于Springboot的影视管理系统-计算机毕业设计

项目介绍 影城管理系统的主要使用者分为管理员和用户&#xff0c;实现功能包括管理员&#xff1a;首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理&#xff0c;用户前台&#xff1a;首页、电影信息、电影资讯、个人中心…

acwing-Django项目——前期工作+前端js css

文章目录1.租服务器配置环境 配置docker环境创建工作用户ljh并赋予sudo权限配置免密登录方式给server1装环境装docker将AC Terminal中的/var/lib/acwing/docker/images/docker_lesson_1_0.tar镜像上传到租好的服务器中将镜像加载到本地配置docker环境创建项目配置git运行一下dj…

C++中前置操作性能一定优于后置操作?

后置操作和前置操作&#xff0c;一个会产生临时变量&#xff0c;一个不会产生临时变量&#xff0c;其原因是&#xff1a;前置操作遵循的规则是change-then-use&#xff0c;而后置操作遵循的规则是use-then-change。正因为后置操作的use-then-change原则&#xff0c;使得编译器在…

Python如何pip批量安装指定包 - 最简单方法

文章目录背景解决办法1. 制作requirements.txt文件2. 将requirements.txt传到需要部署的电脑上3. 批量安装包背景 有很多台服务器需要配置, 简单说也就是公司给我配备了3台Windows, 我需要配置Python环境并安装7个包, 如果按照常规的pip install我至少得安装3x721次, 并且得一…

data shift--学习笔记

一般假设训练集和测试集是独立同分布的&#xff0c;才能保证在训练集上表现良好的模型同样适用于测试集。当训练集和测试集不同分布时&#xff0c;就发生了dataset shiftdata shift类型&#xff1a; 协变量偏移&#xff08;covariate shift&#xff09;&#xff1a; 协变量&…