天天看點

Qt學習之路(56): 二進制檔案讀寫

今天開始進入 Qt 的另一個部分:檔案讀寫,也就是 IO。檔案讀寫在很多應用程式中都是需要的。Qt 通過 QIODevice 提供了IO的抽象,這種裝置(device)具有讀寫位元組塊的能力。常用的IO讀寫的類包括以下幾個:

QFlie

通路本地檔案系統或者嵌入資源

QTemporaryFile

建立和通路本地檔案系統的臨時檔案

QBuffer

讀寫 QByteArray

QProcess

運作外部程式,處理程序間通訊

QTcpSocket

TCP 協定網絡資料傳輸

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>。先看如下的代碼:

QImage image("philip.png");  

QMap<QString, QColor> map;  

map.insert("red", Qt::red);  

map.insert("green", Qt::green);  

map.insert("blue", Qt::blue);  

QFile file("facts.dat");  

if (!file.open(QIODevice::WriteOnly)) {  

    std::cerr << "Cannot open file for writing: " 

              << qPrintable(file.errorString()) << std::endl;  

    return;  

}  

QDataStream out(&file);  

out.setVersion(QDataStream::Qt_4_3);  

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 格式的資料,如

quint32 n;  

QImage image;  

if (!file.open(QIODevice::ReadOnly)) {  

    std::cerr << "Cannot open file for reading: " 

QDataStream in(&file);  

in.setVersion(QDataStream::Qt_4_3);  

in >> n >> image >> map; 

好了,資料讀出了,拿着到處去用吧!

這個 version 是幹什麼用的呢?對于二進制的讀寫,随着 Qt 的版本更新,可能相同的内容有了不同的讀寫方式,比如可能由大端寫入變成了小端寫入等,這樣的話舊版本 Qt 寫入的内容就不能正确的讀出,是以需要設定一個版本号。比如這裡我們使用 QDataStream::Qt_4_3,意思是,我們使用 Qt 4.3 的方式寫入資料。實際上,現在的最高版本号已經是 QDataStream::Qt_4_6。如果這麼寫,就是說,4.3 版本之前的 Qt 是不能保證正确讀寫檔案内容的。那麼,問題就來了:我們以寫死的方式寫入這個 version,豈不是不能使用最新版的 Qt 的讀寫了?

解決方法之一是,我們不僅僅寫入一個魔術數字,同時寫入這個檔案的版本。例如:

QFile file("file.xxx");  

file.open(QIODevice::WriteOnly);  

// 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; 

這個 file.xxx 檔案的版本号是 123。我們認為,如果版本号是123的話,則可以使用 Qt_4_0 版本讀取。是以我們的讀取代碼就需要判斷一下:

 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; 

這樣,我們就可以比較完美的處理二進制格式的資料讀寫了。

本文轉自 FinderCheng 51CTO部落格,原文連結:

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

繼續閱讀