Qt中的QDataStream類爲我們的程序提供了讀寫二進制數據的能力。一個數據流如果是二進制編碼的數據流,那麼它肯定是與計算機的操作系統、CPU或者字節序無關的。例如,一個數據流是在一個運行Windows系統的PC機上被寫入的,那麼它照樣可以在一臺運行Solaris的Sun SPARC的機器上被讀取出來。同樣,我們也可以使用QDataStream去讀寫原生的未編碼的二進制數據。
QDataStream類實現了序列化C++的基本數據類型的功能,比如char,short,int,char* 等等。如果要序列化更復雜的數據類型,可以將複雜數據類型分解成獨立的基本數據類型分別進行序列化。
一個數據流往往需要一個QIODevice配合使用。因爲QIODevice代表了一個可以從中讀取數據或向其寫入數據的輸入輸出設備。我們最常常見的QFile文件類就是一種QIODevice。下面我們先分別看一個使用QDataStream進行二進制數據讀寫的例子。
write binary data to a stream:
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); // we will serialize the data into the file
out << QString("the answer is"); // serialize a string
out << (qint32)42; // serialize an integer
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); // read the data serialized from the file
QString str;
qint32 a;
in >> str >> a; // extract "the answer is" and 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);
QFile file("file.xxx");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
// Write a header with a "magic number" and a version
out << (quint32)0xA0B0C0D0;
out << (qint32)123;
out.setVersion(QDataStream::Qt_4_0);
// Write the data
out << lots_of_interesting_data;
那麼,我們就可以以下面這種方式來讀取:
QFile file("file.xxx");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);
// Read and check the header
quint32 magic;
in >> magic;
if (magic != 0xA0B0C0D0)
return XXX_BAD_FILE_FORMAT;
// Read the version
qint32 version;
in >> version;
if (version < 100)
return XXX_BAD_FILE_TOO_OLD;
if (version > 123)
return XXX_BAD_FILE_TOO_NEW;
if (version <= 110)
in.setVersion(QDataStream::Qt_3_2);
else
in.setVersion(QDataStream::Qt_4_0);
// Read the data
in >> lots_of_interesting_data;
if (version >= 120)
in >> data_new_in_XXX_version_1_2;
in >> other_interesting_data;
讀寫原生二進制數據
有時,我們希望直接從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;
qint32 a;
in >> str >> a; // try to read packet atomically
if (!in.commitTransaction())
return; // wait for more data
如果沒有完整的數據包到來,commitTransaction() 會返回false,並將stream重置爲初始狀態,然後,等待更多數據的到來。
下面給出一個簡單的測試程序:
#include <QCoreApplication>
#include <QDebug>
#include <QDataStream>
#include <QFile>
#include <QVector>
#include <QMap>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//write
QFile file("test.dat");
if (!file.open(QIODevice::ReadWrite))
{
qDebug() << "open file failed";
return 0;
}
QDataStream ds(&file);
const char *wstr = "hello-world";
quint32 wi = 1234;
double wd = 1.1;
float wf = 2.2f;
QVector<int> wvector;
wvector.push_back(1);
wvector.push_back(2);
wvector.push_back(3);
QMap<int,int> wmap;
wmap.insert(4, 4);
wmap.insert(5, 5);
wmap.insert(6, 6);
ds << wstr;
ds << wi;
ds << wd;
ds << wf;
ds << wvector;
ds << wmap;
ds.writeBytes("file end ", qstrlen("file end "));
ds.writeRawData("really end", qstrlen("really end"));
//read
file.seek(0);
char *rstr;
quint32 ri;
double rd;
float rf;
QVector<int> rvector;
QMap<int, int> rmap;
char *rbytes;
uint len;
char *rraw = new char[100]{0};
int rlen;
ds >> rstr;
ds >> ri;
ds >> rd;
ds >> rf;
ds >> rvector;
ds >> rmap;
ds.readBytes(rbytes, len);
ds.readRawData(rraw, rlen);
qDebug() << rstr;
qDebug() << ri;
qDebug() << rd;
qDebug() << rf;
qDebug() << rvector;
qDebug() << rmap;
qDebug() << rbytes;
qDebug() << rraw;
return a.exec();
}