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

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

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

总结:

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


原文地址:https://www.cnblogs.com/lcchuguo/p/5134980.html