Qt中的QDataStream类为我们的程序提供了读写二进制数据的能力。一个数据流如果
是二进制编码的数据流,那么它肯定是与计算机的操作系统、CPU或者字节序无关的。
例如,一个数据流是在一个运行Windows系统的PC机上被写入的,那么它照样可以
在一台运行Solaris的Sun SPARC的机器上被读取出来。同样,我们也可以使用
QDataStream去读写原生的未编码的二进制数据。
QDataStream类实现了序列化C++的基本数据类型的功能,比如char,short,int,char* 等等。
如果要序列化更复杂的数据类型,可以将复杂数据类型分解成独立的基本数据类型分别进行序列化。
一个数据流往往需要一个QIODevice配合使用。因为QIODevice代表了一个可以从中读取数据或
向其写入数据的输入输出设备。我们最常常见的QFile文件类就是一种QIODevice。下面我们先
分别看一个使用QDataStream进行二进制数据读写的例子。
将二进制文件写入流:
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << QString("the answer is");
out << (qint32)42;
从流中读取二进制文件:
每一项被写入的数据,都是按一种预定义的二进制格式写入的,改格式取决于具体每一项的类型。
而QDataStream支持的类型包括QBrush,QColor,QDateTime等等。
特别注意,对应整数来说,在写入时最好转换成Qt中的某种整数类型,读取时也读取为同样的Qt整数
类型。这可以确保得到正确大小的整数并且可以屏蔽掉不同编译器和平台之间的差异。举个栗子,
对于一个char* 字符串来说,先写入一个32-bit的整数值,该值就是字符串的长度,包括'\0',紧接着
是字符串中 的每一个字符,包括'\0'。当读取时,也是这样操作,写读取4字节创建出一个32-bit的字
符串长度值,然后再根据创建出的长度值读取出相应个数的字符,包括'\0'。
QDataStream的二进制格式从Qt1.0就开始形成了,很有可能在将来继续进化已反应Qt的变化。当操作复杂数据类型时,我们就要确保读取和写入时的QDataStream版本是一样的。如果你需要向前和向后兼容,可以在代码中使用硬编码指定流的版本号:
stream.setVersion(QDataStream::Qt_4_0);
如果你正在创建一种新的二进制数据格式,比如作为你的应用程序创建的文件的格式,你可以使用QDataStream以一种可移植的格式去写入这些数据。典型情况下,你可能会在文件头写入一个简短的幻数字符串和一个版本数字,来用于将来扩展。例如:
QFile file("file.xxx");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << (qint32)123;
out << (quint32)0xA0B0C0D0;
out << lots_of_interesting_data;
out.setVersion(QDataStream::Qt_4_0);
那么,我们就可以以下面这种方式来读取:
读写原生二进制数据
有时,我们希望直接从data stream里读取原生的二进制数据。此时,可以使用readRawData() 将数据读入一个预先分配好的char*;同样的数据也可以使用writeRawData() 函数写入data stream。但要记住,使用这种方式的话,要由你自己进行所有数据的编码和解码。于此类似的另外两个函数是readBytes() 和 writeBytes()。这两个函数与上面的Raw版本相比,区别主要是:readBytes() 先读取一个quint32值,该值被当做将要读取的数据的长度,然后读取相应字节的数据到预先定义好的char*中;writeBytes() 也同理,先写入一个quint32的数据长度值,紧接着写入相应数据。同样,使用这两个函数,所以数据的编码和解码要有我们自己负责。注意,readBytes() 不需要我们事先分配好内存, 而readRawData() 需要我们事先分配好内存。
读写Qt集合类和其他Qt类
Qt的常见集合类也可以使用QDataStream进行序列化,这包括QList,QLinkedList,QVector,QSet,QHash和QMap。不过,序列化这些类的流操作符不是这个类的成员函数而已。同理,读写Qt中的其他类型,比如QImage,也是可以的。
使用事务
当在一个异步的设备上读取数据时,数据块可以在任意的时间点上到来。所以,为了应对这种情况,QDataStream提供了一个事务机制来确保原子性的完成一系列的流操作符。例如,你可以在操作socket时,在相应readyRead() 的槽函数中,使用事务来完成不完整的数据读取。
in.startTransaction();
QString str;
in >> str >> a;
qint32 a;
if (!in.commitTransaction())
return;
如果没有完整的数据包到来,commitTransaction() 会返回false,并将stream重置为初始状态,然后,等待更多数据的到来。
下面给出一个简单的测试程序:
#include <QCoreApplication>