当前位置: 首页 > news >正文

QRingBuffer:Qt内部高效环形缓冲区

目录

1.简介

2.实现分析

2.1.QRingChunk:数据片段的 “原子管理单元”

2.2.QRingBuffer:多片段的 “容器与调度中心”

3.设计亮点总结

4.使用建议

5.总结

6.直接可以使用的代码


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

/****************************************************************************
**
** 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
#endifclass QRingChunk
{
public:// initialization and cleanupinline 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 sharingvoid allocate(int alloc);inline bool isShared() const{return !chunk.isDetached();}Q_CORE_EXPORT void detach();QByteArray toByteArray();// gettersinline 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 managementinline 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

/****************************************************************************
**
** 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_NAMESPACEvoid 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 reallocateschunk.resize(tailOffset);}return chunk;
}/*!\internalAccess the bytes at a specified position the out-variable length willcontain the amount of bytes readable from there, e.g. the amount stillthe 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 bufferif (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));elsebuffers.first().allocate(chunkSize);} else {const QRingChunk &chunk = buffers.constLast();// if need a new bufferif (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.available())buffers.append(QRingChunk(chunkSize));elsetail = chunk.size();}buffers.last().grow(bytes);bufferSize += bytes;return buffers.last().data() + tail;
}/*!\internalAllocate 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));elsebuffers.first().allocate(chunkSize);buffers.first().grow(chunkSize);buffers.first().advance(chunkSize - bytes);} else {const QRingChunk &chunk = buffers.constFirst();// if need a new bufferif (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 bufferif (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;
}/*!\internalRead an unspecified amount (will read the first buffer)
*/
QByteArray QRingBuffer::read()
{if (bufferSize == 0)return QByteArray();bufferSize -= buffers.constFirst().size();return buffers.takeFirst().toByteArray();
}/*!\internalPeek 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;
}/*!\internalAppend 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);
}/*!\internalAppend a new buffer to the end
*/
void QRingBuffer::append(const QByteArray &qba)
{if (bufferSize != 0 || buffers.isEmpty())buffers.append(QRingChunk(qba));elsebuffers.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 = 0chunk 为空),无内存分配。
  • 指定大小构造explicit QRingChunk(int alloc)创建一个容量为 alloc 的未初始化片段(Qt::Uninitialized 避免默认零初始化开销),有效数据为空(head=tail=0),用于提前预留内存。
  • 从 QByteArray 构造explicit QRingChunk(const QByteArray &qba)直接复用 qba 的内存(利用 COW,不复制数据),有效数据范围为整个 qbahead=0tail=qba.size()),适用于直接封装已有字节数组。
  • 拷贝构造 / 赋值QRingChunk(const QRingChunk &other)浅拷贝 chunk(COW 特性,仅复制指针)和偏移量,仅在后续修改时才通过 detach() 复制数据,减少初始拷贝开销。
  • 移动构造 / 赋值QRingChunk(QRingChunk &&other)窃取 other 的 chunk 和偏移量,other 重置为空白状态(head=tail=0),零拷贝,适合片段转移场景(如 QVector 扩容时的元素移动)。

(2)swap 方法:高效交换片段

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 字节空间,返回可写指针。

  1. 若缓冲区为空,复用首个片段或新建 QRingChunk(大小为 basicBlockSize);
  2. 若缓冲区非空,检查最后一个片段的 available() 空间:
    • 空间足够:直接使用该片段;
    • 空间不足:新建 QRingChunk(大小为 max(basicBlockSize, bytes)),追加到 buffers
  3. 调用最后一个片段的 grow(bytes) 扩展有效范围,更新 bufferSize,返回可写指针(chunk.data() + 原有 tailOffset)。

reserveFront(qint64 bytes):在头部预留 bytes 字节空间,返回可写指针(支持 “头部写入”,如协议头部封装)。

逻辑与 reserve 对称:检查第一个片段的头部空闲空间(headOffset),不足则在 buffers 头部插入新 QRingChunk,调整 headOffset 后返回指针。

(2)读取相关:头部消费与预览

free(qint64 bytes):从头部释放 bytes 字节(消费数据)。

  1. 若第一个片段的 size() > bytes:调用 advance(bytes) 调整偏移量,bufferSize -= bytes
  2. 若第一个片段的 size() <= bytes:删除该片段(buffers.removeFirst()),bufferSize -= 片段大小,继续处理剩余 bytes
  3. 若释放全部数据,复用小容量片段(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> 扩容开销。

// 已知需写入 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() 整个缓冲区。
// 循环使用缓冲区,避免每次清空后重建片段
QRingBuffer buffer;
while (isRunning) {// 写入数据buffer.append(data, len);// 读取处理processBuffer(buffer);// 重置首个片段(复用内存,比 clear() 更轻量)if (!buffer.buffers.isEmpty()) {buffer.buffers.first().reset();buffer.bufferSize = 0;}
}

4.线程安全需外部保障

类定义中无任何同步机制(如 QMutex),多线程读写时需手动加锁,避免片段状态不一致。

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

/****************************************************************************
**
** 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
#endifclass QRingChunkEx
{
public:// initialization and cleanupinline 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 sharingvoid allocate(int alloc);inline bool isShared() const{return !chunk.isDetached();}void detach();QByteArray toByteArray();// gettersinline 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 managementinline 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

/****************************************************************************
**
** 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 reallocateschunk.resize(tailOffset);}return chunk;
}/*!\internalAccess the bytes at a specified position the out-variable length willcontain the amount of bytes readable from there, e.g. the amount stillthe 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 bufferif (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));elsebuffers.first().allocate(chunkSize);} else {const QRingChunkEx &chunk = buffers.constLast();// if need a new bufferif (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.available())buffers.append(QRingChunkEx(chunkSize));elsetail = chunk.size();}buffers.last().grow(bytes);bufferSize += bytes;return buffers.last().data() + tail;
}/*!\internalAllocate 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));elsebuffers.first().allocate(chunkSize);buffers.first().grow(chunkSize);buffers.first().advance(chunkSize - bytes);} else {const QRingChunkEx &chunk = buffers.constFirst();// if need a new bufferif (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 bufferif (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;
}/*!\internalRead an unspecified amount (will read the first buffer)
*/
QByteArray QRingBufferEx::read()
{if (bufferSize == 0)return QByteArray();bufferSize -= buffers.constFirst().size();return buffers.takeFirst().toByteArray();
}/*!\internalPeek 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;
}/*!\internalAppend 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);
}/*!\internalAppend a new buffer to the end
*/
void QRingBufferEx::append(const QByteArray &qba)
{if (bufferSize != 0 || buffers.isEmpty())buffers.append(QRingChunkEx(qba));elsebuffers.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
http://www.dtcms.com/a/536749.html

相关文章:

  • 单位如何做网站宣传高端网站建设专业营销团队
  • 建设企业网站步骤上海做网站的公司是什么
  • 系统接口对接如何设计预防处理系统异常?
  • LeetCode算法日记 - Day 85: 等差数列划分
  • Maven相关
  • 京东商品视频API:通过商品id商品链接获取商品的视频url
  • Maven快速上手笔记
  • 手机免费制作网站模板免费下载台州网站公司那里好
  • 企业网站哪家好网站流量超标
  • 钢结构网站建设网站建设必备
  • 数据分析综合应用实战:从统计分析到机器学习预测
  • 网站 源码 下载色无极网站正在建设中
  • 【测试理论和实践 2.测试概念】
  • 算法 day 36
  • 【论文阅读】图数据库 Survey: Graph Databases
  • 长沙优化网站关键词合肥政务新区建设局网站
  • 化学网站定制简网app工场怎么创app
  • 今天我们学习Linux架构中的Redis数据库基础
  • 网站商城系统教资报名网站设置
  • 引入实时 3D 渲染技术,地平线与 Unity 开启车载交互空间化时代
  • 用狸窝转换器转换视频后文件变大的问题排查
  • Ansible自动化部署Harbor私有仓库指南④
  • AI模型开发 | 基于AutoDL部署Deepseek OCR模型,从零打造OCR应用平台
  • 网站建设微金手指下拉15价目表app制作
  • 基于深度学习的户口本识别技术通过智能图像处理、文字定位和语义理解,实现99%以上的高精度识别
  • 在线视频教育网站开发公司注册资金最低多少
  • JVM学习第一章
  • Promise 详解
  • [nanoGPT] 性能与效率 | `torch.compile()` |`Flash Attention`|`混合精度训练`|`estimate_mfu`
  • 异常日志不打印堆栈?谈谈 JVM 的 Fast Throw