【大话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编译器中,參数是由右向左入栈的,然后是函数中的局部变量。注意:静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是參数,最后栈顶指针指向最開始存的地址。也就是主函数中的下一条指定。程序由该点继续执行。

堆:通常是在堆的头部用一个字节存放堆的大小。

堆中的详细内容由程序猿安排。

总结:

通过这块内容的分析,对影响程序运行性能的几个方面有了更清除的了解,对进程的堆栈也有了更深入的了解。感觉自己開始慢慢关注那些实质性的东西。这点我感觉是非常好的,加油,对每一个不懂的问题都要认真总结 。

时间: 2024-10-03 22:32:30

【大话QT之七】QT序列化操作的相关文章

C++/Qt 序列化操作

序列化操作的含义顾名思义,即按一定的次序将数据保存到存储设备中,或者从存储设备中读取一定次序的数据,且数据类型要保持一致(即读出的数据是我知道的类型).过程知道了,那怎么做呢? 我是借助Qt框架实现的一个简单的程序.(当然框架把复杂的部分给实现了.如果在你封装的数据中,意思是非Qt标准的数据类型,那么有必要实现"<<" 和 ">>" 运算子来实现自定义类型的序列化操作). 具体的实现过程如下: //order.h #include <Q

Qt中QUdpSocket序列化问题

写了一个小的Qt网络程序,很简单,发送的网络消息除了字符串还有一个结构体.很简单的想到用memcpy()函数来将数据序列化为BYTE数组从而实现网络传输. 序列化是Java中一个概念,C中并没有,C++中后来引入了序列化和反序列化的概念.序列化是指将非系统类型的类的对象通过序列化操作转换成基本数据格式,从而达到便于网络传输或者文件读写的目的.反序列化是序列化的逆操作. 注:C/C++中BYTE与Char在内存中数据一样,形式相同,在Windef.h中有一行: typedef unsigned c

QT SQLite 多数据库操作大全

QT SQLite 多数据库操作大全 一.单数据库模型 一般QT都是把打开一个缺省数据库连接,操作一个数据库连接,但是对Sqlite中频率修改容易加锁,因此有一种设计模式是把频率修改的表放在不同的数据库文件中,但这样要修改代码操作数据库部分 通常打开代码 http://developer.nokia.com/community/wiki/Creating_an_SQLite_database_in_Qt bool DatabaseManager::openDB() { // Find QSLit

第三十二课、Qt中的文件操作

一.Qt中的IO操作 1.Qt中IO操作的处理方式 (1).Qt通过统一的接口简化了文件与外部设备的操作方式 (2).Qt中的文件被看做是一种特殊的外部设备 (3).Qt中的文件操作与外部设备操作相同 2.IO操作中的关键函数接口 注意:IO操作的本质:连续存储空间的数据读写 3.Qt中IO设备类型 (1).顺序存储设备:只能从头开始顺序读写数据,不能指定数据的读写位置(串口) (2).随机存储设备:可以定位到任意位置进行数据读写(seek function函数)(文件) 4.Qt中IO设备 的

第32课 Qt中的文件操作

1. Qt的中IO操作 (1)Qt中IO操作的处理方式 ①Qt通过统一的接口简化了文件和外部设备的操作方式 ②Qt中的文件被看作一种特殊的外部设备 ③Qt中的文件操作与外部设备的操作相同 (2)IO操作中的关键函数接口——IO操作的本质:连续存储空间的数据读写 ①打开设备:bool open(OpenMode mode); ②读取数据:QByteArray read(qint64 maxSize); ③写入数据:qint64 write(const QByteArray& byteArray);

Qt--软件开发流程简介、Qt中的文件操作

一.软件开发流程 Q.什么是软件开发流程?1.通过一系列步骤保证软件的顺利完成2.软件产品在生命期内的管理方法学软件开发流程的本质1.开发流程与具体技术无关2.开发流程是开发团队必须遵守的规则常见的开发流程--即兴.瀑布.增量.螺旋.敏捷等A.即兴模型1.与最终用户交流后立即开始进行开发2.没有需求分析和需求发掘的过程3.没有整体设计以及规划的过程4.没有相关软件文档,维护性差B.瀑布模型1.通过自上而下的步骤进行软件开发2.每个开发步骤都是不可逆的C.增量模型1.将系统能分解为互不重叠的子功能

Qt 与 Qt Creator 简介

该文章原创于Qter开源社区(www.qter.org),作者 yafeilinux,转载请注明出处! 一.Qt 与 Qt Creator 简介 Qt 是一个跨平台应用程序和 UI 开发框架.使用 Qt 您只需一次性开发应用程序,无须重新编写源代码,便可跨不同桌面和嵌入式操作系统部署这些应用程序. Qt Creator 是全新的跨平台 QtIDE(集成开发环境),可单独使用,也可与 Qt 库和开发工具组成一套完整的 SDK(软件开发工具包). 其中包括:高级 C++ 代码编辑器,项目和生成管理工

Qt图形库——Qt画图板

Qt图形库--Qt画图板 完整代码: //mainwindows.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QColorDialog> #include <QInputDialog> #include <QActionGroup> #include <QFileDialog> #include <QMainWindow> #include <QKeyEvent> #in

Qt高级——Qt信号槽机制源码解析

Qt高级--Qt信号槽机制源码解析 基于Qt4.8.6版本 一.信号槽机制的原理 1.信号槽简介 信号槽是观察者模式的一种实现,特性如下:A.一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知:B.一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候--也可以说是信号发出的时候--被调用的函数:C.信号与槽的连接,形成一种观察者-被观察者的关系:D.当事件或者状态发生改变的时候,信号就会被发出:同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽).信号和

Qt:QT右键菜单

Qt QTableView 上加右键弹出菜单, 并复制选中的单元格内容到剪贴板中 http://wenku.baidu.com/view/c51cfb63cf84b9d528ea7a29.html http://www.cppblog.com/biao/archive/2010/01/01/104593.html QWidget及其子类都可有右键菜单,因为QWidget有以下两个与右键菜单有关的函数: Qt::ContextMenuPolicy contextMenuPolicy () cons