1. 程式人生 > 實用技巧 >QT學習:10 IO類

QT學習:10 IO類

--- title: framework-cpp-qt-10-IO類 EntryName: framework-cpp-qt-10-QIODevice date: 2020-04-17 10:24:06 categories: tags: - qt - c/c++ ---

章節描述:
QIODevice類是所有輸入輸出IO類的基礎類,為IO類提供了統一的呼叫介面(因此我們稱QIODevice類以及其派生類為IO類)。
QIODevice 是一個抽象類,所以不能被例項化,但通常會用到它定義的介面,這些介面提供裝置無關的I/O特性。例如Qt的XML類通過操作一個QIODevice 的指標,可以使用各種各樣的裝置(files,buffers等)。

IO裝置型別

IO類支援隨機儲存,和順序儲存裝置。

可以使用 isSequential()判斷裝置是否是順序裝置。

其中順序裝置不支援pos(),size()方法。

呼叫size()函式時,如果是隨機裝置則返回當前裝置的大小,如果是順序裝置則返回bytesAvailable()

。在裝置關閉的情況下呼叫size()函式將不會反映裝置的實際大小,因此呼叫該函式前保證裝置已開啟。

順序存取裝置:只能從頭開始順序讀寫資料,不能指定資料的讀寫位置。順序裝置沒有位置概念,也不支援搜尋,只能在資料可用時一次性讀取所有資料。典型的順序裝置是Socket

隨機存取裝置:隨機裝置就是檔案,它們具有大小和當前位置,支援在資料流中向前向後搜尋,可以定位到任意位置進行資料的讀寫。

QT中IO裝置的繼承類圖:

QIODevice
│	隨機裝置
├── QBuffer
├── QFileDevice
│   ├── QFile
│   │   └── QTemporaryFile
│   └── QSaveFile
│
│	順序裝置
├── QAbstractSocket
│   ├── QTcpSocket
│   │   ├── QSctpSocket
│   │   └── QSslSocket
│   └── QUdpSocket
└── QProcess

IO類繼承於QIODevice類,只需要實現自己的writeData()readData()方法。其他讀寫方法QIODevice都是呼叫writeData()readData()實現的。

操作流程

在訪問裝置之前,必須先呼叫open(),並設定正確的OpenMode(such as ReadOnly or ReadWrite)。你可以用write()putChar()來寫入裝置。也可以用read(),readLine()來讀裝置。使用完畢後呼叫close()

在訪問IO類,必須先呼叫open()方法開啟裝置,之後才能呼叫讀寫方法對類進行操作。結束操作後需要呼叫close()方法關閉裝置。

排程 與 同步

IO類發射readyRead()訊號表示有資料可以讀取,對應的可以呼叫bytesAvailable()方法瞭解可以讀取多少位元組的資料。
同理,發射bytesWritten()訊號表示資料寫入完成,對應的可以呼叫bytesToWrite()方法瞭解寫入了多少位元組的資料。

IO類的讀寫函式是非阻塞的,呼叫方法後不會等待資料讀寫完成方法,而是立即返回。

QTcpSocket and QProcess是QIODevice的子類,是非同步的,這意味著 I/O 函式 write() or read()的結果總是立即返回,然而,當控制元件返回到事件迴圈時,可能會發生與裝置本身的通訊。

因此QIODevice提供函式在阻塞呼叫執行緒和不輸入事件迴圈的同時,允許程式立即執行,這使得QIODevice的子類可以被使用,在沒有迴圈事件或者是單執行緒的條件下,提供了waitForReadyRead()waitForBytesWriten()方法實現阻塞(在呼叫讀寫方法後呼叫對應的wait…方法實現阻塞)

waitFor...():子類會實現相應的函式為了特殊的操作。

比如QProcess 有個叫waitForStarted()的函式。它將會延遲呼叫的執行緒,直到那個process已經啟動。

QProcess gzip;
gzip.start("gzip", QStringList() << "-c");
if (!gzip.waitForStarted())
    return false;
 
gzip.write("uncompressed data");
 
QByteArray compressed;
while (gzip.waitForReadyRead())
    compressed += gzip.readAll();

IO類例如QFile,QTcpSocket提供了buffer機制,用於減少底層驅動系統呼叫,提高訪問速度。特別是提高了getChar,putChar方法的速度 。但是在多物件需要讀取同一個裝置的大批量資料時,buffer會導致記憶體中存在多個同樣的資料副本,記憶體開銷巨大。這個情況,可以 在呼叫open()方法時設定Unbuffered模式關閉buffer機制。

常用操作

open(OpenMode mode):開啟裝置。mode引數用於設定讀寫模式,buffer機制,讀寫機制等。
    
close():關閉裝置
isOpen():判斷裝置是否被開啟。
    
isWriteable:判斷裝置是否支援寫入模式。(Open方法設定的)
isReadable:判斷裝置是否支援讀取。
    
isSequential():判斷裝置是否是順序裝置
    
isTextModeEnable():Text模式getChar方法將忽略’/r’換行符,返回下個字元。
setTextModeEnable():設定text模式 

開啟檔案

bool QIODevice::open(QIODevice::OpenMode mode)

描述:以指定的方式開啟一個檔案。

引數解析:

mode:開啟方式

  • QIODevice::NotOpen 不開啟
  • QIODevice::ReadOnly 以只讀的方式開啟.
  • QIODevice::WriteOnly 以只寫的方式開啟,該模式意味著Truncate,除非與ReadOnly,Append或NewOnly結合使用。
  • QIODevice::ReadWrite 裝置以讀寫的方式開啟,寫入檔案會覆蓋之前的內容(開啟檔案期間多次寫入不會覆蓋)。
  • QIODevice::Append 以追加模式開啟,以便將所有資料寫入檔案末尾,此模式下不能讀檔案。
  • QIODevice::Truncate 如果可能,刪除檔案原有內容。
  • QIODevice::Text 讀取時,行尾終止符被轉換為'\ n'。 寫入時,行尾終止符將轉換為本地編碼,例如Win32的“\ r \ n”。(常用於文字檔案以行為單位的讀取)
  • QIODevice::Unbuffered 無緩衝的形式開啟,裝置中的任何緩衝都會被跳過。
  • NewOnly:只允許開啟一個不存在的檔案
  • ExistingOnly:只允許開啟一個已經存在的檔案

設定

呼叫openMode()函式可以獲取當前裝置的開啟模式。如果在裝置開啟的情況下改變裝置模式,可以呼叫setOpenMode()函式來更改。

呼叫setTextModelEnabled()函式可以設定裝置模式為Text,這對於自定義處理終止符非常有幫助。呼叫isTextmodeEnabled()函式可以確定裝置模式是否有Text。

讀取

讀取資料前必須先判斷是否有資料可讀,有兩種方法來確定是否可以讀取資料:

  • 呼叫isReadable()函式;

  • 接收到readyRead()訊號。(當有新資料可被讀取時會發出該訊號)

通常採用訊號的方式,然後呼叫bytesAvailable()函式來確定可讀取資料的大小,通常與順序裝置一起使用,來確定緩衝區中分配的位元組數。

呼叫read()函式來讀取資料,無法讀取時返回-1。

便捷函式有readAll()readLine()/canReadLine()getChar()/ungetChar()

寫入

呼叫isWritable()函式可以判斷裝置是否設定開啟模式中包含了WriteOnly標誌,這是一個便捷函式。

寫入資料時,一般會先寫入緩衝區,然後再從緩衝區寫入裝置。呼叫bytesToWrite()函式返回即將寫入裝置的資料大小,而對於無緩衝的裝置則返回0。每次寫入裝置資料時都會發出bytesWritten()訊號。

寫入資料到裝置中,呼叫write()函式:

qint64 QIODevice::write(const char *data)
qint64 QIODevice::write(const QByteArray &byteArray)
qint64 QIODevice::write(const char *data, qint64 maxSize)

便捷函式有putChar()函式。

讀寫位置

對於隨機裝置來說,每次讀寫都會導致內部的檔案指標偏移;只有隨機裝置才可以設定/讀取讀寫位置。

bool QIODevice::seek(qint64 pos) //定位檔案指標。
    
qint64 QIODevice::pos() const // 獲取檔案指標位置

事務機制

事務(Transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新資料項的一個程式執行單元(unit)。事務一般由事務開始(begin transaction)和事務結束(end transaction)之間執行的全體操作組成。

為什麼要事務?

事務是為解決資料安全操作提出的,事務控制實際上就是控制資料的安全訪問。

用一個簡單例子說明:銀行轉帳業務,賬戶A要將自己賬戶上的1000元轉到B賬戶下面,A賬戶餘額首先要減去1000元,然後B賬戶要增加1000元。假如在中間網路出現了問題,A賬戶減去1000元已經結束,B因為網路中斷而操作失敗,那麼整個業務失敗,必須做出控制,要求A賬戶轉帳業務撤銷。這才能保證業務的正確性,完成這個操走就需要事務,將A賬戶資金減少和B賬戶資金增加放到同一個事務裡,要麼全部執行成功,要麼全部撤銷,這樣就保證了資料的安全性。

事務的4個特性(ACID):

  1. 原子性(atomicity):事務是資料庫的邏輯工作單位,而且是必須是原子工作單位,對於其資料修改,要麼全部執行,要麼全部不執行。

  2. 一致性(consistency):事務在完成時,必須是所有的資料都保持一致狀態。在相關資料庫中,所有規則都必須應用於事務的修改,以保持所有資料的完整性。(例項:轉賬,兩個賬戶餘額相加,值不變。)

  3. 隔離性(isolation):一個事務的執行不能被其他事務所影響。

  4. 永續性(durability):一個事務一旦提交,事物的操作便永久性的儲存在DB中。即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

一般來說,非同步裝置的輸入流是分段的,資料塊可以在任意的時間內到達。要在這種情況下讀到完整資料,使用QIODevice的事務機制。

讀取裝置中的資料時,我們可以呼叫startTransaction()函式來在讀取操作序列中設定一個恢復點。接下來的程式碼進行一般的讀取操作,然後呼叫commitTransaction()函式來提交所有的操作。例如:

in.startTransaction();
 
QString nextFortune;
in >> nextFortune;
 
if (!in.commitTransaction())
 return;

呼叫rollbackTransaction()函式來回滾事務,這會將來自源的輸入流恢復到startTransaction()時的點。

讀寫通道

一些順序裝置支援多通道通訊,這些通道代表了具有獨立排序傳遞特性的獨立資料流。開啟裝置後,呼叫readChannelCount()writeChannelCount()函式來確定通道數。呼叫setCurrentReadChannel()setCurrentWriteChannel()函式來切換通道。

此外,QIODevice還提供額外的訊號來處理通道的非同步通訊。

  • 如果通道有新資料可被裝置讀取時,發出channelReadyRead()訊號。

  • 如果通道有資料寫入裝置時,發出channelBytesWritten()訊號。

  • 關閉讀取通道禁止輸入流時,會發出readChannelFinished()訊號。

錯誤資訊

當裝置發生故障時,我們可以呼叫setErrorString()函式來給裝置設定一個錯誤資訊,任何時候都可以呼叫errorString()函式來檢視當前裝置的錯誤資訊。