目录
[2.1.QRingChunk:数据片段的 "原子管理单元"](#2.1.QRingChunk:数据片段的 “原子管理单元”)
[2.2.QRingBuffer:多片段的 "容器与调度中心"](#2.2.QRingBuffer:多片段的 “容器与调度中心”)
1.简介
Qt源码中的QRingBuffer类,这个类不是Qt API的一部分,所以Qt助手里是查不到的,它的存在只是为了服务其他的源码。这里用的是5.12.12版本。
QRingBuffer的源文件:
.\Qt\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\tools\qringbuffer_p.h
.\Qt\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\tools\qringbuffer.cpp
QRingBuffer实现的环形缓冲区大概如下图所示:

源码如下:qringbuffer_p.h
cpp
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QRINGBUFFER_P_H
#define QRINGBUFFER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of a number of Qt sources files. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/private/qglobal_p.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qvector.h>
QT_BEGIN_NAMESPACE
#ifndef QRINGBUFFER_CHUNKSIZE
#define QRINGBUFFER_CHUNKSIZE 4096
#endif
class QRingChunk
{
public:
// initialization and cleanup
inline QRingChunk() Q_DECL_NOTHROW :
headOffset(0), tailOffset(0)
{
}
inline QRingChunk(const QRingChunk &other) Q_DECL_NOTHROW :
chunk(other.chunk), headOffset(other.headOffset), tailOffset(other.tailOffset)
{
}
explicit inline QRingChunk(int alloc) :
chunk(alloc, Qt::Uninitialized), headOffset(0), tailOffset(0)
{
}
explicit inline QRingChunk(const QByteArray &qba) Q_DECL_NOTHROW :
chunk(qba), headOffset(0), tailOffset(qba.size())
{
}
inline QRingChunk &operator=(const QRingChunk &other) Q_DECL_NOTHROW
{
chunk = other.chunk;
headOffset = other.headOffset;
tailOffset = other.tailOffset;
return *this;
}
inline QRingChunk(QRingChunk &&other) Q_DECL_NOTHROW :
chunk(other.chunk), headOffset(other.headOffset), tailOffset(other.tailOffset)
{
other.headOffset = other.tailOffset = 0;
}
inline QRingChunk &operator=(QRingChunk &&other) Q_DECL_NOTHROW
{
swap(other);
return *this;
}
inline void swap(QRingChunk &other) Q_DECL_NOTHROW
{
chunk.swap(other.chunk);
qSwap(headOffset, other.headOffset);
qSwap(tailOffset, other.tailOffset);
}
// allocating and sharing
void allocate(int alloc);
inline bool isShared() const
{
return !chunk.isDetached();
}
Q_CORE_EXPORT void detach();
QByteArray toByteArray();
// getters
inline int head() const
{
return headOffset;
}
inline int size() const
{
return tailOffset - headOffset;
}
inline int capacity() const
{
return chunk.size();
}
inline int available() const
{
return chunk.size() - tailOffset;
}
inline const char *data() const
{
return chunk.constData() + headOffset;
}
inline char *data()
{
if (isShared())
detach();
return chunk.data() + headOffset;
}
// array management
inline void advance(int offset)
{
Q_ASSERT(headOffset + offset >= 0);
Q_ASSERT(size() - offset > 0);
headOffset += offset;
}
inline void grow(int offset)
{
Q_ASSERT(size() + offset > 0);
Q_ASSERT(head() + size() + offset <= capacity());
tailOffset += offset;
}
inline void assign(const QByteArray &qba)
{
chunk = qba;
headOffset = 0;
tailOffset = qba.size();
}
inline void reset()
{
headOffset = tailOffset = 0;
}
inline void clear()
{
assign(QByteArray());
}
private:
QByteArray chunk;
int headOffset, tailOffset;
};
class QRingBuffer
{
public:
explicit inline QRingBuffer(int growth = QRINGBUFFER_CHUNKSIZE) :
bufferSize(0), basicBlockSize(growth) { }
inline void setChunkSize(int size) {
basicBlockSize = size;
}
inline int chunkSize() const {
return basicBlockSize;
}
inline qint64 nextDataBlockSize() const {
return bufferSize == 0 ? Q_INT64_C(0) : buffers.first().size();
}
inline const char *readPointer() const {
return bufferSize == 0 ? nullptr : buffers.first().data();
}
Q_CORE_EXPORT const char *readPointerAtPosition(qint64 pos, qint64 &length) const;
Q_CORE_EXPORT void free(qint64 bytes);
Q_CORE_EXPORT char *reserve(qint64 bytes);
Q_CORE_EXPORT char *reserveFront(qint64 bytes);
inline void truncate(qint64 pos) {
Q_ASSERT(pos >= 0 && pos <= size());
chop(size() - pos);
}
Q_CORE_EXPORT void chop(qint64 bytes);
inline bool isEmpty() const {
return bufferSize == 0;
}
inline int getChar() {
if (isEmpty())
return -1;
char c = *readPointer();
free(1);
return int(uchar(c));
}
inline void putChar(char c) {
char *ptr = reserve(1);
*ptr = c;
}
void ungetChar(char c)
{
char *ptr = reserveFront(1);
*ptr = c;
}
inline qint64 size() const {
return bufferSize;
}
Q_CORE_EXPORT void clear();
inline qint64 indexOf(char c) const { return indexOf(c, size()); }
Q_CORE_EXPORT qint64 indexOf(char c, qint64 maxLength, qint64 pos = 0) const;
Q_CORE_EXPORT qint64 read(char *data, qint64 maxLength);
Q_CORE_EXPORT QByteArray read();
Q_CORE_EXPORT qint64 peek(char *data, qint64 maxLength, qint64 pos = 0) const;
Q_CORE_EXPORT void append(const char *data, qint64 size);
Q_CORE_EXPORT void append(const QByteArray &qba);
inline qint64 skip(qint64 length) {
qint64 bytesToSkip = qMin(length, bufferSize);
free(bytesToSkip);
return bytesToSkip;
}
Q_CORE_EXPORT qint64 readLine(char *data, qint64 maxLength);
inline bool canReadLine() const {
return indexOf('\n') >= 0;
}
private:
QVector<QRingChunk> buffers;
qint64 bufferSize;
int basicBlockSize;
};
Q_DECLARE_SHARED(QRingChunk)
Q_DECLARE_TYPEINFO(QRingBuffer, Q_MOVABLE_TYPE);
QT_END_NAMESPACE
#endif // QRINGBUFFER_P_H
qringbuffer.cpp
cpp
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Alex Trotsenko <alex1973tr@gmail.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "private/qringbuffer_p.h"
#include "private/qbytearray_p.h"
#include <string.h>
QT_BEGIN_NAMESPACE
void QRingChunk::allocate(int alloc)
{
Q_ASSERT(alloc > 0 && size() == 0);
if (chunk.size() < alloc || isShared())
chunk = QByteArray(alloc, Qt::Uninitialized);
}
void QRingChunk::detach()
{
Q_ASSERT(isShared());
const int chunkSize = size();
QByteArray x(chunkSize, Qt::Uninitialized);
::memcpy(x.data(), chunk.constData() + headOffset, chunkSize);
chunk = qMove(x);
headOffset = 0;
tailOffset = chunkSize;
}
QByteArray QRingChunk::toByteArray()
{
if (headOffset != 0 || tailOffset != chunk.size()) {
if (isShared())
return chunk.mid(headOffset, size());
if (headOffset != 0) {
char *ptr = chunk.data();
::memmove(ptr, ptr + headOffset, size());
tailOffset -= headOffset;
headOffset = 0;
}
chunk.reserve(0); // avoid that resizing needlessly reallocates
chunk.resize(tailOffset);
}
return chunk;
}
/*!
\internal
Access the bytes at a specified position the out-variable length will
contain the amount of bytes readable from there, e.g. the amount still
the same QByteArray
*/
const char *QRingBuffer::readPointerAtPosition(qint64 pos, qint64 &length) const
{
Q_ASSERT(pos >= 0);
for (const QRingChunk &chunk : buffers) {
length = chunk.size();
if (length > pos) {
length -= pos;
return chunk.data() + pos;
}
pos -= length;
}
length = 0;
return 0;
}
void QRingBuffer::free(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qint64 chunkSize = buffers.constFirst().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunk &chunk = buffers.first();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
chunk.advance(bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeFirst();
}
}
char *QRingBuffer::reserve(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
const int chunkSize = qMax(basicBlockSize, int(bytes));
int tail = 0;
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.append(QRingChunk(chunkSize));
else
buffers.first().allocate(chunkSize);
} else {
const QRingChunk &chunk = buffers.constLast();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.available())
buffers.append(QRingChunk(chunkSize));
else
tail = chunk.size();
}
buffers.last().grow(bytes);
bufferSize += bytes;
return buffers.last().data() + tail;
}
/*!
\internal
Allocate data at buffer head
*/
char *QRingBuffer::reserveFront(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
const int chunkSize = qMax(basicBlockSize, int(bytes));
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.prepend(QRingChunk(chunkSize));
else
buffers.first().allocate(chunkSize);
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
const QRingChunk &chunk = buffers.constFirst();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.head()) {
buffers.prepend(QRingChunk(chunkSize));
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
buffers.first().advance(-bytes);
}
}
bufferSize += bytes;
return buffers.first().data();
}
void QRingBuffer::chop(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qint64 chunkSize = buffers.constLast().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunk &chunk = buffers.last();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
chunk.grow(-bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeLast();
}
}
void QRingBuffer::clear()
{
if (buffers.isEmpty())
return;
buffers.erase(buffers.begin() + 1, buffers.end());
buffers.first().clear();
bufferSize = 0;
}
qint64 QRingBuffer::indexOf(char c, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
if (maxLength == 0)
return -1;
qint64 index = -pos;
for (const QRingChunk &chunk : buffers) {
const qint64 nextBlockIndex = qMin(index + chunk.size(), maxLength);
if (nextBlockIndex > 0) {
const char *ptr = chunk.data();
if (index < 0) {
ptr -= index;
index = 0;
}
const char *findPtr = reinterpret_cast<const char *>(memchr(ptr, c,
nextBlockIndex - index));
if (findPtr)
return qint64(findPtr - ptr) + index + pos;
if (nextBlockIndex == maxLength)
return -1;
}
index = nextBlockIndex;
}
return -1;
}
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;
}
/*!
\internal
Read an unspecified amount (will read the first buffer)
*/
QByteArray QRingBuffer::read()
{
if (bufferSize == 0)
return QByteArray();
bufferSize -= buffers.constFirst().size();
return buffers.takeFirst().toByteArray();
}
/*!
\internal
Peek the bytes from a specified position
*/
qint64 QRingBuffer::peek(char *data, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
qint64 readSoFar = 0;
for (const QRingChunk &chunk : buffers) {
if (readSoFar == maxLength)
break;
qint64 blockLength = chunk.size();
if (pos < blockLength) {
blockLength = qMin(blockLength - pos, maxLength - readSoFar);
memcpy(data + readSoFar, chunk.data() + pos, blockLength);
readSoFar += blockLength;
pos = 0;
} else {
pos -= blockLength;
}
}
return readSoFar;
}
/*!
\internal
Append bytes from data to the end
*/
void QRingBuffer::append(const char *data, qint64 size)
{
Q_ASSERT(size >= 0);
if (size == 0)
return;
char *writePointer = reserve(size);
if (size == 1)
*writePointer = *data;
else
::memcpy(writePointer, data, size);
}
/*!
\internal
Append a new buffer to the end
*/
void QRingBuffer::append(const QByteArray &qba)
{
if (bufferSize != 0 || buffers.isEmpty())
buffers.append(QRingChunk(qba));
else
buffers.last().assign(qba);
bufferSize += qba.size();
}
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
{
Q_ASSERT(data != nullptr && maxLength > 1);
--maxLength;
qint64 i = indexOf('\n', maxLength);
i = read(data, i >= 0 ? (i + 1) : maxLength);
// Terminate it.
data[i] = '\0';
return i;
}
QT_END_NAMESPACE
2.实现分析
源码实现包括两部分QRingChunk和QRingBuffer。
https://contribute.qt-project.org/doc/dc/d92/classQRingBuffer.html
2.1.QRingChunk:数据片段的 "原子管理单元"
QRingChunk 封装了单一段落的内存存储(QByteArray) 和有效数据范围(headOffset/tailOffset) ,负责单个片段的内存分配、数据访问、共享控制,是 QRingBuffer 实现 "片段化存储" 的基础。
1.核心成员变量解析
| 成员变量 | 作用 |
|---|---|
QByteArray chunk |
实际存储数据的载体,利用 QByteArray 的写时复制(COW) 特性减少冗余拷贝; |
int headOffset |
有效数据的起始偏移(从 chunk 开头到有效数据的距离); |
int tailOffset |
有效数据的结束偏移(从 chunk 开头到有效数据末尾的距离); |
- 有效数据的长度 =
tailOffset - headOffset(通过size()方法直接返回); - 片段的剩余可用空间 =
chunk.size() - tailOffset(通过available()方法返回)。
2.构造与拷贝:兼顾效率与安全性
QRingChunk 提供了多场景的构造函数和拷贝 / 移动语义,确保不同场景下的内存效率:
(1)构造函数:覆盖主流初始化需求
- 默认构造 :
QRingChunk()初始化空片段(headOffset = tailOffset = 0,chunk为空),无内存分配。 - 指定大小构造 :
explicit QRingChunk(int alloc)创建一个容量为alloc的未初始化片段(Qt::Uninitialized避免默认零初始化开销),有效数据为空(head=tail=0),用于提前预留内存。 - 从 QByteArray 构造 :
explicit QRingChunk(const QByteArray &qba)直接复用qba的内存(利用 COW,不复制数据),有效数据范围为整个qba(head=0,tail=qba.size()),适用于直接封装已有字节数组。 - 拷贝构造 / 赋值 :
QRingChunk(const QRingChunk &other)浅拷贝chunk(COW 特性,仅复制指针)和偏移量,仅在后续修改时才通过detach()复制数据,减少初始拷贝开销。 - 移动构造 / 赋值 :
QRingChunk(QRingChunk &&other)窃取other的chunk和偏移量,other重置为空白状态(head=tail=0),零拷贝 ,适合片段转移场景(如QVector扩容时的元素移动)。
(2)swap 方法:高效交换片段
cpp
inline void swap(QRingChunk &other) Q_DECL_NOTHROW
{
chunk.swap(other.chunk);
qSwap(headOffset, other.headOffset);
qSwap(tailOffset, other.tailOffset);
}
- 直接交换
QByteArray(内部仅交换指针)和偏移量,无数据拷贝,是移动语义的核心支撑,确保片段转移的高效性。
3. 核心方法:控制数据访问与内存状态
QRingChunk 的方法可分为 "内存分配""数据访问""状态调整" 三类,均围绕 "减少拷贝、复用内存" 设计:
(1)内存分配与共享控制
allocate(int alloc):为空白片段(size() == 0)分配至少alloc字节内存。若现有chunk容量不足,或chunk处于共享状态(isShared()),则重新创建未初始化的QByteArray,避免修改共享数据。isShared():判断片段是否处于共享状态(依赖QByteArray::isDetached()),用于决定是否需要detach()。detach():当片段共享时,复制有效数据到新的独立QByteArray,重置偏移量,确保后续修改不影响原共享对象(COW 逻辑的核心实现)。
(2)数据访问:安全且高效
data() const:返回有效数据的只读指针(chunk.constData() + headOffset),无拷贝,适合预览数据。data():返回可写指针,若片段共享则先detach(),确保修改安全,避免破坏共享数据(写时复制的触发点)。
(3)状态调整:零拷贝修改有效范围
advance(int offset):移动headOffset(增加offset),缩小有效数据范围(用于 "消费头部数据",如读取后跳过已读部分),无数据移动,仅调整偏移量。grow(int offset):移动tailOffset(增加offset),扩大有效数据范围(用于 "写入尾部数据",如reserve后填充数据),无数据拷贝,仅调整偏移量。reset():重置headOffset = tailOffset = 0,保留chunk内存(不释放),用于复用片段(如QRingBuffer::clear()时保留第一个片段)。clear():调用assign(QByteArray()),释放chunk内存并重置偏移量,用于彻底清空片段。
2.2.QRingBuffer:多片段的 "容器与调度中心"
QRingBuffer 通过 QVector<QRingChunk> 管理多个片段,负责片段的新增、删除、调度,实现 "环形存储" 的整体逻辑(如头部读取、尾部写入、头部预留等),并通过 bufferSize 快速维护总数据量,避免遍历片段计算大小。
1.核心成员变量解析
| 成员变量 | 作用 |
|---|---|
QVector<QRingChunk> buffers |
存储所有数据片段的动态数组(QVector 是连续内存容器,尾部新增效率高); |
qint64 bufferSize |
缓冲区总有效数据量(快速判断空满:isEmpty() = (bufferSize == 0)); |
int basicBlockSize |
新增片段的默认大小(默认 QRINGBUFFER_CHUNKSIZE,通常为 4096 字节); |
2.关键设计:利用 QVector 特性优化片段调度
QVector 是连续内存容器 ,其尾部插入(append)效率为 amortized O(1) ,头部删除(removeFirst)效率为 O (n)(需移动后续元素)。QRingBuffer 通过逻辑优化规避 QVector 头部删除的性能问题:
- 仅当第一个片段的有效数据被完全消费(
free时chunk.size() <= bytes),才调用buffers.removeFirst()删除片段; - 若第一个片段未完全消费(
chunk.size() > bytes),则仅调用QRingChunk::advance(bytes)调整偏移量,不删除片段,避免QVector移动元素的开销。
3.核心方法:片段调度与数据流转
QRingBuffer 的方法围绕 "读写" 展开,通过操作 buffers 中的 QRingChunk 实现高效数据处理,关键方法如下:
(1)写入相关:尾部 / 头部预留空间
reserve(qint64 bytes) :在尾部预留 bytes 字节空间,返回可写指针。
- 若缓冲区为空,复用首个片段或新建
QRingChunk(大小为basicBlockSize); - 若缓冲区非空,检查最后一个片段的
available()空间:- 空间足够:直接使用该片段;
- 空间不足:新建
QRingChunk(大小为max(basicBlockSize, bytes)),追加到buffers;
- 调用最后一个片段的
grow(bytes)扩展有效范围,更新bufferSize,返回可写指针(chunk.data() + 原有 tailOffset)。
reserveFront(qint64 bytes) :在头部预留 bytes 字节空间,返回可写指针(支持 "头部写入",如协议头部封装)。
逻辑与 reserve 对称:检查第一个片段的头部空闲空间(headOffset),不足则在 buffers 头部插入新 QRingChunk,调整 headOffset 后返回指针。
(2)读取相关:头部消费与预览
free(qint64 bytes):从头部释放 bytes 字节(消费数据)。
- 若第一个片段的
size()>bytes:调用advance(bytes)调整偏移量,bufferSize -= bytes; - 若第一个片段的
size()<=bytes:删除该片段(buffers.removeFirst()),bufferSize -= 片段大小,继续处理剩余bytes; - 若释放全部数据,复用小容量片段(
capacity() <= basicBlockSize),避免后续分配开销。
read(char *data, qint64 maxLength):读取头部数据并消费。
循环读取 buffers.first() 的数据,每次读取后调用 free() 释放已读部分,直到满足 maxLength 或缓冲区为空,无数据移动 ,仅通过 free() 调整片段。
peek(char *data, qint64 maxLength, qint64 pos):预览指定位置数据(不消费)。
遍历 buffers 找到 pos 所在片段,复制有效数据到 data,不修改片段状态,适合协议解析前的帧头判断。
(3)数据截断与清空
chop(qint64 bytes):从尾部截断 bytes 字节(删除数据)。
与 free 对称:检查最后一个片段的 size(),不足则删除该片段,否则调用 grow(-bytes) 缩小有效范围。
clear():清空缓冲区,保留首个片段复用。
删除除首个片段外的所有元素(buffers.erase(buffers.begin()+1, ...)),调用首个片段的 clear() 重置,避免频繁创建 / 删除片段的开销。
(4)辅助方法:高效查询与操作
nextDataBlockSize():返回第一个片段的有效数据长度(buffers.first().size()),用于read时判断单次可读取字节数。readPointer():返回第一个片段的有效数据指针(buffers.first().data()),用于直接访问头部数据,减少函数调用开销。indexOf(char c, ...):遍历片段查找字符c的位置,为readLine提供底层支持(查找换行符'\n')。
3.设计亮点总结
1.COW 特性深度利用 :通过 QByteArray 的共享特性,QRingChunk 仅在修改共享数据时才复制,减少冗余内存占用;
2.零拷贝数据操作 :advance/grow 仅调整偏移量,free/chop 仅删除片段或调整偏移,无传统环形缓冲区的数据移动开销;
3.内存复用优化 :clear() 保留首个片段,reset() 重置偏移量不释放内存,减少频繁分配 / 释放的开销;
4.QVector 效率适配 :规避 QVector 头部删除的性能问题,仅在片段完全消费时才删除,确保调度效率;
5.轻量接口设计 :readPointer()/nextDataBlockSize() 等 inline 方法直接访问成员,减少函数调用开销,适合高频读写场景。
4.使用建议
1.合理设置 basicBlockSize
让 basicBlockSize 接近单次写入的平均数据量,避免 "大块小用" 或 "小块多用"。
- 小数据高频写入(如每次 100~500 字节,如串口数据):保持默认 4096 字节(与系统页大小匹配,减少内存碎片)。
- 大数据低频写入(如每次 8KB~16KB,如文件块、视频帧):设置为 8KB/16KB,避免单个数据分拆到多个片段。
- 超大块数据(如每次 64KB+):直接将
basicBlockSize设为单次写入大小,避免片段拆分。
2.优先使用 reserve 而非直接 append
若已知写入数据大小,先调用 reserve(size) 获取指针后直接写入,避免 append 内部的指针计算开销。
提前分配足够片段,避免多次 append/reserve 触发片段新增,减少 QVector<QRingChunk> 扩容开销。
cpp
// 已知需写入 100 帧,每帧 4KB,共 400KB
QRingBuffer buffer(4 * 1024);
buffer.reserve(100 * 4 * 1024); // 预分配 400KB 空间,生成 100 个 4KB 片段(或更少大片段)
// 批量写入,复用预分配片段,无新增片段开销
for (int i = 0; i < 100; ++i) {
char* ptr = buffer.reserve(4 * 1024);
memcpy(ptr, frameData[i], 4 * 1024);
}
3.避免频繁头部操作(reserveFront/ungetChar)
QVector<QRingChunk> 是连续内存容器,头部插入 / 删除片段会触发后续元素移动(O (n) 开销)。
头部插入可能导致 QVector 头部新增片段,后续 free 时删除头部片段会触发元素移动,尽量在尾部写入。
4.避免频繁创建 / 删除
- 利用
clear()特性:clear()会保留首个片段,仅重置偏移量(headOffset=tailOffset=0),避免重新创建片段。 - 避免不必要的
clear():若后续仍需写入同类数据,可调用QRingChunk::reset()重置单个片段,而非clear()整个缓冲区。
cpp
// 循环使用缓冲区,避免每次清空后重建片段
QRingBuffer buffer;
while (isRunning) {
// 写入数据
buffer.append(data, len);
// 读取处理
processBuffer(buffer);
// 重置首个片段(复用内存,比 clear() 更轻量)
if (!buffer.buffers.isEmpty()) {
buffer.buffers.first().reset();
buffer.bufferSize = 0;
}
}
4.线程安全需外部保障
类定义中无任何同步机制(如 QMutex),多线程读写时需手动加锁,避免片段状态不一致。
cpp
class ThreadSafeBuffer {
private:
QRingBuffer buffer;
QMutex mutex;
QWaitCondition notEmpty;
QWaitCondition notFull;
const qint64 maxSize = 1024 * 1024; // 1MB 上限
public:
// 生产者:批量写入
void writeBatch(const QList<QByteArray>& dataList) {
QMutexLocker locker(&mutex);
qint64 totalLen = 0;
for (const auto& ba : dataList) totalLen += ba.size();
// 缓冲区满则等待
while (buffer.size() + totalLen > maxSize) {
notFull.wait(&mutex);
}
// 批量写入,减少锁内操作时间
for (const auto& ba : dataList) {
buffer.append(ba);
}
notEmpty.wakeOne();
}
// 消费者:批量读取
QList<QByteArray> readBatch(qint64 maxTotalLen) {
QMutexLocker locker(&mutex);
QList<QByteArray> result;
qint64 readLen = 0;
// 无数据则等待
while (buffer.isEmpty()) {
notEmpty.wait(&mutex);
}
// 批量读取,直到达到上限或缓冲区空
while (readLen < maxTotalLen && !buffer.isEmpty()) {
qint64 blockSize = qMin(buffer.nextDataBlockSize(), maxTotalLen - readLen);
char* ptr = new char[blockSize];
buffer.read(ptr, blockSize);
result.append(QByteArray(ptr, blockSize));
delete[] ptr;
readLen += blockSize;
}
notFull.wakeOne();
return result;
}
};
5.总结
通过 QRingChunk 的精细内存管理和 QRingBuffer 的高效片段调度,两者共同实现了 "低拷贝、高复用、灵活扩展" 的环形缓冲区,尤其适合 IO 缓存、多线程数据传输等高频读写场景。
6.直接可以使用的代码
源码在Qt外部不能直接使用,我整理了一下,直接拷贝,加入工程,直接调用。整理代码如下:
QRingBufferEx.h
cpp
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QRINGBUFFEREX_H
#define QRINGBUFFEREX_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of a number of Qt sources files. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#include <QByteArray>
#include <QVector>
//QT_BEGIN_NAMESPACE
#ifndef QRINGBUFFER_CHUNKSIZE
#define QRINGBUFFER_CHUNKSIZE 4096
#endif
class QRingChunkEx
{
public:
// initialization and cleanup
inline QRingChunkEx() Q_DECL_NOTHROW :
headOffset(0), tailOffset(0)
{
}
inline QRingChunkEx(const QRingChunkEx &other) Q_DECL_NOTHROW :
chunk(other.chunk), headOffset(other.headOffset), tailOffset(other.tailOffset)
{
}
explicit inline QRingChunkEx(int alloc) :
chunk(alloc, Qt::Uninitialized), headOffset(0), tailOffset(0)
{
}
explicit inline QRingChunkEx(const QByteArray &qba) Q_DECL_NOTHROW :
chunk(qba), headOffset(0), tailOffset(qba.size())
{
}
inline QRingChunkEx &operator=(const QRingChunkEx &other) Q_DECL_NOTHROW
{
chunk = other.chunk;
headOffset = other.headOffset;
tailOffset = other.tailOffset;
return *this;
}
inline QRingChunkEx(QRingChunkEx &&other) Q_DECL_NOTHROW :
chunk(other.chunk), headOffset(other.headOffset), tailOffset(other.tailOffset)
{
other.headOffset = other.tailOffset = 0;
}
inline QRingChunkEx &operator=(QRingChunkEx &&other) Q_DECL_NOTHROW
{
swap(other);
return *this;
}
inline void swap(QRingChunkEx &other) Q_DECL_NOTHROW
{
chunk.swap(other.chunk);
qSwap(headOffset, other.headOffset);
qSwap(tailOffset, other.tailOffset);
}
// allocating and sharing
void allocate(int alloc);
inline bool isShared() const
{
return !chunk.isDetached();
}
void detach();
QByteArray toByteArray();
// getters
inline int head() const
{
return headOffset;
}
inline int size() const
{
return tailOffset - headOffset;
}
inline int capacity() const
{
return chunk.size();
}
inline int available() const
{
return chunk.size() - tailOffset;
}
inline const char *data() const
{
return chunk.constData() + headOffset;
}
inline char *data()
{
if (isShared())
detach();
return chunk.data() + headOffset;
}
// array management
inline void advance(int offset)
{
Q_ASSERT(headOffset + offset >= 0);
Q_ASSERT(size() - offset > 0);
headOffset += offset;
}
inline void grow(int offset)
{
Q_ASSERT(size() + offset > 0);
Q_ASSERT(head() + size() + offset <= capacity());
tailOffset += offset;
}
inline void assign(const QByteArray &qba)
{
chunk = qba;
headOffset = 0;
tailOffset = qba.size();
}
inline void reset()
{
headOffset = tailOffset = 0;
}
inline void clear()
{
assign(QByteArray());
}
private:
QByteArray chunk;
int headOffset, tailOffset;
};
class QRingBufferEx
{
public:
explicit inline QRingBufferEx(int growth = QRINGBUFFER_CHUNKSIZE) :
bufferSize(0), basicBlockSize(growth) { }
inline void setChunkSize(int size) {
basicBlockSize = size;
}
inline int chunkSize() const {
return basicBlockSize;
}
inline qint64 nextDataBlockSize() const {
return bufferSize == 0 ? Q_INT64_C(0) : buffers.first().size();
}
inline const char *readPointer() const {
return bufferSize == 0 ? nullptr : buffers.first().data();
}
const char *readPointerAtPosition(qint64 pos, qint64 &length) const;
void free(qint64 bytes);
char *reserve(qint64 bytes);
char *reserveFront(qint64 bytes);
inline void truncate(qint64 pos) {
Q_ASSERT(pos >= 0 && pos <= size());
chop(size() - pos);
}
void chop(qint64 bytes);
inline bool isEmpty() const {
return bufferSize == 0;
}
inline int getChar() {
if (isEmpty())
return -1;
char c = *readPointer();
free(1);
return int(uchar(c));
}
inline void putChar(char c) {
char *ptr = reserve(1);
*ptr = c;
}
void ungetChar(char c)
{
char *ptr = reserveFront(1);
*ptr = c;
}
inline qint64 size() const {
return bufferSize;
}
void clear();
inline qint64 indexOf(char c) const { return indexOf(c, size()); }
qint64 indexOf(char c, qint64 maxLength, qint64 pos = 0) const;
qint64 read(char *data, qint64 maxLength);
QByteArray read();
qint64 peek(char *data, qint64 maxLength, qint64 pos = 0) const;
void append(const char *data, qint64 size);
void append(const QByteArray &qba);
inline qint64 skip(qint64 length) {
qint64 bytesToSkip = qMin(length, bufferSize);
free(bytesToSkip);
return bytesToSkip;
}
qint64 readLine(char *data, qint64 maxLength);
inline bool canReadLine() const {
return indexOf('\n') >= 0;
}
private:
QVector<QRingChunkEx> buffers;
qint64 bufferSize;
int basicBlockSize;
};
//Q_DECLARE_SHARED(QRingChunk)
//Q_DECLARE_TYPEINFO(QRingBuffer, Q_MOVABLE_TYPE);
//QT_END_NAMESPACE
#endif // QRINGBUFFER_P_H
QRingBufferEx.cpp
cpp
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Alex Trotsenko <alex1973tr@gmail.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <string.h>
#include <limits>
#include "QRingBufferEx.h"
//QT_BEGIN_NAMESPACE
// -1 because of the terminating NUL
#define MaxAllocSize std::numeric_limits<int>::max()
constexpr qsizetype MaxByteArraySize = MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPtr>::type) - 1;
constexpr qsizetype MaxStringSize = (MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPtr>::type)) / 2 - 1;
void QRingChunkEx::allocate(int alloc)
{
Q_ASSERT(alloc > 0 && size() == 0);
if (chunk.size() < alloc || isShared())
chunk = QByteArray(alloc, Qt::Uninitialized);
}
void QRingChunkEx::detach()
{
Q_ASSERT(isShared());
const int chunkSize = size();
QByteArray x(chunkSize, Qt::Uninitialized);
::memcpy(x.data(), chunk.constData() + headOffset, chunkSize);
chunk = qMove(x);
headOffset = 0;
tailOffset = chunkSize;
}
QByteArray QRingChunkEx::toByteArray()
{
if (headOffset != 0 || tailOffset != chunk.size()) {
if (isShared())
return chunk.mid(headOffset, size());
if (headOffset != 0) {
char *ptr = chunk.data();
::memmove(ptr, ptr + headOffset, size());
tailOffset -= headOffset;
headOffset = 0;
}
chunk.reserve(0); // avoid that resizing needlessly reallocates
chunk.resize(tailOffset);
}
return chunk;
}
/*!
\internal
Access the bytes at a specified position the out-variable length will
contain the amount of bytes readable from there, e.g. the amount still
the same QByteArray
*/
const char *QRingBufferEx::readPointerAtPosition(qint64 pos, qint64 &length) const
{
Q_ASSERT(pos >= 0);
for (const QRingChunkEx &chunk : buffers) {
length = chunk.size();
if (length > pos) {
length -= pos;
return chunk.data() + pos;
}
pos -= length;
}
length = 0;
return 0;
}
void QRingBufferEx::free(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qint64 chunkSize = buffers.constFirst().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunkEx &chunk = buffers.first();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
chunk.advance(bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeFirst();
}
}
char *QRingBufferEx::reserve(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
const int chunkSize = qMax(basicBlockSize, int(bytes));
int tail = 0;
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.append(QRingChunkEx(chunkSize));
else
buffers.first().allocate(chunkSize);
} else {
const QRingChunkEx &chunk = buffers.constLast();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.available())
buffers.append(QRingChunkEx(chunkSize));
else
tail = chunk.size();
}
buffers.last().grow(bytes);
bufferSize += bytes;
return buffers.last().data() + tail;
}
/*!
\internal
Allocate data at buffer head
*/
char *QRingBufferEx::reserveFront(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
const int chunkSize = qMax(basicBlockSize, int(bytes));
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.prepend(QRingChunkEx(chunkSize));
else
buffers.first().allocate(chunkSize);
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
const QRingChunkEx &chunk = buffers.constFirst();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.head()) {
buffers.prepend(QRingChunkEx(chunkSize));
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
buffers.first().advance(-bytes);
}
}
bufferSize += bytes;
return buffers.first().data();
}
void QRingBufferEx::chop(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qint64 chunkSize = buffers.constLast().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunkEx &chunk = buffers.last();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
chunk.grow(-bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeLast();
}
}
void QRingBufferEx::clear()
{
if (buffers.isEmpty())
return;
buffers.erase(buffers.begin() + 1, buffers.end());
buffers.first().clear();
bufferSize = 0;
}
qint64 QRingBufferEx::indexOf(char c, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
if (maxLength == 0)
return -1;
qint64 index = -pos;
for (const QRingChunkEx &chunk : buffers) {
const qint64 nextBlockIndex = qMin(index + chunk.size(), maxLength);
if (nextBlockIndex > 0) {
const char *ptr = chunk.data();
if (index < 0) {
ptr -= index;
index = 0;
}
const char *findPtr = reinterpret_cast<const char *>(memchr(ptr, c,
nextBlockIndex - index));
if (findPtr)
return qint64(findPtr - ptr) + index + pos;
if (nextBlockIndex == maxLength)
return -1;
}
index = nextBlockIndex;
}
return -1;
}
qint64 QRingBufferEx::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;
}
/*!
\internal
Read an unspecified amount (will read the first buffer)
*/
QByteArray QRingBufferEx::read()
{
if (bufferSize == 0)
return QByteArray();
bufferSize -= buffers.constFirst().size();
return buffers.takeFirst().toByteArray();
}
/*!
\internal
Peek the bytes from a specified position
*/
qint64 QRingBufferEx::peek(char *data, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
qint64 readSoFar = 0;
for (const QRingChunkEx &chunk : buffers) {
if (readSoFar == maxLength)
break;
qint64 blockLength = chunk.size();
if (pos < blockLength) {
blockLength = qMin(blockLength - pos, maxLength - readSoFar);
memcpy(data + readSoFar, chunk.data() + pos, blockLength);
readSoFar += blockLength;
pos = 0;
} else {
pos -= blockLength;
}
}
return readSoFar;
}
/*!
\internal
Append bytes from data to the end
*/
void QRingBufferEx::append(const char *data, qint64 size)
{
Q_ASSERT(size >= 0);
if (size == 0)
return;
char *writePointer = reserve(size);
if (size == 1)
*writePointer = *data;
else
::memcpy(writePointer, data, size);
}
/*!
\internal
Append a new buffer to the end
*/
void QRingBufferEx::append(const QByteArray &qba)
{
if (bufferSize != 0 || buffers.isEmpty())
buffers.append(QRingChunkEx(qba));
else
buffers.last().assign(qba);
bufferSize += qba.size();
}
qint64 QRingBufferEx::readLine(char *data, qint64 maxLength)
{
Q_ASSERT(data != nullptr && maxLength > 1);
--maxLength;
qint64 i = indexOf('\n', maxLength);
i = read(data, i >= 0 ? (i + 1) : maxLength);
// Terminate it.
data[i] = '\0';
return i;
}
//QT_END_NAMESPACE