Java 环形缓冲区(Ring Buffer)

更新于 2025-12-25

Jakob Jenkov 发布于 2015-09-30

环形缓冲区是一种用作队列的数组。它包含一个读位置和一个写位置,分别标记下一个要从中读取和写入的位置。当写位置到达数组末尾时,会重置为 0;读位置同理。这种“回绕”(wrapping around)的行为使数组表现得像一个环,因此得名“环形缓冲区”。

本教程将解释环形缓冲区的工作原理,并展示两种 Java 实现方式。


环形缓冲区的工作原理

环形缓冲区是一个固定大小的数组,类似于有界队列。数组中存储着缓冲区中的元素。

除了元素数组外,环形缓冲区还包含:

  • 写位置(write position):指向下一个要插入元素的位置。
  • 读位置(read position):指向下一个要读取元素的位置。
  • 已用/空闲空间信息:用于判断缓冲区是否满或空。

情况一:写位置尚未回绕

此时,已用空间位于读位置到写位置之间,其余为空闲空间。

情况二:写位置已经回绕

此时,空闲空间位于写位置到读位置之间,其余为已用空间。

有多种方式跟踪读写位置和缓冲区状态。下面将介绍两种 Java 实现方式。


环形缓冲区为何高效?

环形缓冲区是实现类队列结构的一种高效方式——既易于实现,又性能出色。


使用场景

  • 实际的队列(如消息队列)
  • 数据生产者-消费者模型(流式处理)
  • 需要严格限制缓冲区最大容量的场景

如果不需要容量上限,可考虑使用链表,或支持动态扩容的环形缓冲区(在满时分配更大的数组并迁移数据)。
本教程仅关注有界环形缓冲区。


实现技巧

本文介绍两种简单高效的实现方式:

  1. 使用填充计数(Fill Count)
  2. 使用翻转标记(Flip Marker)

基于填充计数的实现

该方法维护:

  • writePos:下一个写入位置
  • available(即 fill count):当前缓冲区中元素数量

读位置通过 writePos - available 动态计算。若结果为负,则加上缓冲区容量。

public class RingBufferFillCount {
    public Object[] elements = null;
    private int capacity = 0;
    private int writePos = 0;
    private int available = 0;

    public RingBufferFillCount(int capacity) {
        this.capacity = capacity;
        this.elements = new Object[capacity];
    }

    public void reset() {
        this.writePos = 0;
        this.available = 0;
    }

    public int capacity() { return this.capacity; }
    public int available() { return this.available; }
    public int remainingCapacity() { return this.capacity - this.available; }

    public boolean put(Object element) {
        if (available < capacity) {
            if (writePos >= capacity) {
                writePos = 0;
            }
            elements[writePos] = element;
            writePos++;
            available++;
            return true;
        }
        return false;
    }

    public Object take() {
        if (available == 0) {
            return null;
        }
        int nextSlot = writePos - available;
        if (nextSlot < 0) {
            nextSlot += capacity;
        }
        Object nextObj = elements[nextSlot];
        available--;
        return nextObj;
    }
}

基于翻转标记的实现

该方法显式维护:

  • readPos:下一个读取位置
  • writePos:下一个写入位置
  • flipped:标记写位置是否已回绕

元素数量根据是否翻转分别计算:

  • 未翻转:writePos - readPos
  • 已翻转:capacity - readPos + writePos
public class RingBufferFlipMarker {
    public Object[] elements = null;
    public int capacity = 0;
    public int writePos = 0;
    public int readPos = 0;
    public boolean flipped = false;

    public RingBufferFlipMarker(int capacity) {
        this.capacity = capacity;
        this.elements = new Object[capacity];
    }

    public void reset() {
        this.writePos = 0;
        this.readPos = 0;
        this.flipped = false;
    }

    public int available() {
        if (!flipped) {
            return writePos - readPos;
        }
        return capacity - readPos + writePos;
    }

    public int remainingCapacity() {
        if (!flipped) {
            return capacity - writePos;
        }
        return readPos - writePos;
    }

    public boolean put(Object element) {
        if (!flipped) {
            if (writePos == capacity) {
                writePos = 0;
                flipped = true;
                if (writePos < readPos) {
                    elements[writePos++] = element;
                    return true;
                } else {
                    return false;
                }
            } else {
                elements[writePos++] = element;
                return true;
            }
        } else {
            if (writePos < readPos) {
                elements[writePos++] = element;
                return true;
            } else {
                return false;
            }
        }
    }

    public Object take() {
        if (!flipped) {
            if (readPos < writePos) {
                return elements[readPos++];
            } else {
                return null;
            }
        } else {
            if (readPos == capacity) {
                readPos = 0;
                flipped = false;
                if (readPos < writePos) {
                    return elements[readPos++];
                } else {
                    return null;
                }
            } else {
                return elements[readPos++];
            }
        }
    }
}

性能对比

  • 单元素操作:基于填充计数的实现略快(但差距极小)。
  • 批量操作:两种实现均显著优于单元素操作(吞吐量最高提升 4 倍)。
  • 批量性能:基于翻转标记的实现比填充计数快约 15%。

批量操作因减少方法调用开销,在紧凑的数组复制循环中效率更高。


批量操作实现

填充计数版(含批量操作)

public class RingBufferFillCount {
    // ...(字段与构造函数同上)

    public int put(Object[] newElements) {
        return put(newElements, newElements.length);
    }

    public int put(Object[] newElements, int length) {
        int readPos = 0;
        if (this.writePos > this.available) {
            if (length <= this.capacity - this.writePos) {
                for (; readPos < length; readPos++) {
                    this.elements[this.writePos++] = newElements[readPos];
                }
                this.available += readPos;
                return length;
            } else {
                // 需同时使用数组顶部和底部空间
                int lastEmptyPos = writePos - available;
                for (; this.writePos < this.capacity; this.writePos++) {
                    this.elements[this.writePos] = newElements[readPos++];
                }
                this.writePos = 0;
                int endPos = Math.min(length - readPos, capacity - available - readPos);
                for (; this.writePos < endPos; this.writePos++) {
                    this.elements[this.writePos] = newElements[readPos++];
                }
                this.available += readPos;
                return readPos;
            }
        } else {
            int endPos = this.capacity - this.available + this.writePos;
            for (; this.writePos < endPos; this.writePos++) {
                this.elements[this.writePos] = newElements[readPos++];
            }
            this.available += readPos;
            return readPos;
        }
    }

    public int take(Object[] into) {
        return take(into, into.length);
    }

    public int take(Object[] into, int length) {
        int intoPos = 0;
        if (available <= writePos) {
            int nextPos = writePos - available;
            int endPos = nextPos + Math.min(available, length);
            for (; nextPos < endPos; nextPos++) {
                into[intoPos++] = this.elements[nextPos];
            }
            this.available -= intoPos;
            return intoPos;
        } else {
            int nextPos = writePos - available + capacity;
            int leftInTop = capacity - nextPos;
            if (length <= leftInTop) {
                for (; intoPos < length; intoPos++) {
                    into[intoPos] = this.elements[nextPos++];
                }
                this.available -= length;
                return length;
            } else {
                // 复制顶部
                for (; nextPos < capacity; nextPos++) {
                    into[intoPos++] = this.elements[nextPos];
                }
                // 复制底部(从0到writePos)
                nextPos = 0;
                int leftToCopy = length - intoPos;
                int endPos = Math.min(writePos, leftToCopy);
                for (; nextPos < endPos; nextPos++) {
                    into[intoPos++] = this.elements[nextPos];
                }
                this.available -= intoPos;
                return intoPos;
            }
        }
    }
}

翻转标记版(含批量操作)

public class RingBufferFlip {
    // ...(字段与构造函数同上)

    public int put(Object[] newElements, int length) {
        int newElementsReadPos = 0;
        if (!flipped) {
            if (length <= capacity - writePos) {
                for (; newElementsReadPos < length; newElementsReadPos++) {
                    this.elements[this.writePos++] = newElements[newElementsReadPos];
                }
                return newElementsReadPos;
            } else {
                // 写入顶部
                for (; this.writePos < capacity; this.writePos++) {
                    this.elements[this.writePos] = newElements[newElementsReadPos++];
                }
                // 写入底部
                this.writePos = 0;
                this.flipped = true;
                int endPos = Math.min(this.readPos, length - newElementsReadPos);
                for (; this.writePos < endPos; this.writePos++) {
                    this.elements[writePos] = newElements[newElementsReadPos++];
                }
                return newElementsReadPos;
            }
        } else {
            // free space: writePos 到 readPos
            int endPos = Math.min(this.readPos, this.writePos + length);
            for (; this.writePos < endPos; this.writePos++) {
                this.elements[this.writePos] = newElements[newElementsReadPos++];
            }
            return newElementsReadPos;
        }
    }

    public int take(Object[] into, int length) {
        int intoWritePos = 0;
        if (!flipped) {
            // 可用空间: readPos 到 writePos
            int endPos = Math.min(this.writePos, this.readPos + length);
            for (; this.readPos < endPos; this.readPos++) {
                into[intoWritePos++] = this.elements[this.readPos];
            }
            return intoWritePos;
        } else {
            if (length <= capacity - readPos) {
                // 直接从顶部复制
                for (; intoWritePos < length; intoWritePos++) {
                    into[intoWritePos] = this.elements[this.readPos++];
                }
                return intoWritePos;
            } else {
                // 复制顶部
                for (; this.readPos < capacity; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }
                // 复制底部
                this.readPos = 0;
                this.flipped = false;
                int endPos = Math.min(this.writePos, length - intoWritePos);
                for (; this.readPos < endPos; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }
                return intoWritePos;
            }
        }
    }
}

并发安全性

  • 上述两种实现均非线程安全,仅适用于单线程。
  • 单生产者-单消费者场景下,基于翻转标记的实现更易改造为线程安全版本(因读写位置分离)。