ArrayBuilderQueue

By | 2011.06.02

… is that the right name?

It basically works like a combination of the StringBuilder and Queue objects. You enqueue arrays of T, and you dequeue custom sized chunks of the array. What makes it nice is that I’ve optimized memory usage so it never creates any temp arrays internally except the one returned to you when you eat (of course).

Added some comments in case you need to change anything.

    /// <summary>
    /// Works like a Queue object except it enqueues arrays and lets you dequeue custom sized chunks of the arrays enqueued
    /// </summary>
    /// <typeparam name="T">Array type</typeparam>
    public class ArrayBuilderQueue<T>
    {
        private List<Array> buffer = new List<Array>();
        private int currentArrayPos = 0;
        public int Length { get; private set; }

        /// <summary>
        /// Adds an array to the queue
        /// </summary>
        /// <param name="items">Array of items to add</param>
        public void Enqueue(T[] items)
        {
            lock (buffer)
            {
                if (items == null)
                    return;
                buffer.Add(items);
                Length += items.Length;
            }
        }

        public void Clear()
        {
            lock (buffer)
            {
                buffer.Clear();
                Length = 0;
                currentArrayPos = 0;
            }
        }

        /// <summary>
        /// Dequeues items from buffer.
        /// </summary>
        /// <param name="count">Number of items</param>
        /// <returns>Array of items</returns>
        public T[] Dequeue(int count)
        {
            lock (buffer)
            {
                count = Math.Min(count, Length);
                T[] ret = new T[count];
                int eaten = 0;
                int retPos = 0;
                while (eaten < count)
                {
                    // Check how much remains of first array
                    int bl = 0;
                    if (buffer.Count > 0)
                        bl = buffer[0].Length;
                    int remaining = bl - currentArrayPos;
                    // Nothing?
                    while (remaining == 0 && buffer.Count > 0)
                    {
                        // Remove it and recalculate
                        buffer.RemoveAt(0);
                        currentArrayPos = 0;
                        if (buffer.Count > 0)
                        {
                            // Still got some, try next
                            if (buffer[0] != null)
                                remaining = buffer[0].Length - currentArrayPos;
                        }
                    }
                    // Copy data to return var
                    int eatSize = Math.Min(remaining, count - eaten);
                    if (eatSize > 0)
                        Array.Copy(buffer[0], currentArrayPos, ret, retPos, eatSize);
                    // Update internal tracking
                    retPos += eatSize; // Where we are in buffer we are copying to
                    currentArrayPos += eatSize; // Where we are in current array after eating
                    eaten += eatSize; // How much we have eaten (main loop)
                    Length -= eatSize; // Length was just decreased
                }

                return ret;
            }
        }
    }

And the unit test if you want (doesn’t cover all scenarios):

[TestMethod]
public void BufferBuilder()
{
    Common.Utils.ArrayBuilderQueue<byte> bufferBuilder = new ArrayBuilderQueue<byte>();
    bufferBuilder.Enqueue(System.Text.Encoding.ASCII.GetBytes("This is a test\r\n"));
    bufferBuilder.Enqueue(System.Text.Encoding.ASCII.GetBytes("Of the "));
    bufferBuilder.Enqueue(System.Text.Encoding.ASCII.GetBytes("BufferBuilder class.\r\n"));
    bufferBuilder.Enqueue(System.Text.Encoding.ASCII.GetBytes("Have fun!\r\n"));
    string str = null;
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(8)); Assert.IsTrue(str == "This is ");
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(8)); Assert.IsTrue(str == "a test\r\n");
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(7)); Assert.IsTrue(str == "Of the ");
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(20)); Assert.IsTrue(str == "BufferBuilder class.");
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(13)); Assert.IsTrue(str == "\r\nHave fun!\r\n");
    bufferBuilder.Clear();
    bufferBuilder.Enqueue(System.Text.Encoding.ASCII.GetBytes("1234567890"));
    str = System.Text.Encoding.ASCII.GetString(bufferBuilder.Dequeue(10)); Assert.IsTrue(str == "1234567890");
}

Leave a Reply