QDataStream 類
Qt中的QDataStream類為我們的程序提供了讀寫二進制數據的能力。一個數據流如果
是二進制編碼的數據流,那么它肯定是與計算機的操作系統、CPU或者字節序無關的。
例如,一個數據流是在一個運行Windows系統的PC機上被寫入的,那么它照樣可以
在一台運行Solaris的Sun SPARC的機器上被讀取出來。同樣,我們也可以使用
QDataStream去讀寫原生的未編碼的二進制數據。
QDataStream類實現了序列化C++的基本數據類型的功能,比如char,short,int,char* 等等。
如果要序列化更復雜的數據類型,可以將復雜數據類型分解成獨立的基本數據類型分別進行序列化。
一個數據流往往需要一個QIODevice配合使用。因為QIODevice代表了一個可以從中讀取數據或
向其寫入數據的輸入輸出設備。我們最常常見的QFile文件類就是一種QIODevice。下面我們先
分別看一個使用QDataStream進行二進制數據讀寫的例子。
將二進制文件寫入流:
1 QFile file("file.dat"); 2 file.open(QIODevice::WriteOnly); 3 QDataStream out(&file); 4 out << QString("the answer is"); 5 out << (qint32)42;
從流中讀取二進制文件:
1 QFile file("file.dat"); 2 file.open(QIODevice::ReadOnly); 3 QDataStream in(&file); 4 QString str; qint32 a; 5 in >> str >> a;
每一項被寫入的數據,都是按一種預定義的二進制格式寫入的,改格式取決於具體每一項的類型。
而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以一種可移植的格式去寫入這些數據。典型情況下,你可能會在文件頭寫入一個簡短的幻數字符串和一個版本數字,來用於將來擴展。例如:
1 QFile file("file.xxx"); 2 file.open(QIODevice::WriteOnly); 3 QDataStream out(&file); 4 out << (qint32)123; 5 out << (quint32)0xA0B0C0D0; 6 out << lots_of_interesting_data; 7 out.setVersion(QDataStream::Qt_4_0);
那么,我們就可以以下面這種方式來讀取:
1 QFile file("file.xxx"); 2 file.open(QIODevice::ReadOnly); 3 QDataStream in(&file); 4 quint32 magic; 5 // Read and check the header in >> magic;
6 return XXX_BAD_FILE_FORMAT; 7 if (magic != 0xA0B0C0D0) // Read the version qint32 version;
8 if (version > 123) 9 in >> version; if (version < 100) return XXX_BAD_FILE_TOO_OLD; 10 in.setVersion(QDataStream::Qt_3_2); 11 return XXX_BAD_FILE_TOO_NEW; if (version <= 110) else in.setVersion(QDataStream::Qt_4_0); 12 in >> data_new_in_XXX_version_1_2; 13 // Read the data in >> lots_of_interesting_data; if (version >= 120)
14 in >> other_interesting_data;
同時,還可以在序列化數據時指定一個字節序。默認情況下是big endian。除非特殊需求,我們一個建議保持這個設置的默認值,不做修改。
讀寫原生二進制數據
有時,我們希望直接從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() 的槽函數中,使用事務來完成不完整的數據讀取。
1 in.startTransaction(); 2 QString str; 3 in >> str >> a; 4 qint32 a; 5 if (!in.commitTransaction()) 6 return;
如果沒有完整的數據包到來,commitTransaction() 會返回false,並將stream重置為初始狀態,然后,等待更多數據的到來。
下面給出一個簡單的測試程序:
1 #include <QCoreApplication>
2 #include <QDebug>
3 #include <QDataStream>
4 #include <QFile>
5 #include <QMap>
6 #include <QVector>
7 QCoreApplication a(argc, argv); 8 int main(int argc, char *argv[]) 9 { //write
10 if (!file.open(QIODevice::ReadWrite)) 11 QFile file("test.dat"); 12 { 13 qDebug() << "open file failed"; 14 const char *wstr = "hello-world"; 15 return 0; 16 } 17 QDataStream ds(&file); quint32 wi = 1234; 18 wvector.push_back(1); 19 double wd = 1.1; 20 float wf = 2.2f; 21 QVector<int> wvector; 22 wvector.push_back(2); 23 wmap.insert(5, 5); 24 wvector.push_back(3); 25 QMap<int,int> wmap; 26 wmap.insert(4, 4); 27 wmap.insert(6, 6); 28 ds << wstr; 29 ds.writeBytes("file end ", qstrlen("file end ")); 30 ds << wi; ds << wd; ds << wf; 31 ds << wvector; ds << wmap; 32 double rd; 33 ds.writeRawData("really end", qstrlen("really end")); //read file.seek(0); char *rstr; quint32 ri; float rf;
34 ds >> rstr; 35 QVector<int> rvector; 36 QMap<int, int> rmap; 37 char *rbytes; uint len; 38 char *rraw = new char[100]{0}; 39 int rlen; ds >> ri; 40 ds >> rd; 41 qDebug() << rd; 42 ds >> rf; 43 ds >> rvector; 44 ds >> rmap; 45 ds.readBytes(rbytes, len); 46 ds.readRawData(rraw, rlen); 47 qDebug() << rstr; 48 qDebug() << ri; 49 qDebug() << rf; 50 } 51 qDebug() << rvector; qDebug() << rmap; 52 qDebug() << rbytes; qDebug() << rraw; 53 return a.exec();