1. 程式人生 > >Qt-二進位制檔案讀寫-QDataStream

Qt-二進位制檔案讀寫-QDataStream

轉自:http://blog.51cto.com/devbean/293892

今天開始進入 Qt 的另一個部分:檔案讀寫,也就是 IO。檔案讀寫在很多應用程式中都是需要的。Qt 通過 QIODevice 提供了IO的抽象,這種裝置(device)具有讀寫位元組塊的能力。常用的IO讀寫的類包括以下幾個:
QFlie訪問本地檔案系統或者嵌入資源
QTemporaryFile建立和訪問本地檔案系統的臨時檔案
QBuffer讀寫 QByteArray
QProcess執行外部程式,處理程序間通訊
QTcpSocketTCP 協議網路資料傳輸
QUdpSocket傳輸 UDP 報文
QSslSocket使用 SSL/TLS 傳輸資料
 QProcess、QTcpSocket、QUdpSoctet 和 QSslSocket 是順序訪問裝置,它們的資料只能訪問一遍,也就是說,你只能從第一個位元組開始訪問,直到最後一個位元組。QFile、QTemporaryFile 和 QBuffer 是隨機訪問裝置,你可以從任何位置訪問任意次數,還可以使用 QIODevice::seek() 函式來重新定位檔案指標。在訪問方式上,Qt 提供了兩個更高級別的抽象:使用 QDataStream 進行二進位制方式的訪問和使用 QTextStream 進行文字方式的訪問。這些類可以幫助我們控制位元組順序和文字編碼,使程式設計師從這種問題中解脫出來。QFile 對於訪問獨立的檔案是非常方便的,無論是在檔案系統中還是在應用程式的資原始檔中。Qt 同樣也提供了 QDir 和 QFileInfo 兩個類,用於處理資料夾相關事務以及檢視檔案資訊等。這次我們先從二進位制檔案的讀寫說起。以二進位制格式訪問資料的最簡單的方式是例項化一個 QFile 物件,開啟檔案,然後使用 QDataStream 進行訪問。QDataStream 提供了平臺獨立的訪問資料格式的方法,這些資料格式包括標準的 C++ 型別,如 int、double等;多種 Qt 型別,如QByteArray、QFont、QImage、QPixmap、QString 和 QVariant,以及 Qt 的容器類,如 QList<T> 和 QMap<K, T>。先看如下的程式碼:
  1. QImage image(
    "philip.png");  
  2. QMap<QString, QColor> map;  
  3. map.insert("red", Qt::red);  
  4. map.insert("green", Qt::green);  
  5. map.insert("blue", Qt::blue);  
  6. QFile file("facts.dat");  
  7. if (!file.open(QIODevice::WriteOnly)) {  
  8.     std::cerr << "Cannot open file for writing: "
  9.               << qPrintable(file.errorString()) << std::endl;  
  10. return;  
  11. }  
  12. QDataStream out(&file);  
  13. out.setVersion(QDataStream::Qt_4_3);  
  14. out << quint32(0x12345678) << image << map; 
這裡,我們首先建立了一個 QImage 物件,一個 QMap<QString, QColor>,然後使用 QFile 建立了一個名為 "facts.dat" 的檔案,然後以只寫方式開啟。如果開啟失敗,直接 return;否則我們使用 QFile 的指標建立一個 QDataStream 物件,然後設定 version,這個我們以後再詳細說明,最後就像 std 的 cout 一樣,使用 << 運算子輸出結果。0x12345678 成為“魔術數字”,這是二進位制檔案輸出中經常使用的一種技術。我們定義的二進位制格式通常具有一個這樣的“魔術數字”,用於標誌檔案格式。例如,我們在檔案最開始寫入 0x12345678,在讀取的時候首先檢查這個數字是不是 0x12345678,如果不是的話,這就不是可識別格式,因此根本不需要去讀取。一般二進位制格式都會有這麼一個魔術數字,例如 Java 的 class 檔案的魔術數字就是 0xCAFE BABE(很 Java 的名字),使用二進位制檢視器就可以檢視。魔術數字是一個 32 位的無符號整數,因此我們使用 quint32 巨集來得到一個平臺無關的 32 位無符號整數。在這段程式碼中我們使用了一個 qPrintable() 巨集,這個巨集實際上是把 QString 物件轉換成 const char *。注意到我們使用的是 C++ 標準錯誤輸出 cerr,因此必須使用這個轉換。當然,QString::toStdString() 函式也能夠完成同樣的操作。讀取的過程就很簡單了,需要注意的是讀取必須同寫入的過程一一對應,即第一個寫入 quint32 型的魔術數字,那麼第一個讀出的也必須是一個 quint32 格式的資料,如
  1. quint32 n;  
  2. QImage image;  
  3. QMap<QString, QColor> map;  
  4. QFile file("facts.dat");  
  5. if (!file.open(QIODevice::ReadOnly)) {  
  6.     std::cerr << "Cannot open file for reading: "
  7.               << qPrintable(file.errorString()) << std::endl;  
  8. return;  
  9. }  
  10. QDataStream in(&file);  
  11. in.setVersion(QDataStream::Qt_4_3);  
  12. in >> n >> image >> map; 
好了,資料讀出了,拿著到處去用吧!這個 version 是幹什麼用的呢?對於二進位制的讀寫,隨著 Qt 的版本升級,可能相同的內容有了不同的讀寫方式,比如可能由大端寫入變成了小端寫入等,這樣的話舊版本 Qt 寫入的內容就不能正確的讀出,因此需要設定一個版本號。比如這裡我們使用 QDataStream::Qt_4_3,意思是,我們使用 Qt 4.3 的方式寫入資料。實際上,現在的最高版本號已經是 QDataStream::Qt_4_6。如果這麼寫,就是說,4.3 版本之前的 Qt 是不能保證正確讀寫檔案內容的。那麼,問題就來了:我們以硬編碼的方式寫入這個 version,豈不是不能使用最新版的 Qt 的讀寫了?解決方法之一是,我們不僅僅寫入一個魔術數字,同時寫入這個檔案的版本。例如:
  1. QFile file("file.xxx");  
  2. file.open(QIODevice::WriteOnly);  
  3. QDataStream out(&file);  
  4. // Write a header with a "magic number" and a version
  5. out << (quint32)0xA0B0C0D0;  
  6. out << (qint32)123;  
  7. out.setVersion(QDataStream::Qt_4_0);  
  8. // Write the data
  9. out << lots_of_interesting_data; 
這個 file.xxx 檔案的版本號是 123。我們認為,如果版本號是123的話,則可以使用 Qt_4_0 版本讀取。所以我們的讀取程式碼就需要判斷一下:
  1. QFile file("file.xxx");  
  2.  file.open(QIODevice::ReadOnly);  
  3.  QDataStream in(&file);  
  4. // Read and check the header
  5.  quint32 magic;  
  6.  in >> magic;  
  7. if (magic != 0xA0B0C0D0)  
  8. return XXX_BAD_FILE_FORMAT;  
  9. // Read the version
  10.  qint32 version;  
  11.  in >> version;  
  12. if (version < 100)  
  13. return XXX_BAD_FILE_TOO_OLD;  
  14. if (version > 123)  
  15. return XXX_BAD_FILE_TOO_NEW;  
  16. if (version <= 110)  
  17.      in.setVersion(QDataStream::Qt_3_2);  
  18. else
  19.      in.setVersion(QDataStream::Qt_4_0);  
  20. // Read the data
  21.  in >> lots_of_interesting_data;  
  22. if (version >= 120)  
  23.      in >> data_new_in_XXX_version_1_2;  
  24.  in >> other_interesting_data; 
這樣,我們就可以比較完美的處理二進位制格式的資料讀寫了。