【循环数组】使用循环数组实现PCM数据按固定大小读取

循环数组

对于需要积攒到一定数据量才取出的数据可以使用缓存储存起来,缓存的方式有许多种,这里介绍一个循环数组的缓存形式。

循环数组只是对他处理方式上的叫法,他处理的方式就像一个首尾相接的数组,维护一个头部索引和一个尾部索引。然后一直循环转圈圈的存储和写入数据。 初始化的样子

初始化的样子

写入4个数据后的样子

写入4个数据后的样子

读取2个数据后的样子

读取2个数据后的样子

然而数组的存储空间在内存中是线性排列的,没有首尾相接的排列方式,这个就需要我们用代码控制来模拟他是可以首尾相接来存取数据。申请6个数据空间,初始化如下图。 初始化

初始化

重复上面上面的写入写出部分如下图。

写入4个数据,有效数据为4

写入4个数据,有效数据为4

读取2个数据,有效数据为2

读取2个数据,有效数据为2

当再次写入数据并且索引超过最大索引的时候,写入指针就会重新从0开始移动。下图演示了接着往数组里面写入4个数据。

**注意:**除了维护写入索引和读取索引,还需要维护有效数据个数。每次写入的最大数据个数 = 数组长度 - 有效数据个数。

写入1个数据后,写入索引移动到最大值,有效数据为3

写入1个数据后,写入索引移动到最大值,有效数据为3

写入第2个数据后,写入索引移动到初始0位置继续写入,有效数据为4

写入第2个数据后,写入索引移动到初始0位置继续写入,有效数据为4

4个数据写入完毕,有效数据为6等于数组的长度,此时缓存区满不能再写入数据。

4个数据写入完毕,有效数据为6等于数组的长度,此时缓存区满不能再写入数据。

读取数据涉及到最大索引的处理方式是一样的。

下面是代码实现:

public class CircularBuffer
{
    //缓存数组
    private readonly byte[] buffer;
    //锁对象
    private readonly object lockObject;
    //写入索引
    private int writePosition;
    //读取索引
    private int readPosition;
    //有效数据个数
    private int byteCount;

    public CircularBuffer(int length)
    {
        buffer = new byte[length];
        lockObject = new object();
        ResetInner();
    }

    /// <summary>
    /// Number of bytes currently stored in the circular buffer
    /// </summary>
    public int Count
    {
        get
        {
            lock (lockObject)
            {
                return byteCount;
            }
        }
    }
    /// <summary>
    /// 写入数据
    /// <param name="data">要写入缓存的数组</param>
    /// <param name="offset">读取data的起始索引</param>
    /// <param name="count">读取多少个数据</param>
    /// <returns>缓存了多少个数据</returns>
    /// </summary>
    public int Write(byte[] data,int offset,int count)
    {
        lock (lockObject)
        {
            //表示已经写入的数据
            var bytesWritten = 0;
            if(count > buffer.Length - byteCount)
            {
                //最多只能写这么多数据
                count = buffer.Length - byteCount;
            }
            //将从写入头部到数组末尾的长度与要写入的数据长度进行比较,找出最小的,然后进行分段循环从头写入。
            int writeToEnd = Math.Min(buffer.Length - writePosition,count);
            Array.Copy(data, offset, buffer, writePosition, writeToEnd);
            //更新写入索引位置
            writePosition += writeToEnd;
            writePosition %= buffer.Length;
            //更新写入多少数据
            bytesWritten += writeToEnd;
            //如果写入的数据小于要写入的数量就说明此时索引要从0开始写入
            if (bytesWritten < count)
            {
                //说明一次只写了一部分数据,第二部分数据从头开始写入
                Debug.Assert(writePosition == 0);
                //写入剩余的数据,count-bytesWritten个数据。
                Array.Copy(data,offset+bytesWritten,buffer,writePosition,count-bytesWritten);
                //更新写入索引位置(这时候一定不会超过索引最大值)
                writePosition += count - bytesWritten;
                bytesWritten = count;
            }
            //有效数据更新
            byteCount += bytesWritten;
            return bytesWritten;
        }
    }
    /// <summary>
    /// 读取数据
    /// <param name="data">读取时写入的数组</param>
    /// <param name="offset">写入data的起始索引</param>
    /// <param name="count">写入多少个数据</param>
    /// <returns>写进去了多少个数据</returns>
    /// </summary>
    public int Read(byte[] data ,int offset,int count)
    {
        lock (lockObject)
        {
            if (count > byteCount)
            {
                count = byteCount;
            }
            //表示读取了多少个字节
            int bytesRead = 0;
            //获取第一次读取多少数据。
            int readToEnd = Math.Min(buffer.Length - readPosition, count);
            Array.Copy(buffer, readPosition, data, offset, readToEnd);
            readPosition += readToEnd;
            readPosition %= buffer.Length;
            bytesRead += readToEnd;

            if (readToEnd < count)
            {
                Debug.Assert(readPosition==0);
                Array.Copy(buffer, readPosition, data, offset + bytesRead, count - bytesRead);
                readPosition += count - bytesRead;
                bytesRead = count;
            }
            byteCount -= bytesRead;
            Debug.Assert(byteCount>=0);
            return bytesRead;
        }
    }
    /// <summary>
    /// 复位
    /// </summary>
    public void Reset()
    {
        lock (lockObject)
        {
            resetInner();
        }
    }

    /// <summary>
    /// 内部复位
    /// </summary>
    private void resetInner()
    {
        byteCount = 0;
        readPosition = 0;
        writePosition = 0;
    }
}
复制代码

选用这种方式的原因

只用创建一次够大的数组就可以循环使用,其中的数据也保持了原有的连贯性。

需要注意的

要确保数据再一定周期内可以处理完毕,不会因为处理不及时而积累,最后超过最大数组空间。这里做了最大数据判断,如果超出的数据不会写入到缓存中,换句话说数据直接丢了。

Guess you like

Origin juejin.im/post/7050005909661024270