【大话QT之七】QT序列化操作
应用需求:
在网盘开发过程中有这样一个需求。即对文件版本号进行控制,即记录文件版本号的更替信息,这里说的更替信息不过记录不同一时候刻的文件变化,即文件的增、删、改、重命名等操作。在每一个待监控的文件夹下都会保存一份文件。记录文件变化的增量信息。每次低版本号到高版本号升级的时候就能够通过消元合并操作高速地进行。关于文件版本号控制的详细实现方案会在开发完好后列出。这里只指出它的保存方式,即将文件操作的实例对象序列化后保存在文件里。
序列化的实现:
这里我们採用QDataStream来实现序列化,QT针对不同的实例化对象有不同的要求。这里主要分两类。即:QT中原生的数据类型。比如:QString、QMap、QHash等,这对这样的原生数据类型的序列化。我们不须要做其他额外的操作,直接就能够序列化到文件里。另一类特殊的就是我们自定义的数据结构或类,这样的方式利用QDataStream不能直接实现序列化,我们必须重载<<和>>操作符,仅仅有重载完之后才干够按我们的要求实现序列化。以下就举例来说明一下。我们自定义的数据结构或类应该怎样实现序列化:
自己定义的类:LHTFileVersionItem,该类用来记录某一个操作。它的定义为:
#ifndef LHT_FILEVERSIONITEM_H
#define LHT_FILEVERSIONITEM_H #include <QDataStream> struct FileVersionItem
{
QString m_sFileAbsolutePath ; QString m_sFileOrgName ;
QString m_sFileNowName ; int m_sFileType ; QString m_sFileMoveFromAbsolutePath ;
QString m_sFileMoveToAbsolutePath ;
}; class LHTFileVersionItem
{
public:
LHTFileVersionItem();
~LHTFileVersionItem(); void setVersion(int version);
void setOp(int op); int GetVersion();
int GetOp();
FileVersionItem* GetFileVersionPointer(); friend QDataStream &operator<<(QDataStream & , const LHTFileVersionItem &);
friend QDataStream &operator>>(QDataStream & , LHTFileVersionItem &); private:
int m_iVersion; //!-1:delete 1:crate 2:change
//!3:rename 4:move
int m_iOp;
FileVersionItem *m_hFileVersionPointer ;
}; #endif // LHT_FILEVERSIONITEM_H
当中,friend QDataStream &operator << 和 friend QDataStream &operator >>就是声明的对操作符的重载。注意:这里我们使用了friend来声明为友元函数。这里对friendkeyword做些介绍:
何谓友元?为什么要使用友元?我们知道,採用类的机制后实现了数据的隐藏和封装。类的数据成员一般定义为私有成员。成员函数一般定义为公有的。依此提供类与外界间的訪问接口。可是,有时须要定义一些函数,注意:这些函数并非类的一部分(因此在cpp文件里实现该函数时。函数前不须要使用。类名::函数名的方式),但又须要频繁地訪问类的私有数据成员,这是能够将这些函数定义为友元函数。除了友元函数外还有友元类,两者统称为友元。友元的作用是提高了程序的执行效率(即:降低了类型检查和安全性检查等,这些操作都须要时间开销),可是它同一时候也破坏了类的封装性和隐蔽性,使得非成员函数能够訪问类的私有成员。
事实上,这里理解友元关注friend就能够了,friend代指“朋友”、“关系友好”的意思。仅仅有两者(函数与类友好形成友元函数。类与类友好形成友元类)关系友好,我才同意它訪问我的私有成员。
自己定义的类:LHTFileVersionItem的实现:
#include "lht_fileversionitem.h" LHTFileVersionItem::LHTFileVersionItem()
{
m_hFileVersionPointer = new FileVersionItem();
} LHTFileVersionItem::~LHTFileVersionItem()
{
} void LHTFileVersionItem::setVersion(int version)
{
this->m_iVersion = version ;
} void LHTFileVersionItem::setOp(int op)
{
this->m_iOp = op ;
} int LHTFileVersionItem::GetVersion()
{
return this->m_iVersion ;
} int LHTFileVersionItem::GetOp()
{
return this->m_iOp ;
} FileVersionItem* LHTFileVersionItem::GetFileVersionPointer()
{
return this->m_hFileVersionPointer;
} //! 重载操作符<<的实现
QDataStream &operator<<(QDataStream &output , const LHTFileVersionItem & item)
{
output << item.m_iVersion << item.m_iOp << item.m_hFileVersionPointer->m_sFileAbsolutePath << \
item.m_hFileVersionPointer->m_sFileMoveFromAbsolutePath << item.m_hFileVersionPointer->m_sFileMoveToAbsolutePath << \
item.m_hFileVersionPointer->m_sFileNowName << item.m_hFileVersionPointer->m_sFileOrgName ;
return output ;
} //! 重载操作符>>的实现
QDataStream &operator>>(QDataStream & input, LHTFileVersionItem & item)
{
input >> item.m_iVersion >> item.m_iOp >> item.m_hFileVersionPointer->m_sFileAbsolutePath >> \
item.m_hFileVersionPointer->m_sFileMoveFromAbsolutePath >> item.m_hFileVersionPointer->m_sFileMoveToAbsolutePath >> \
item.m_hFileVersionPointer->m_sFileNowName >> item.m_hFileVersionPointer->m_sFileOrgName ;
return input ;
}
继承自Object后会出现的问题:
假设我们自己定义的类继承自QObject,在使用时可能会出现这种编译错误:error C2248 'QObject::QObject' : cannot access private member declared in class 'QObject'例如以下所看到的:
为什么加了继承自QObject就会出现这样的问题呢,肯定是QObject的问题,查看源代码中在private中有这样一句:
private:
Q_DISABLE_COPY(QObject)
Q_PRIVATE_SLOT(d_func(), void _q_reregisterTimers(void *)) /*
Some classes do not permit copies to be made of an object. These
classes contains a private copy constructor and assignment
operator to disable copying (the compiler gives an error message).
*/
#define Q_DISABLE_COPY(Class) \
Class(const Class &); \
Class &operator=(const Class &);
从上面的凝视和实现能够看出继承自QObject后它不同意对象的赋值操作,即=。我查找我全部调用的函数里面没有看到直接赋值的操作啊,那为什么会出现这种问题呢?原因在于在函数调用时形參的传递也会被觉得是赋值操作。因此,出现这样问题,直接将QObject的继承去掉就能够,我们没有使用到QObject独特的特定。
局部变量使用对性能的影响以及进程的堆和栈:
因为在代码中我使用了QMulitHash<QString , LHFilteVersionItem> tmp;这一局部变量来保存某一文件夹下的文件。因为在写測试代码期间,我利用循环模拟了50万的数据序列化后保存在文件里,在执行期间我发现读取函数耗费非常长的时间。而函数里面最耗时的读取操作也仅仅花费了非常短的时间,可是函数一直无法马上退出,在等待了大约30s后才干退出,相关代码例如以下:
void LHTWORKFLOW::ReadAllDataFromFile(QMultiHash<QString, LHTFILEITEM> &m_hFileItemInfo)
{
if (NULL == m_fFileInfoHandle)
{
OpenFile(m_sFileItemInfoAbsolutePath , 0);
} m_fFileInfoHandle->seek(0); QDataStream input(m_fFileInfoHandle); QMultiHash<QString, LHTFILEITEM> final;
while (!input.atEnd())
{
QMultiHash<QString, LHTFILEITEM> tmp ;
input >> tmp ;
final += tmp ;
} m_hFileItemInfo = final ;
CloseFile(m_fFileInfoHandle);
}
经过细致分析和思考,发现问题就出在局部变量final上,因为是局部变量,因此当函数运行完成后局部变量就要销毁,因为是QMultiHash类型的变量,我们知道Hash相比数组来说它的一大优点是数据地址不连续。元素在内存控件中占用的内存地址是不连续的,而数据量又大。因此在销毁的过程中应该是逐步遍历去释放内存指针去了。假设是数组这样的连续的数据结构的话。释放会非常快,仅仅须要把该块内存的标志设为无用它就又能够被系统回收利用了。这样的情况还是我第一次碰到。感到非常有意思。对我以后的代码编写也有一定的知道意义。于是我就查阅了进行相关堆和栈的相关内容。
下面内容的參考链接:http://blog.csdn.net/hairetz/article/details/4141043 。为了加深理解我这里再列出一点吧。
1> 预备知识—程序的内存分配
一个由C++编译的程序占用的内存分为一下几个部分:
1) 栈区(stack)— 由编译器自己主动分配释放。存放函数的參数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2) 堆区(heap)— 一般由程序猿分配释放,若程序猿不释放,程序结束时可能又操作系统回收。
3) 全局区(静态区)(static) — 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域。未初始化的全局变量和未初始化的静态变量在相邻的还有一块区域。程序结束和由系统释放。
4) 文字常量区— 常量字符串就是放在这里的。程序结束后由系统释放。
5) 程序代码区— 存放函数体的二进制代码。
2> 堆和栈的对照
1) 申请方式
栈(stack)由系统自己主动分配。比如。声明在函数中的一个局部变量 int b ; 系统自己主动在栈中为b开辟空间。
堆(heap)须要程序猿自己申请。并指明大小,在c中malloc函数如:p1 = (char *)malloc(10) ; 在C++中用new运算符如:p2 = new char[10];
但注意:p1、p2本身是在栈中的,仅仅是通过malloc和new分配的空间是在堆中的。
3> 申请后的系统响应
栈(stack):仅仅要栈的剩余空间大于所申请的空间,系统将为程序提供内存。否则将报异常提示栈溢出。
堆(heap):首先应该知道操作系统有一个记录空暇内存地址的链表。当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空暇节点链表中删除,并将该节点的空间分配给程序,另外。对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小。这样代码中的delete语句才干正确的释放本内存空间。
另外,因为找到堆节点的大小不一定正好等于申请的大小,系统会自己主动的将多余的那部分又一次放入空暇链表中。
4> 申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶和栈的最大容量是系统预先规定好的。在windows下。栈的大小是2M,假设申请的空间超过栈的剩余空间时,将提示溢出。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是因为系统使用链表连存储的空暇内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比較灵活,也比較大。
5> 申请效率的比較
栈:由系统自己主动分配。速度较快。但程序猿是无法控制的。
堆:是由new分配的内存,最好的方式是用VirtualAlloc分配虚拟内存,它既不是在堆也不是在栈,而是直接在进程的地址空间中保留一块内存。尽管用起来最不方便。可是速度快也最灵活。
6> 堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个參数。在大多数的C编译器中,參数是由右向左入栈的,然后是函数中的局部变量。注意:静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是參数,最后栈顶指针指向最開始存的地址。也就是主函数中的下一条指定。程序由该点继续执行。
堆:通常是在堆的头部用一个字节存放堆的大小。
堆中的详细内容由程序猿安排。
总结:
通过这块内容的分析,对影响程序运行性能的几个方面有了更清除的了解,对进程的堆栈也有了更深入的了解。感觉自己開始慢慢关注那些实质性的东西。这点我感觉是非常好的,加油,对每一个不懂的问题都要认真总结 。
【大话QT之七】QT序列化操作的更多相关文章
- Qt中QUdpSocket序列化问题
写了一个小的Qt网络程序,很简单,发送的网络消息除了字符串还有一个结构体.很简单的想到用memcpy()函数来将数据序列化为BYTE数组从而实现网络传输. 序列化是Java中一个概念,C中并没有,C+ ...
- 第32课 Qt中的文件操作
1. Qt的中IO操作 (1)Qt中IO操作的处理方式 ①Qt通过统一的接口简化了文件和外部设备的操作方式 ②Qt中的文件被看作一种特殊的外部设备 ③Qt中的文件操作与外部设备的操作相同 (2)IO操 ...
- 【QT】文件读写操作
读取输出: QFile file("D:/Englishpath/QTprojects/1.dat"); if(!file.open(QIODevice::ReadOnly)) { ...
- Qt数据库 QSqlTableModel实例操作(转)
本文介绍的是Qt数据库 QSqlTableModel实例操作,详细操作请先来看内容.与上篇内容衔接着,不顾本文也有关于上篇内容的链接. Qt数据库 QSqlTableModel实例操作是本文所介绍的内 ...
- C/C++ Qt TreeWidget 嵌套节点操作技巧
在上一篇博文<C/C++ Qt TreeWidget 单层树形组件应用>中给大家演示了如何使用TreeWidget组件创建单层树形结构,并给这个树形组件增加了右键菜单功能,接下来将继续延申 ...
- 3.Qt GUI中一些操作记录
一.如何在Widget中利用代码添加背景图片 this->setAutoFillBackground(true); // QPalette palette = this->palette( ...
- Qt Table Widget常用操作
一.鼠标悬浮在item上 显示提示信息 1.在构造函数开启table Widget控件的鼠标捕获功能 // 开启鼠标捕获功能(实现table widget的悬浮功能) ui.tableWidget-& ...
- 【Qt】Qt之自定义界面(QMessageBox)【转】
简述 通过前几节的自定义窗体的学习,我们可以很容易的写出一套属于自己风格的界面框架,通用于各种窗体,比如:QWidget.QDialog.QMainWindow. 大多数窗体的实现都是采用控件堆积来完 ...
- 【Qt】Qt Assistant介绍【转】
简介 Qt Assistant也就是我们常说的Qt助手,是一款用于呈现在线文档的工具. 简介 一分钟学会使用 Qt参考文档 Qt Assistant详解 命令行选项 工具窗口 文档窗口 工具栏 菜单 ...
- 【Qt】Qt Creator介绍【转】
简介 Qt Creator是使用Qt开发的IDE.Qt支持Windows.Linux/Unix.Mac OS X.Android.BlackBerry.QNX等多种平台,Qt Creator为不同平台 ...
随机推荐
- c++ 重载,覆盖,重定义
写的不是很明白,后来又重新整理过了,在: http://www.cnblogs.com/iois/p/4986790.html 函数重载(Function Overloading) C++允许同一范围 ...
- Python 文本解析器
Python 文本解析器 一.课程介绍 本课程讲解一个使用 Python 来解析纯文本生成一个 HTML 页面的小程序. 二.相关技术 Python:一种面向对象.解释型计算机程序设计语言,用它可以做 ...
- 支付平台程序,支付程序,网络pos程序,api接口程序,锋锐支付平台程序开发领导者!
支付平台程序,支付程序,网络pos程序,api接口程序,锋锐支付平台程序开发领导者! 锋锐支付平台程序(www.100freenet.com)隶属于盐城市沐良商贸有限公司(沈阳杰速网络科技有限公司旗下 ...
- CentOS桌面环境如何打开终端以及如何将终端加入右键
安装完CentOS的桌面环境后,默认在桌面以及右键是没有打开终端选项的,要想打开终端,可以由以下步骤: 在左上角菜单[Applications]--->[System Tools]---> ...
- 基于visual Studio2013解决C语言竞赛题之1039移动
题目 解决代码及点评 /* 39. 有n个整数,编程序将前面的各个数依次向后移动k个位置, 最后k个数移到最前边的k个位置(见下图,其中n=8,k=3). */ # ...
- Java 异常解决之java.lang.IllegalArgumentException: Comparison method violates its general contract!
Java 异常解决 在你的代码前加一句 System.setProperty("java.util.Arrays.useLegacyMergeSort", "true&q ...
- [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性
依据<Java并发编程实践>一书整理的思维导图.
- ASP.NET - (Session)后台登陆时,判断是不是已经登陆,如果不是,跳转回登陆页
admin(小写):用户输入的账户. password(小写):用户输入的密码. 1.先将用户名和密码,存储到Session会话中. Session["Admin"] = admi ...
- 基于visual Studio2013解决面试题之0807strstr函数
题目
- 浅析嵌入式Linux系统的构成和启动过程
在我们的周围,大量的嵌入式设备都是基于Linux系统来构建的,嵌入式Linux与主机Linux相比有着自己的一些特点,本文就嵌入式Linux系统的构成和启动过程做一些总结. 一.嵌入式Linux系统构 ...