QDataStream 二進位制資料讀寫
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
read binary data from a stream:
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);
如果你正在建立一種新的二進位制資料格式,比如作為你的應用程式建立的檔案的格式,你可以使用QDataStream以一種可移植的格式去寫入這些資料。典型情況下,你可能會在檔案頭寫入一個簡短的幻數字符串和一個版本數字,來用於將來擴充套件。例如:
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;
同時,還可以在序列化資料時指定一個位元組序。預設情況下是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() 的槽函式中,使用事務來完成不完整的資料讀取。
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();
}