標籤:
在上一章中,我們介紹了有關QFile和QFileInfo兩個類的使用。我們提到,QIODevice提供了read()、readLine()等基本的操作。同時,Qt 還提供了更高一級的操作:用於二進位的流QDataStream和用於文字資料流的QTextStream。本節,我們將講解有關QDataStream的使用以及一些技巧。下一章則是QTextStream的相關內容。
QDataStream提供了基於QIODevice的位元據的序列化。資料流是一種二進位流,這種流完全不依賴於底層作業系統、CPU 或者位元組順序(大端或小端)。例如,在安裝了 Windows 平台的 PC 上面寫入的一個資料流,可以不經過任何處理,直接拿到運行了 Solaris 的 SPARC 機器上讀取。由於資料流就是二進位流,因此我們也可以直接讀寫沒有編碼的位元據,例像、視頻、音頻等。
QDataStream既能夠存取 C++ 基本類型,如 int、char、short 等,也可以存取複雜的資料類型,例如自訂的類。實際上,QDataStream對於類的儲存,是將複雜的類分割為很多基本單元實現的。
結合QIODevice,QDataStream可以很方便地對檔案、網路通訊端等進行讀寫操作。我們從代碼開始看起:
QFile file("file.dat");file.open(QIODevice::WriteOnly);QDataStream out(&file);out << QString("the answer is");out << (qint32)42;
在這段代碼中,我們首先開啟一個名為 file.dat 的檔案(注意,我們為簡單起見,並沒有檢查檔案開啟是否成功,這在正式程式中是不允許的)。然後,我們將剛剛建立的file對象的指標傳遞給一個QDataStream執行個體out。類似於std::cout標準輸出資料流,QDataStream也重載了輸出重新導向<<運算子。後面的代碼就很簡單了:將“the answer is”和數字 42 輸出到資料流(如果你不明白這句話的意思,這可是宇宙終極問題的答案 ;-P 請自行搜尋《銀河系漫遊指南》)。由於我們的 out 對象建立在file之上,因此相當於將宇宙終極問題的答案寫入file。
需要指出一點:最好使用 Qt 整型來進行讀寫,比如程式中的qint32。這保證了在任意平台和任意編譯器都能夠有相同的行為。
我們通過一個例子來看看 Qt 是如何儲存資料的。例如char *字串,在儲存時,會首先儲存該字串包括 \0 結束符的長度(32位整型),然後是字串的內容以及結束符 \0。在讀取時,先以 32 位整型讀出整個的長度,然後按照這個長度取出整個字串的內容。
但是,如果你直接運行這段代碼,你會得到一個空白的 file.dat,並沒有寫入任何資料。這是因為我們的file沒有正常關閉。為效能起見,資料只有在檔案關閉時才會真正寫入。因此,我們必須在最後添加一行代碼:
file.close(); // 如果不想關閉檔案,可以使用 file.flush();
重新運行一下程式,就OK了。
我們已經獲得宇宙終極問題的答案了,下面,我們要將這個答案讀取出來:
QFile file("file.dat");file.open(QIODevice::ReadOnly);QDataStream in(&file);QString str;qint32 a;in >> str >> a;
這段代碼沒什麼好說的。唯一需要注意的是,你必須按照寫入的順序,將資料讀取出來。也就是說,程式資料寫入的順序必須預先定義好。在這個例子中,我們首先寫入字串,然後寫入數字,那麼就首先讀出來的就是字串,然後才是數字。順序顛倒的話,程式行為是不確定的,嚴重時會直接造成程式崩潰。
由於二進位流是純粹的位元組資料,帶來的問題是,如果程式不同版本之間按照不同的方式讀取(前面說過,Qt 保證讀寫內容的一致,但是並不能保證不同 Qt 版本之間的一致),資料就會出現錯誤。因此,我們必須提供一種機制來確保不同版本之間的一致性。通常,我們會使用如下的代碼寫入:
QFile file("file.dat");file.open(QIODevice::WriteOnly);QDataStream out(&file);// 寫入魔術數字和版本out << (quint32)0xA0B0C0D0;out << (qint32)123;out.setVersion(QDataStream::Qt_4_0);// 寫入資料out << lots_of_interesting_data;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
這裡,我們增加了兩行代碼:
out << (quint32)0xA0B0C0D0;
用於寫入魔術數字。所謂魔術數字,是二進位輸出中經常使用的一種技術。二進位格式是人不可讀的,並且通常具有相同的尾碼名(比如 dat 之類),因此我們沒有辦法區分兩個二進位檔案哪個是合法的。所以,我們定義的二進位格式通常具有一個魔術數字,用於標識檔案的合法性。在本例中,我們在檔案最開始寫入 0xA0B0C0D0,在讀取的時候首先檢查這個數字是不是 0xA0B0C0D0。如果不是的話,說明這個檔案不是可識別格式,因此根本不需要去繼續讀取。一般二進位檔案都會有這麼一個魔術數字,例如 Java 的 class 檔案的魔術數字就是 0xCAFEBABE,使用二進位查看器就可以查看。魔術數字是一個 32 位的無符號整型,因此我們使用quint32來得到一個平台無關的 32 位無符號整型。
接下來一行,
out << (qint32)123;
是標識檔案的版本。我們用魔術數位識別碼檔案的類型,從而判斷檔案是不是合法的。但是,檔案的不同版本之間也可能存在差異:我們可能在第一版儲存整型,第二版可能儲存字串。為了標識不同的版本,我們只能將版本寫入檔案。比如,現在我們的版本是 123。
下面一行還是有關版本的:
out.setVersion(QDataStream::Qt_4_8);
上面一句是檔案的版本號碼,但是,Qt 不同版本之間的讀取方式可能也不一樣。這樣,我們就得指定 Qt 按照哪個版本去讀。這裡,我們指定以 Qt 4.8 格式去讀取內容。
當我們這樣寫入檔案之後,我們在讀取的時候就需要增加一系列的判斷:
QFile file("file.dat");file.open(QIODevice::ReadOnly);QDataStream in(&file);// 檢查魔術數字quint32 magic;in >> magic;if (magic != 0xA0B0C0D0) { return BAD_FILE_FORMAT;}// 檢查版本qint32 version;in >> version;if (version < 100) { return BAD_FILE_TOO_OLD;}if (version > 123) { return BAD_FILE_TOO_NEW;}if (version <= 110) { in.setVersion(QDataStream::Qt_3_2);} else { in.setVersion(QDataStream::Qt_4_0);}// 讀取資料in >> lots_of_interesting_data;if (version >= 120) { in >> data_new_in_version_1_2;}in >> other_interesting_data;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
這段代碼就是按照前面的解釋進行的。首先讀取魔術數字,檢查檔案是否合法。如果合法,讀取檔案版本:小於 100 或者大於 123 都是不支援的。如果在支援的版本範圍內(100 <= version <= 123),則當是小於等於 110 的時候,按照Qt_3_2的格式讀取,否則按照Qt_4_0的格式讀取。當設定完這些參數之後,開始讀取資料。
至此,我們介紹了有關QDataStream的相關內容。那麼,既然QIODevice提供了read()、readLine()之類的函數,為什麼還要有QDataStream呢?QDataStream同QIODevice有什麼區別?區別在於,QDataStream提供流的形式,效能上一般比直接調用原始 API 更好一些。我們通過下面一段代碼看看什麼是流的形式:
QFile file("file.dat");file.open(QIODevice::ReadWrite);QDataStream stream(&file);QString str = "the answer is 42";QString strout;stream << str;file.flush();stream >> strout;
在這段代碼中,我們首先向檔案中寫入資料,緊接著把資料讀出來。有什麼問題嗎?運行之後你會發現,strout實際是空的。為什麼沒有讀取出來?我們不是已經添加了file.flush();語句嗎?原因並不在於檔案有沒有寫入,而是在於我們使用的是“流”。所謂流,就像水流一樣,它的遊標會隨著輸出向後移動。當使用<<操作符輸出之後,流的遊標已經到了最後,此時你再去讀,當然什麼也讀不到了。所以你需要在輸出之後重新把遊標設定為 0 的位置才能夠繼續讀取。具體程式碼片段如下:
stream << str;stream.device()->seek(0);stream >> strout;
http://blog.csdn.net/u013007900/article/details/46459613
Qt 學習 之 二進位檔案讀寫