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

目录

1.简介

2.实现分析

[2.1.QRingChunk:数据片段的 "原子管理单元"](#2.1.QRingChunk:数据片段的 “原子管理单元”)

[2.2.QRingBuffer:多片段的 "容器与调度中心"](#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

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 = 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)窃取 otherchunk 和偏移量,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 头部删除的性能问题:

  • 仅当第一个片段的有效数据被完全消费(freechunk.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> 扩容开销。

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
相关推荐
江公望3 小时前
如何在Qt QML中定义枚举浅谈
开发语言·qt·qml
苏纪云3 小时前
算法<C++>——双指针操作链表
c++·算法·链表·双指针
louisdlee.4 小时前
扫描线1:朴素扫描线
数据结构·c++·算法·扫描线
仰泳的熊猫4 小时前
LeetCode:1905. 统计子岛屿
数据结构·c++·算法·leetcode
月夜的风吹雨5 小时前
【C++ string 类实战指南】:从接口用法到 OJ 解题的全方位解析
c++·接口·string·范围for·auto·力扣oj
OKkankan5 小时前
模板的进阶
开发语言·数据结构·c++·算法
拾光Ծ5 小时前
【高阶数据结构】哈希表
数据结构·c++·哈希算法·散列表
终焉代码5 小时前
【C++】C++11特性学习(1)——列表初始化 | 右值引用与移动语义
c语言·c++·学习·1024程序员节
枫叶丹45 小时前
【Qt开发】容器类控件(二)-> QTabWidget
开发语言·qt