鍍金池/ 教程/ C/ 二進(jìn)制文件讀寫(xiě)
Qt 容器和算法拾遺
自定義 model 之一
反走樣
Hello, world!
Qt 容器類(lèi)之關(guān)聯(lián)存儲(chǔ)容器
QStringListModel
拖放技術(shù)之一
狀態(tài)欄
QTreeWidget
拖放技術(shù)之二
通用算法
event()
Qt 學(xué)習(xí)之路(18): Qt 標(biāo)準(zhǔn)對(duì)話框之 QInputDialog
Qt 容器類(lèi)之遍歷器和隱式數(shù)據(jù)共享
QListWidget
Meta-Object 系統(tǒng)
事件接收與忽略
Qt 學(xué)習(xí)之路(tip): parent 參數(shù)
Qt 標(biāo)準(zhǔn)對(duì)話框之 QColorDialog
QPainter(續(xù))
國(guó)際化(下)
漸變填充
自定義委托
創(chuàng)建 shared library
model-view 架構(gòu)
Graphics View Framework
自定義拖放數(shù)據(jù)對(duì)象
QSortFilterProxyModel
國(guó)際化(上)
組件布局
自定義 Model 之三
事件過(guò)濾器
QDirModel
Hello, world!(續(xù))
Qt 標(biāo)準(zhǔn)對(duì)話框之 QFileDialog
自定義 model 之二
深入了解信號(hào)槽
坐標(biāo)變換
剪貼板操作
QTableWidget
QByteArray 和 QVariant
創(chuàng)建一個(gè)對(duì)話框(下)
Qt 學(xué)習(xí)之路(32): 一個(gè)簡(jiǎn)易畫(huà)板的實(shí)現(xiàn)(Graphics View)
文本文件讀寫(xiě)
自定義事件
編寫(xiě)跨平臺(tái)的程序
MainWindow
初探信號(hào)槽
Qt 學(xué)習(xí)之路(17): Qt 標(biāo)準(zhǔn)對(duì)話框之 QMessageBox
繪圖設(shè)備
菜單和工具條(續(xù))
二進(jìn)制文件讀寫(xiě)
QString
事件(event)
菜單和工具條
QPainter
Qt 容器類(lèi)之順序存儲(chǔ)容器
進(jìn)程間交互
API 文檔的使用
創(chuàng)建一個(gè)對(duì)話框(上)
一個(gè)簡(jiǎn)易畫(huà)板的實(shí)現(xiàn)(QWidget)

二進(jìn)制文件讀寫(xiě)

今天開(kāi)始進(jìn)入 Qt 的另一個(gè)部分:文件讀寫(xiě),也就是 IO。文件讀寫(xiě)在很多應(yīng)用程序中都是需要的。Qt 通過(guò) QIODevice 提供了 IO 的抽象,這種設(shè)備(device)具有讀寫(xiě)字節(jié)塊的能力。常用的 IO 讀寫(xiě)的類(lèi)包括以下幾個(gè):

QFlie 訪問(wèn)本地文件系統(tǒng)或者嵌入資源
QTemporaryFile 創(chuàng)建和訪問(wèn)本地文件系統(tǒng)的臨時(shí)文件
QBuffer 讀寫(xiě) QByteArray
QProcess 運(yùn)行外部程序,處理進(jìn)程間通訊
QTcpSocket TCP 協(xié)議網(wǎng)絡(luò)數(shù)據(jù)傳輸
QUdpSocket 傳輸 UDP 報(bào)文
QSslSocket 使用 SSL/TLS 傳輸數(shù)據(jù)

QProcess、QTcpSocket、QUdpSoctet 和 QSslSocket 是順序訪問(wèn)設(shè)備,它們的數(shù)據(jù)只能訪問(wèn)一遍,也就是說(shuō),你只能從第一個(gè)字節(jié)開(kāi)始訪問(wèn),直到最后一個(gè)字節(jié)。QFile、QTemporaryFile 和 QBuffer 是隨機(jī)訪問(wèn)設(shè)備,你可以從任何位置訪問(wèn)任意次數(shù),還可以使用 QIODevice::seek() 函數(shù)來(lái)重新定位文件指針。

在訪問(wèn)方式上,Qt 提供了兩個(gè)更高級(jí)別的抽象:使用 QDataStream 進(jìn)行二進(jìn)制方式的訪問(wèn)和使用 QTextStream 進(jìn)行文本方式的訪問(wèn)。這些類(lèi)可以幫助我們控制字節(jié)順序和文本編碼,使程序員從這種問(wèn)題中解脫出來(lái)。

QFile 對(duì)于訪問(wèn)獨(dú)立的文件是非常方便的,無(wú)論是在文件系統(tǒng)中還是在應(yīng)用程序的資源文件中。Qt 同樣也提供了 QDir 和 QFileInfo 兩個(gè)類(lèi),用于處理文件夾相關(guān)事務(wù)以及查看文件信息等。 這次我們先從二進(jìn)制文件的讀寫(xiě)說(shuō)起。

以二進(jìn)制格式訪問(wèn)數(shù)據(jù)的最簡(jiǎn)單的方式是實(shí)例化一個(gè) QFile 對(duì)象,打開(kāi)文件,然后使用 QDataStream 進(jìn)行訪問(wèn)。QDataStream 提供了平臺(tái)獨(dú)立的訪問(wèn)數(shù)據(jù)格式的方法,這些數(shù)據(jù)格式包括標(biāo)準(zhǔn)的 C++ 類(lèi)型,如 int、double 等;多種 Qt 類(lèi)型,如 QByteArray、QFont、QImage、QPixmap、QString 和 QVariant,以及 Qt 的容器類(lèi),如 QList 和 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; 

這里,我們首先創(chuàng)建了一個(gè) QImage 對(duì)象,一個(gè) QMap<QString, QColor>,然后使用 QFile 創(chuàng)建了一個(gè)名為 "facts.dat" 的文件,然后以只寫(xiě)方式打開(kāi)。如果打開(kāi)失敗,直接 return;否則我們使用 QFile 的指針創(chuàng)建一個(gè) QDataStream 對(duì)象,然后設(shè)置 version,這個(gè)我們以后再詳細(xì)說(shuō)明,最后就像 std 的 cout 一樣,使用 << 運(yùn)算符輸出結(jié)果。

0x12345678 成為“魔術(shù)數(shù)字”,這是二進(jìn)制文件輸出中經(jīng)常使用的一種技術(shù)。我們定義的二進(jìn)制格式通常具有一個(gè)這樣的“魔術(shù)數(shù)字”,用于標(biāo)志文件格式。例如,我們?cè)谖募铋_(kāi)始寫(xiě)入 0x12345678,在讀取的時(shí)候首先檢查這個(gè)數(shù)字是不是 0x12345678,如果不是的話,這就不是可識(shí)別格式,因此根本不需要去讀取。一般二進(jìn)制格式都會(huì)有這么一個(gè)魔術(shù)數(shù)字,例如 Java 的 class 文件的魔術(shù)數(shù)字就是 0xCAFE BABE(很 Java 的名字),使用二進(jìn)制查看器就可以查看。魔術(shù)數(shù)字是一個(gè) 32 位的無(wú)符號(hào)整數(shù),因此我們使用 quint32 宏來(lái)得到一個(gè)平臺(tái)無(wú)關(guān)的 32 位無(wú)符號(hào)整數(shù)。

在這段代碼中我們使用了一個(gè) qPrintable() 宏,這個(gè)宏實(shí)際上是把 QString 對(duì)象轉(zhuǎn)換成 const char *。注意到我們使用的是 C++ 標(biāo)準(zhǔn)錯(cuò)誤輸出 cerr,因此必須使用這個(gè)轉(zhuǎn)換。當(dāng)然,QString::toStdString() 函數(shù)也能夠完成同樣的操作。

讀取的過(guò)程就很簡(jiǎn)單了,需要注意的是讀取必須同寫(xiě)入的過(guò)程一一對(duì)應(yīng),即第一個(gè)寫(xiě)入 quint32 型的魔術(shù)數(shù)字,那么第一個(gè)讀出的也必須是一個(gè) quint32 格式的數(shù)據(jù),如


quint32 n;  
QImage image;  
QMap<QString, QColor> map;  

QFile file("facts.dat");  
if (!file.open(QIODevice::ReadOnly)) {  
    std::cerr << "Cannot open file for reading: " 
              << qPrintable(file.errorString()) << std::endl;  
    return;  
}  

QDataStream in(&file);  
in.setVersion(QDataStream::Qt_4_3);  

in >> n >> image >> map; 

好了,數(shù)據(jù)讀出了,拿著到處去用吧!

這個(gè) version 是干什么用的呢?對(duì)于二進(jìn)制的讀寫(xiě),隨著 Qt 的版本升級(jí),可能相同的內(nèi)容有了不同的讀寫(xiě)方式,比如可能由大端寫(xiě)入變成了小端寫(xiě)入等,這樣的話舊版本 Qt 寫(xiě)入的內(nèi)容就不能正確的讀出,因此需要設(shè)定一個(gè)版本號(hào)。比如這里我們使用 QDataStream::Qt_4_3,意思是,我們使用 Qt 4.3 的方式寫(xiě)入數(shù)據(jù)。實(shí)際上,現(xiàn)在的最高版本號(hào)已經(jīng)是 QDataStream::Qt_4_6。如果這么寫(xiě),就是說(shuō),4.3 版本之前的 Qt 是不能保證正確讀寫(xiě)文件內(nèi)容的。那么,問(wèn)題就來(lái)了:我們以硬編碼的方式寫(xiě)入這個(gè) version,豈不是不能使用最新版的 Qt 的讀寫(xiě)了?

解決方法之一是,我們不僅僅寫(xiě)入一個(gè)魔術(shù)數(shù)字,同時(shí)寫(xiě)入這個(gè)文件的版本。例如:


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;

這個(gè) file.xxx 文件的版本號(hào)是 123。我們認(rèn)為,如果版本號(hào)是123的話,則可以使用 Qt_4_0 版本讀取。所以我們的讀取代碼就需要判斷一下:


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;

這樣,我們就可以比較完美的處理二進(jìn)制格式的數(shù)據(jù)讀寫(xiě)了。

本文出自 “豆子空間” 博客,請(qǐng)務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918