檔案操作是應用程式必不可少的部分。Qt 作為一個通用開發庫,提供了跨平台的檔案操作能力。Qt 通過QIODevice提供了對 I/O 裝置的抽象,這些裝置具有讀寫位元組塊的能力。下面是 I/O 裝置的類圖(Qt5):
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SYxcTNkZ2N1AzYyIWY5MWOiFDM0YTZmhTMzcTMwE2N58CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
QIODevice:所有 I/O 裝置類的父類,提供了位元組塊讀寫的通用操作以及基本接口;
QFileDevice:Qt5新增加的類,提供了有關檔案操作的通用實作。
QFlie:通路本地檔案或者嵌入資源;
QTemporaryFile:建立和通路本地檔案系統的臨時檔案;
QBuffer:讀寫QbyteArray, 記憶體檔案;
QProcess:運作外部程式,處理程序間通訊;
QAbstractSocket:所有套接字類的父類;
QTcpSocket:TCP協定網絡資料傳輸;
QUdpSocket:傳輸 UDP 封包;
QSslSocket:使用 SSL/TLS 傳輸資料;
檔案系統分類:
順序通路裝置:
是指它們的資料隻能通路一遍:從頭走到尾,從第一個位元組開始通路,直到最後一個位元組,中途不能傳回去讀取上一個位元組,這其中,QProcess、QTcpSocket、QUdpSoctet和QSslSocket是順序通路裝置。
随機通路裝置:
可以通路任意位置任意次數,還可以使用QIODevice::seek()函數來重新定位檔案通路位置指針,QFile、QTemporaryFile和QBuffer是随機通路裝置,
11.1 基本檔案操作
檔案操作是應用程式必不可少的部分。Qt 作為一個通用開發庫,提供了跨平台的檔案操作能力。在所有的 I/O 裝置中,檔案 I/O 是最重要的部分之一。因為我們大多數的程式依舊需要首先通路本地檔案(當然,在雲計算大行其道的将來,這一觀點可能改變)。QFile提供了從檔案中讀取和寫入資料的能力。
我們通常會将檔案路徑作為參數傳給QFile的構造函數。不過也可以在建立好對象最後,使用setFileName()來修改。QFile需要使用 / 作為檔案分隔符,不過,它會自動将其轉換成作業系統所需要的形式。例如 C:/windows 這樣的路徑在 Windows 平台下同樣是可以的。
QFile主要提供了有關檔案的各種操作,比如打開檔案、關閉檔案、重新整理檔案等。我們可以使用QDataStream或QTextStream類來讀寫檔案,也可以使用QIODevice類提供的read()、readLine()、readAll()以及write()這樣的函數。值得注意的是,有關檔案本身的資訊,比如檔案名、檔案所在目錄的名字等,則是通過QFileInfo擷取,而不是自己分析檔案路徑字元串。
下面我們使用一段代碼來看看QFile的有關操作:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Open file failed.";
return -1;
} else {
while (!file.atEnd()) {
qDebug() << file.readLine();
}
}
QFileInfo info(file);
qDebug() << info.isDir();
qDebug() << info.isExecutable();
qDebug() << info.baseName();
qDebug() << info.completeBaseName();
qDebug() << info.suffix();
qDebug() << info.completeSuffix();
return app.exec();
}
我們首先使用QFile建立了一個檔案對象。
這個檔案名字是 in.txt。如果你不知道應該把它放在哪裡,可以使用QDir::currentPath()來獲得應用程式執行時的目前路徑。隻要将這個檔案放在與目前路徑一緻的目錄下即可。
使用open()函數打開這個檔案,打開形式是隻讀方式,文本格式。
這個類似于fopen()的 r 這樣的參數。open()函數傳回一個 bool 類型,如果打開失敗,我們在控制台輸出一段提示然後程式退出。否則,我們利用 while 循環,将每一行讀到的内容輸出。
可以使用QFileInfo擷取有關該檔案的資訊。
QFileInfo有很多類型的函數,我們隻舉出一些例子。比如:
isDir()檢查該檔案是否是目錄;
isExecutable() 檢查該檔案是否是可執行檔案等。
baseName() 可以直接獲得檔案名;
completeBaseName() 擷取完整的檔案名
suffix() 則直接擷取檔案字尾名。
completeSuffix() 擷取完整的檔案字尾
我們可以由下面的示例看到,baseName()和completeBaseName(),以及suffix()和completeSuffix()的差別:
QFileInfo fi("/tmp/archive.tar.gz");
QString base = fi.baseName(); // base = "archive"
QString base = fi.completeBaseName(); // base = "archive.tar"
QString ext = fi.suffix(); // ext = "gz"
QString ext = fi.completeSuffix(); // ext = "tar.gz"
11.2 二進制檔案讀寫
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 輸出到資料流。由于我們的 out 對象建立在file之上,是以相當于将問題和答案寫入file。
需要指出一點:最好使用 Qt 整型來進行讀寫,比如程式中的qint32。這保證了在任意平台和任意編譯器都能夠有相同的行為。
如果你直接運作這段代碼,你會得到一個空白的 file.dat,并沒有寫入任何資料。這是因為我們的file沒有正常關閉。為性能起見,資料隻有在檔案關閉時才會真正寫入。是以,我們必須在最後添加一行代碼:
file.close(); // 如果不想關閉檔案,可以使用 file.flush();
接下來我們将存儲到檔案中的答案取出來
file.open(QIODevice::ReadOnly);
QDataStream in(&file);
QString str;
qint32 a;
in >> str >> a;
唯一需要注意的是,你必須按照寫入的順序,将資料讀取出來。順序颠倒的話,程式行為是不确定的,嚴重時會直接造成程式崩潰。
那麼,既然QIODevice提供了read()、readLine()之類的函數,為什麼還要有QDataStream呢?QDataStream同QIODevice有什麼差別?差別在于,QDataStream提供流的形式,性能上一般比直接調用原始 API 更好一些。我們通過下面一段代碼看看什麼是流的形式:
file.open(QIODevice::ReadWrite);
QDataStream stream(&file);
QString str = "the answer is 42";
stream << str;
11.3 文本檔案讀寫
上一節我們介紹了有關二進制檔案的讀寫。二進制檔案比較小巧,卻不是人可讀的格式。而文本檔案是一種人可讀的檔案。為了操作這種檔案,我們需要使用QTextStream類。QTextStream和QDataStream的使用類似,隻不過它是操作純文字檔案的。
QTextStream會自動将 Unicode 編碼同作業系統的編碼進行轉換,這一操作對開發人員是透明的。它也會将換行符進行轉換,同樣不需要自己處理。QTextStream使用 16 位的QChar作為基礎的資料存儲機關,同樣,它也支援 C++ 标準類型,如 int 等。實際上,這是将這種标準類型與字元串進行了互相轉換。
QTextStream同QDataStream的使用基本一緻,例如下面的代碼将把“The answer is 42”寫入到 file.txt 檔案中:
QFile data("file.txt");
if (data.open(QFile::WriteOnly | QIODevice::Truncate))
QTextStream out(&data);
out << "The answer is " << 42;
這裡,我們在open()函數中增加了QIODevice::Truncate打開方式。我們可以從下表中看到這些打開方式的差別:
枚舉值 描述
QIODevice::NotOpen 未打開
QIODevice::ReadOnly 以隻讀方式打開
QIODevice::WriteOnly 以隻寫方式打開
QIODevice::ReadWrite 以讀寫方式打開
QIODevice::Append 以追加的方式打開,
新增加的内容将被追加到檔案末尾
QIODevice::Truncate 以重寫的方式打開,在寫入新的資料時會将原有
資料全部清除,遊标設定在檔案開頭。
QIODevice::Text 在讀取時,将行結束符轉換成 \n;在寫入時,
将行結束符轉換成本地格式,例如 Win32 平台
上是 \r\n
QIODevice::Unbuffered 忽略緩存
我們在這裡使用了QFile::WriteOnly | QIODevice::Truncate,也就是以隻寫并且覆寫已有内容的形式操作檔案。注意,QIODevice::Truncate會直接将檔案内容清空。
雖然QTextStream的寫入内容與QDataStream一緻,但是讀取時卻會有些困難:
if (data.open(QFile::ReadOnly))
QTextStream in(&data);
QString str;
int ans = 0;
in >> str >> ans;
在使用QDataStream的時候,這樣的代碼很友善,但是使用了QTextStream時卻有所不同:讀出的時候,str 裡面将是 The answer is 42,ans 是 0。這是因為當使用QDataStream寫入的時候,實際上會在要寫入的内容前面,額外添加一個這段内容的長度值。而以文本形式寫入資料,是沒有資料之間的分隔的。是以,使用文本檔案時,很少會将其分割開來讀取,而是使用諸如使用:
QTextStream::readLine() 讀取一行
QTextStream::readAll()讀取所有文本
這種函數之後再對獲得的QString對象進行處理。
預設情況下,QTextStream的編碼格式是 Unicode,如果我們需要使用另外的編碼,可以使用:
stream.setCodec("UTF-8");
這樣的函數進行設定。