目錄
- QIODevice類:
- 一、一般操作
- 1、open()和close()
- 2、read()
- 3、write()
- 二、隨機存取設備和順序設備
- 三、讀寫信號
- 四、阻塞函數
- 五、虛函數readData、readLineData、writeData
- 六、內存緩沖區
- 七、事務機制
- QIODevicePrivate類
- QRingBuffer和QRingChunk
QIODevice類:
QIODevice 為支持讀取和寫入數據塊的設備(如QFile、QBuffer、和QTcpSocket)提供了通用實現和抽象接口。QIODevice 是抽象的,無法實例化,但通常使用它定義的接口來提供與設備無關的 I/O 功能。例如,Qt的XML類在QIODevice指針上運行,允許它們與各種設備(如文件和緩沖區)一起使用。
一、一般操作
在訪問設備之前,必須調用open()
來設置正確的 OpenMode(例如 ReadOnly 或 ReadWrite)。然后,可以使用write()
或putChar()
寫入設備,并通過調用read()
、readLine()
或readAll()
進行讀取。使用完設備后調用close()
。
1、open()和close()
public:virtual bool open(OpenMode mode);virtual void close();
QIODevice::open()對QIODevicePrivate類d指針的屬性數據進行設置。
bool QIODevice::open(OpenMode mode)
{Q_D(QIODevice);d->openMode = mode;d->pos = (mode & Append) ? size() : qint64(0);d->accessMode = QIODevicePrivate::Unset;d->readBuffers.clear();d->writeBuffers.clear();//.............................return true;
}
2、read()
public:qint64 read(char *data, qint64 maxlen);QByteArray read(qint64 maxlen);QByteArray readAll();qint64 readLine(char *data, qint64 maxlen);QByteArray readLine(qint64 maxlen = 0);virtual bool canReadLine() const;
QIODevice類中提供了讀寫等操作的接口,QIODevicePrivate中保存實際的數據和相關屬性、相關操作。幾個讀取函數最終都調用了qint64 QIODevice::read(char *data, qint64 maxSize)
。在內部調用d指針的buffer成員的getchar()
或者QIODevicePrivate::read()
。
qint64 QIODevice::read(char *data, qint64 maxSize)
{Q_D(QIODevice);const bool sequential = d->isSequential();// getChar()的快捷方式,除非我們需要將數據保存在緩沖區中。if (maxSize == 1 && !(sequential && d->transactionStarted)) {while ((chint = d->buffer.getChar()) != -1) {//................................return (qint64(1));}}//....................................const qint64 readBytes = d->read(data, maxSize);return readBytes;
}
public:qint64 peek(char *data, qint64 maxlen);QByteArray peek(qint64 maxlen);qint64 skip(qint64 maxSize);
peek:從設備讀取maxlen的字節,而不會產生不好的結果。
skip:從設備跳到maxSize字節。返回實際跳過的字節數。
3、write()
public:qint64 write(const char *data, qint64 len);qint64 write(const char *data);inline qint64 write(const QByteArray &data){ return write(data.constData(), data.size()); }
寫操作調用了writeData純虛函數,同時對d指針的屬性成員做相應更改,在windows系統和非windows系統中的處理不同。
qint64 QIODevice::write(const char *data, qint64 maxSize)
{Q_D(QIODevice);//........................................
#ifdef Q_OS_WIN//.................writeData
#endifqint64 written = writeData(data, maxSize);if (!sequential && written > 0) {d->pos += written;d->devicePos += written;d->buffer.skip(written);}return written;
}
public:void ungetChar(char c);bool putChar(char c);bool getChar(char *c);
putChar:將字符c寫入設備。調用了QIODevicePrivate::putCharHelper(char)
,該函數中又會調用QIODevice::write
。
bool QIODevice::putChar(char c)
{return d_func()->putCharHelper(c);
}
bool QIODevicePrivate::putCharHelper(char c)
{return q_func()->write(&c, 1) == 1;
}
getChar:從設備中讀取一個字符并將其存儲在c中。調用了QIODevice::read(char *, int)
bool QIODevice::getChar(char *c)
{// readability checked in read()char ch;return (1 == read(c ? c : &ch, 1));
}
ungetChar:將字符c放回設備中,并遞減當前位置,除非位置為0。調用此函數通常用于“撤消”getChar()操作。
二、隨機存取設備和順序設備
QIODevice分為兩種類型的設備:隨機存取設備和順序設備,可以使用isSequential()
來確定設備的類型。
- 隨機存取設備支持使用
seek()
尋找任意位置。通過調用pos()
可以獲取文件中的當前位置。QFile和QBuffer都是隨機存取設備。
- 順序設備不支持搜索任意位置。數據必須一次性讀取。函數
pos()
和size()
不適用于順序設備。QTcpSocket和QProcess都是順序設備。
public:virtual qint64 pos() const;virtual qint64 size() const;virtual bool seek(qint64 pos);virtual bool atEnd() const;virtual bool reset();
seek:對于隨機存取設備,此函數將當前位置設置為 pos。
三、讀寫信號
當有新數據可供讀取時,QIODevice 發出readyRead()
;例如,如果新數據已到達網絡,或者如果將其他數據追加到要讀取的文件。您可以調用bytesAvailable()
來確定當前可用于讀取的字節數。在使用異步設備(例如QTcpSocket)進行編程時,通常將bytesAvailable()
與readyRead()
信號一起使用,其中數據片段可以在任意時間點到達。QIODevice 每次將數據有效載荷寫入設備時都會發出bytesWritten()
信號。使用bytesToWrite()
確定當前等待寫入的數據量。
Q_SIGNALS:void readyRead();void channelReadyRead(int channel);void bytesWritten(qint64 bytes);void channelBytesWritten(int channel, qint64 bytes);void aboutToClose();void readChannelFinished();
Q_SIGNALS和signals宏的內容一樣,都是替換為public
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
public:virtual qint64 bytesAvailable() const;virtual qint64 bytesToWrite() const;
四、阻塞函數
QIODevice 的某些子類(QTcpSocket和QProcess)是異步的。這意味著write()
或read()
等I/O 功能總是立即返回,而當控制返回到事件循環時,可能會與設備本身進行通信。QIODevice提供了一些函數,允許強制立即執行這些操作,同時阻塞調用線程,而無需進入事件循環。這允許在沒有事件循環的情況下使用QIODevice子類,或者在單獨的線程中使用:
waitForReadyRead()
,此函數暫停調用線程中的操作,直到有新數據可供讀取。waitForBytesWritten()
,此函數暫停調用線程中的操作,直到將一個數據有效負載寫入設備。waitFor....()
,QIODevice的子類為特定于設備的操作實現阻塞功能。例如,QProcess有一個名為waitForStarted()
的函數,該函數暫停調用線程中的操作,直到進程啟動。
public:virtual bool waitForReadyRead(int msecs);virtual bool waitForBytesWritten(int msecs);
從主 GUI 線程調用這些函數可能會導致用戶界面凍結。
五、虛函數readData、readLineData、writeData
通過對 QIODevice 進行子類化,可以為自己的 I/O 設備提供相同的接口。QIODevice 的子類只需要實現受保護的readData()
和writeData()
函數。QIODevice 使用這些函數來實現其所有便利功能,例如getChar()
、readLine()
和write()
。QIODevice還處理訪問控制,因此在調用writeData()
可以放心地假設設備以寫入模式打開。
protected:virtual qint64 readData(char *data, qint64 maxlen) = 0;virtual qint64 readLineData(char *data, qint64 maxlen);virtual qint64 writeData(const char *data, qint64 len) = 0;
六、內存緩沖區
某些子類(QFile和QTcpSocket)是使用內存緩沖區實現的,用于中間存儲數據。這減少了設備訪問調用的數量,這些調用通常非常慢。緩沖使getChar()
和putChar()
等函數變得快速,因為它們可以在內存緩沖區上運行,而不是直接在設備本身上運行。但是,某些 I/O 操作不能很好地與緩沖區配合使用。例如,如果多個用戶打開同一設備并逐個字符讀取它,則當他們打算讀取每個單獨的塊時,他們最終可能會讀取相同的數據。出于這個原因,QIODevice 允許通過將 Unbuffered 標志傳遞給open()
來繞過任何緩沖。在對 QIODevice 進行子類化時,請記住在無緩沖模式下打開設備時繞過可能使用的任何緩沖區。
七、事務機制
通常,來自異步設備的傳入數據流是碎片化的,數據塊可以在任意時間點到達。要處理數據結構的不完整讀取,請使用 QIODevice 實現的事務機制。
void startTransaction();void commitTransaction();void rollbackTransaction();bool isTransactionStarted() const;
startTransaction:在設備上啟動新的讀取事務,定義讀取操作序列中的可還原點。
commitTransaction:完成讀取事務。對于順序設備,事務期間記錄在內部緩沖區中的所有數據都將被丟棄。
rollbackTransaction:回滾讀取事務。
QIODevicePrivate類
class Q_CORE_EXPORT QIODevicePrivate : public QObjectPrivate
{Q_DECLARE_PUBLIC(QIODevice)
public:QIODevicePrivate();virtual ~QIODevicePrivate();QIODevice::OpenMode openMode;QString errorString;
//...........................................class QRingBufferRef {QRingBuffer *m_buf;//getChar.putChar.size.clear.skip.append.peek...............};QRingBufferRef buffer;QRingBufferRef writeBuffer;qint64 pos;qint64 devicePos;enum AccessMode {//............};mutable AccessMode accessMode;//........函數.......
};
如果允許緩沖且有數據在緩沖區中,直接buffer.read()
讀取緩沖區數據。如果讀取數據小于目標數據,1.readData()
直接從設備上讀取。2.readData()
從設備讀取到緩存區,進入下一次循環,繼續buffer.read()
從緩沖區讀取。
qint64 QIODevicePrivate::read(char *data, qint64 maxSize, bool peeking)
{Q_Q(QIODevice);//.............forever {qint64 bufferReadChunkSize = keepDataInBuffer? buffer.peek(data, maxSize, bufferPos): buffer.read(data, maxSize);maxSize -= bufferReadChunkSize;if (maxSize > 0 && !deviceAtEof) {//.............if ((!buffered || maxSize >= readBufferChunkSize) && !keepDataInBuffer) {readFromDevice = q->readData(data, maxSize);//.............} else {readFromDevice = q->readData(buffer.reserve(bytesToBuffer), bytesToBuffer);//.............}//.............}}
}
QRingBuffer和QRingChunk
QRingBuffer是QIODevice用來存儲緩沖數據的類,在該類中定義了一個動態數組,在頭部讀取,尾部追加。
bufferSize為緩沖區存儲數據大小,basicBufferSize為可存儲數據大小,#define QRINGBUFFER_CHUNKSIZE 4096
,在QRingBuffer初始化時會將這個宏賦值給basicBufferSize。
class QRingBuffer
{
public:inline int getChar() {//........}inline void putChar(char c) {char *ptr = reserve(1);//返回地址*ptr = c;}Q_CORE_EXPORT qint64 read(char *data, qint64 maxLength);//..........................
private:QVector<QRingChunk> buffers;qint64 bufferSize;int basicBlockSize;
};
緩沖區讀取最終調用函數,先取最大長度和緩沖區數據大小的較小值作為總讀取數據長度。每一次循環中取剩余長度和下一塊待讀取數據長度的較小值作為單次讀取數據的長度。memcpy拷貝緩沖數據,拷貝完釋放數據,從動態數組buffers中移除。
qint64 QRingBuffer::read(char *data, qint64 maxLength)
{const qint64 bytesToRead = qMin(size(), maxLength);qint64 readSoFar = 0;while (readSoFar < bytesToRead) {const qint64 bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar,nextDataBlockSize());if (data)memcpy(data + readSoFar, readPointer(), bytesToReadFromThisBlock);readSoFar += bytesToReadFromThisBlock;free(bytesToReadFromThisBlock);}return readSoFar;
}
QRingBuffer用來存儲數據的QVector<QRingChunk> buffers
存儲了若干個QRingChunk對象,該類對象就是對QByteArray做簡單封裝,增加頭尾索引值。
class QRingChunk
{
public:void allocate(int alloc);inline const char *data() const{return chunk.constData() + headOffset;}
//...............................
private:QByteArray chunk;int headOffset, tailOffset;
};