【问题标题】:circular buffer implementation循环缓冲区实现
【发布时间】:2016-08-10 14:45:18
【问题描述】:

是的,我又来了一个非常直接的实现,就像这样:

    // write data always! if buffer is already full, overwrite old data!
    void Put( const CONTENT_TYPE &data )
    {
        buffer[ inOffset++] = data;
        inOffset%=size;

        // was data overwritten, skip it by increment read offset
        if ( inOffset == outOffset ) 
        {
            outOffset++;
            outOffset%=size;
            std::cout << "Overwrite" << std::endl;
        }
    }

    CONTENT_TYPE Pull()
    {
        CONTENT_TYPE data = buffer[ outOffset++ ];
        outOffset %= size;
        return data;
    }

但是这个简单的算法只使用了 size-1 个缓冲区的元素!

如果我想避免这种情况,我只找到了添加另一个计数器变量的解决方案,这浪费了我 sizeof(counter_var) - sizeof(element) 个字节。

问:有没有不浪费内存的解决方案?它看起来如此简单,但我无法理解:-)

备注:还有一些代码行可以保护空读取和其他内容,但这对问题并不重要。而且它没有被标记为 c++,因为算法不依赖于语言,如果我给出一个 c++ 代码示例也是如此。

【问题讨论】:

  • 您的第一句话似乎表明这是对某事而不是问题的答案;也许您想改写一下;此外,语言标记总是一个好主意。
  • @MarcusMüller:改变了第一句话,语言对于这里的算法并不重要。
  • 我认为您可以通过将偏移变量之一替换为大小变量来解决此问题。
  • @Klaus 我谦虚地不同意
  • @klaus 我仍然不能 100% 确定我理解您要解决的 问题。您能否更清楚地说明您真正想要实现的内容?

标签: c++ algorithm circular-buffer


【解决方案1】:

如果一个是索引,另一个是元素计数,您可以使用两个整数并填充所有槽,然后转换以即时找到第二个索引:

void put(const ELEMENT& element) {
  if (nElements == size) throw "put: buffer full";
  buffer[(start + nElements++) % size] = element;
}

ELEMENT get() {
  if (nElements == 0) throw "get: buffer empty";
  ELEMENT& value = buffer[start];
  start = (start + 1) % size;
  --nElements;
  return value;
}

当然你可以用if (foo &gt; size) foo -= size;替换mod操作。

【讨论】:

    【解决方案2】:

    您只需使用执行模运算的不同时间点来处理它;假设我们在每次访问后增加读写指针。如果我们现在在增加后立即对读指针取模,在读之前对写指针取模,|write-read|一个完整缓冲区的长度将是缓冲区的长度,没有任何特殊情况处理。为此,您的写指针应始终使用 % buffer_length,但存储 % (2 * buffer_length)

    我不是特别喜欢 Mark 的回答,因为将事情作为特殊情况处理通常不是一个好主意,只要在您通常使用 size_t 的地方引入负标记值(即无符号整数) )。

    【讨论】:

    • 这不是循环缓冲区的工作方式。你只增加一个偏移量或另一个,这取决于你是在阅读还是在写作;唯一的例外是当您因为缓冲区已满而覆盖时,这需要修改其他偏移量以擦除最旧的元素。在您的计划中,您如何区分空缓冲区和满缓冲区?附言我也不喜欢我的答案,但这是我能想到的最好的满足限制条件的答案——接受 1 个元素容量的损失要容易得多。
    • 确切地说,您正在增加读取或写入指针。通过在何时“重置”为零时使用不同的语义,您可以保留读写指针差异中包含的完整/空信息。空缓冲区:读==写指针;完整的缓冲区读取!= 写入,但是(读取 + 缓冲区长度)% 缓冲区长度 = 写入
    • 啊,我应该补充一点,写入缓冲区存在于两倍的缓冲区长度上,并且只是被别名为单个长度。
    • 魔鬼在细节中。如果将length*2写入缓冲区,则写入索引将再次换行,并且再次看起来为空。
    【解决方案3】:

    您可以为其中一个偏移量使用特殊的标记值,例如 -1,以指示缓冲区已满或为空。这将使您的代码检查和修改偏移量变得复杂。

    // write data always! if buffer is already full, overwrite old data!
    void Put( const CONTENT_TYPE &data )
    {
        buffer[ inOffset++] = data;
        inOffset%=size;
    
        // was data overwritten, skip it by setting read offset to sentinel
        if ( inOffset == outOffset || outOffset == -1 ) 
        {
            outOffset = -1;
            std::cout << "Overwrite" << std::endl;
        }
    }
    
    CONTENT_TYPE Pull()
    {
        if (outOffset == -1)
            outOffset = inOffset;
        CONTENT_TYPE data = buffer[ outOffset++ ];
        outOffset %= size;
        return data;
    }
    
    bool IsEmpty()
    {
        return outOffset == inOffset;
    }
    

    【讨论】:

    • (您可能想指出 使用 sentinel values 的优点。)
    • @greybeard 不确定是否有。如果您有不同的线程调用 PutPull,这几乎但不是完全无锁。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-02
    • 2012-11-17
    • 2010-10-24
    • 1970-01-01
    相关资源
    最近更新 更多