天天看點

11、QT基礎——檔案系統

檔案操作是應用程式必不可少的部分。Qt 作為一個通用開發庫,提供了跨平台的檔案操作能力。Qt 通過QIODevice提供了對 I/O 裝置的抽象,這些裝置具有讀寫位元組塊的能力。下面是 I/O 裝置的類圖(Qt5):

11、QT基礎——檔案系統

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");

這樣的函數進行設定。