Qt实现环形缓冲区的两种方法

一个环形buffer,在尾部追加数据,从头部读取数据,适合用作IO的缓冲区。

详细介绍可参考:https://en.wikipedia.org/wiki/Circular_buffer

一.使用QList和QByteArray

这个方法参考的是Qt源码中的QRingBuffer类,这个类不是Qt API的一部分,所以Qt助手里是查不到的,它的存在只是为了服务其他的源码。

QRingBuffer的源文件在D:\Qt\Qt5.7.0\5.7\Src\qtbase\src\corelib\tools目录中,由qringbuffer_p.h和qringbuffer.cpp实现。

QRingBuffer实现的环形缓冲区大概如下图所示。

Qt实现环形缓冲区的两种方法_第1张图片

qringbuffer.h

#ifndef QRINGBUFFER_P_H
#define QRINGBUFFER_P_H

#include 
#include 

#ifndef QRINGBUFFER_CHUNKSIZE
#define QRINGBUFFER_CHUNKSIZE 4096
#endif
enum
{
    //1G-1字节
    MaxAllocSize = (1 << (std::numeric_limits::digits - 1)) - 1
};

enum
{
    //1G-1-16字节
    MaxByteArraySize = MaxAllocSize - sizeof(QtPrivate::remove_pointer::type)
};


class QRingBuffer
{
public:
    //默认分配QRINGBUFFER_CHUNKSIZE大小的buffer
    QRingBuffer(int growth = QRINGBUFFER_CHUNKSIZE) :
        head(0), tail(0), tailBuffer(0), basicBlockSize(growth), bufferSize(0) { }
    ~QRingBuffer(){}
    //获取环形缓冲区指定位置的指针
    //length,输出这个指定位置到缓冲区结尾的长度
    char *readPointerAtPosition(qint64 pos, qint64 &length);
    //申请空间:从尾开始,返回新空间的指针
    char *reserve(qint64 bytes);
    //申请空间:从头开始,返回新空间的指针
    char *reserveFront(qint64 bytes);
    //缩短空间
    void truncate(qint64 pos)
    {
        if (pos < bufferSize)
            chop(bufferSize - pos);
    }
    //判断buffers数据是否为空
    bool isEmpty()
    {
        return bufferSize == 0;
    }
    //从头读取一个字符,并转换为int返回
    int getChar()
    {
        if (isEmpty())
            return -1;
        char c = *readPointer();
        free(1);
        return int(uchar(c));
    }
    //在缓冲区尾部添加字符
    void putChar(char c)
    {
        char *ptr = reserve(1);
        *ptr = c;
    }
    //在缓冲区头部添加字符
    void ungetChar(char c)
    {
        if (head > 0) {
            --head;
            buffers.first()[head] = c;
            ++bufferSize;
        } else {
            char *ptr = reserveFront(1);
            *ptr = c;
        }
    }
    //清空缓冲区
    void clear();
    //读取maxLength长度数据到data中,如果buffers中的数据少于maxLength,则读取所有数据,
    //返回读取数据的长度
    qint64 read(char *data, qint64 maxLength);
    //读取buffers中的第一个buffer
    QByteArray read();
    //从指定位置pos拷贝maxLength长度的数据到data中
    //返回实际截取的数据长度
    qint64 peek(char *data, qint64 maxLength, qint64 pos = 0);
    //扩展最后一个buffer
    void append(const char *data, qint64 size);
    //在最后添加一个新buffer
    void append(const QByteArray &qba);
    //从头释放lenght长度空间,一般需要配合reserve使用
    qint64 skip(qint64 length)
    {
        qint64 bytesToSkip = qMin(length, bufferSize);

        free(bytesToSkip);
        return bytesToSkip;
    }
    //从尾释放length长度空间,一般需要配合reserve使用
    void chop(qint64 length);
    //读取一行,包括该行的结束标志'\n'
    qint64 readLine(char *data, qint64 maxLength);

    bool canReadLine()
    {
        return indexOf('\n', bufferSize) >= 0;
    }
private:
    //获取下一个数据块的大小
    //如果只剩一个buffer,返回最后一个buffer所含数据的大小;否则返回第一个buffer所含数据的大小。
    qint64 nextDataBlockSize()
    {
        return (tailBuffer == 0 ? tail : buffers.first().size()) - head;
    }
    //获取缓冲区第一个有效数据的指针
    char *readPointer()
    {
        return bufferSize == 0 ? Q_NULLPTR : (buffers.first().data() + head);
    }
    qint64 indexOf(char c, qint64 maxLength, qint64 pos = 0);
    //释放空间
    void free(qint64 bytes);
private:
    QList buffers;
    //标识第一个buffer数据起始位置和最后一个buffer数据的结尾位置
    int head, tail;
    //大小为buffers.size()-1,如果为0,说明只剩一个buffer
    int tailBuffer;
    //初始分配空间的大小
    int basicBlockSize;
    //buffers数据总大小
    qint64 bufferSize;
};

#endif // QRINGBUFFER_P_H
qringbuffer.cpp

#include "qringbuffer.h"
#include 

char *QRingBuffer::readPointerAtPosition(qint64 pos, qint64 &length)
{
    if (pos >= 0)
    {
        pos += head;
        for (int i = 0; i < buffers.size(); ++i)
        {
            length = (i == tailBuffer ? tail : buffers[i].size());
            if (length > pos)
            {
                length -= pos;
                return buffers[i].data() + pos;
            }
            pos -= length;
        }
    }

    length = 0;
    return 0;
}

void QRingBuffer::free(qint64 bytes)
{
    Q_ASSERT(bytes <= bufferSize);

    while (bytes > 0)
    {
        const qint64 blockSize = buffers.first().size() - head;
        if (tailBuffer == 0 || blockSize > bytes)
        {
            if (bufferSize <= bytes)
            {
                if (buffers.first().size() <= basicBlockSize)
                {
                    bufferSize = 0;
                    head = tail = 0;
                } else
                {
                    clear();
                }
            }
            else
            {
                Q_ASSERT(bytes < MaxByteArraySize);
                head += int(bytes);
                bufferSize -= bytes;
            }
            return;
        }
        bufferSize -= blockSize;
        bytes -= blockSize;
        buffers.removeFirst();
        --tailBuffer;
        head = 0;
    }
}

char *QRingBuffer::reserve(qint64 bytes)
{
    if (bytes <= 0 || bytes >= MaxByteArraySize)
        return 0;

    if (buffers.isEmpty())
    {
        buffers.append(QByteArray());
        buffers.first().resize(qMax(basicBlockSize, int(bytes)));
    }
    else
    {
        const qint64 newSize = bytes + tail;
        //如果超过最后一个buffer所含数据的大小,则最后一个buffer需要从新分配
        if (newSize > buffers.last().size())
        {
            //满足以下条件时,将最后一个buffer的容积缩小到其当前所含数据的大小,
            //然后新开辟一个buffer,并将该buffer数据的结尾位置tail设置为0
            if (newSize > buffers.last().capacity() && (tail >= basicBlockSize
                    || newSize >= MaxByteArraySize))
            {
                buffers.last().resize(tail);
                buffers.append(QByteArray());
                ++tailBuffer;
                tail = 0;
            }
            //将最后一个buffer进行扩容
            buffers.last().resize(qMax(basicBlockSize, tail + int(bytes)));
        }
    }
    char *writePtr = buffers.last().data() + tail;
    bufferSize += bytes;
    Q_ASSERT(bytes < MaxByteArraySize);
    tail += int(bytes);
    return writePtr;
}

char *QRingBuffer::reserveFront(qint64 bytes)
{
    if (bytes <= 0 || bytes >= MaxByteArraySize)
        return 0;

    if (head < bytes)
    {
        if (buffers.isEmpty())
        {
            buffers.append(QByteArray());
        }
        else
        {
            buffers.first().remove(0, head);
            if (tailBuffer == 0)
                tail -= head;
        }

        head = qMax(basicBlockSize, int(bytes));
        if (bufferSize == 0)
        {
            tail = head;
        }
        else
        {
            buffers.prepend(QByteArray());
            ++tailBuffer;
        }
        buffers.first().resize(head);
    }

    head -= int(bytes);
    bufferSize += bytes;
    return buffers.first().data() + head;
}

void QRingBuffer::chop(qint64 length)
{
    Q_ASSERT(length <= bufferSize);

    while (length > 0)
    {
        if (tailBuffer == 0 || tail > length)
        {
            if (bufferSize <= length)
            {
                if (buffers.first().size() <= basicBlockSize)
                {
                    bufferSize = 0;
                    head = tail = 0;
                }
                else
                {
                    clear();
                }
            }
            else
            {
                Q_ASSERT(length < MaxByteArraySize);
                tail -= int(length);
                bufferSize -= length;
            }
            return;
        }

        bufferSize -= tail;
        length -= tail;
        buffers.removeLast();
        --tailBuffer;
        tail = buffers.last().size();
    }
}

void QRingBuffer::clear()
{
    if (buffers.isEmpty())
        return;

    buffers.erase(buffers.begin() + 1, buffers.end());
    buffers.first().clear();

    head = tail = 0;
    tailBuffer = 0;
    bufferSize = 0;
}

qint64 QRingBuffer::indexOf(char c, qint64 maxLength, qint64 pos)
{
    if (maxLength <= 0 || pos < 0)
        return -1;

    qint64 index = -(pos + head);
    for (int i = 0; i < buffers.size(); ++i)
    {
        qint64 nextBlockIndex = qMin(index + (i == tailBuffer ? tail : buffers[i].size()),
                                           maxLength);

        if (nextBlockIndex > 0)
        {
            const char *ptr = buffers[i].data();
            if (index < 0)
            {
                ptr -= index;
                index = 0;
            }

            const char *findPtr = reinterpret_cast(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(bufferSize, 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;
}


QByteArray QRingBuffer::read()
{
    if (bufferSize == 0)
        return QByteArray();

    QByteArray qba(buffers.takeFirst());

    //避免调整大小时不必要的内存分配,使QByteArray更高效
    qba.reserve(0);
    if (tailBuffer == 0)
    {
        qba.resize(tail);
        tail = 0;
    } else
    {
        --tailBuffer;
    }
    qba.remove(0, head);
    head = 0;
    bufferSize -= qba.size();
    return qba;
}


qint64 QRingBuffer::peek(char *data, qint64 maxLength, qint64 pos)
{
    qint64 readSoFar = 0;

    if (pos >= 0)
    {
        pos += head;
        for (int i = 0; readSoFar < maxLength && i < buffers.size(); ++i)
        {
            qint64 blockLength = (i == tailBuffer ? tail : buffers[i].size());

            if (pos < blockLength)
            {
                blockLength = qMin(blockLength - pos, maxLength - readSoFar);
                memcpy(data + readSoFar, buffers[i].data() + pos, blockLength);
                readSoFar += blockLength;
                pos = 0;
            }
            else
            {
                pos -= blockLength;
            }
        }
    }

    return readSoFar;
}

void QRingBuffer::append(const char *data, qint64 size)
{
    char *writePointer = reserve(size);
    if (size == 1)
        *writePointer = *data;
    else if (size)
        ::memcpy(writePointer, data, size);
}

void QRingBuffer::append(const QByteArray &qba)
{
    if (tail == 0)
    {
        if (buffers.isEmpty())
            buffers.append(qba);
        else
            buffers.last() = qba;
    }
    else
    {
        buffers.last().resize(tail);
        buffers.append(qba);
        ++tailBuffer;
    }
    tail = qba.size();
    bufferSize += tail;
}

qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
{
    if (!data || --maxLength <= 0)
        return -1;

    qint64 i = indexOf('\n', maxLength);
    i = read(data, i >= 0 ? (i+1) : maxLength);

    data[i] = '\0';
    return i;
}
main.cpp

#include 
#include 

int main()
{
    //测试环形缓冲区的写入和读取+++++++++++++++++++++++++++++++
    qDebug()<
测试结果

Qt实现环形缓冲区的两种方法_第2张图片

二.使用QSemaphore

用定时器将当前时间存储到QStringList对象中,然后通过现场去QStringList对象中取出并打印,通过两个QSemaphore对象进行同步,形成环形缓冲区。

thread.h

#ifndef THREAD_H
#define THREAD_H

#include 
#include 
#include 

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    ~Thread();

protected:
    void run();

private slots:
   void slotTimeout();

};

#endif // THREAD_H
thread.cpp

#include "thread.h"
#include 
#include 
#include 
//1024是这个list的缓冲区大小,当然可以随意设定;80的意思是初始空闲的缓冲区大小是1024
QSemaphore freeBytes(1024);
//初始被使用的缓冲区大小是0
QSemaphore usedBytes(0);
QStringList timeList;
Thread::Thread()
{
    QTimer *timer=new QTimer(this);
    connect(timer,SIGNAL(timeout()),this,SLOT(slotTimeout()));
    timer->start(50);
}

Thread::~Thread()
{

}

void Thread::run()
{
    //获取并打印list中的时间
    while(true)
    {
        usedBytes.acquire();
        qDebug()<
main.cpp

#include 
#include "thread.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Thread *thread=new Thread;
    thread->start();
    return a.exec();
}
测试结果

Qt实现环形缓冲区的两种方法_第3张图片





你可能感兴趣的:(Qt)