ArraySegment - 返回实际的段C#

时间:2011-04-22 15:00:37

标签: c# arrays segment

我一直在寻找返回分段的方法,该分段基本上由ArraySegment在偏移和计数方面持有。虽然ArraySegment包含完整的原始数组,但它只是将其分隔,因为对该段的任何更改都会反映到原始数组中。问题或说ArraySegment的限制是它不会返回整个段本身,我必须遍历值。返回整个细分市场的最佳方法是什么?

 byte[] input = new byte[5]{1,2,3,4,5};
 ArraySegment<byte> delimited = new ArraySegment<byte>(input,0,2);
 byte[] segment = HERE I NEED SOMETHING THAT WILL RETURN THE SEGMENT i.e. [0,1,2]

最重要的一点,段不能是副本,而应该引用原始数组。如果对段进行了任何更改,则必须将它们反映在原始数组中。

非常感谢任何提示,谢谢!

作业基准:来自ThomasdigEmAll

的一些答案后

好的,我针对digEmAll和Thomas的代码运行了一些基准测试,令我惊讶的是代码速度非常快。正是我拼命寻找的东西。结果如下。

Construct             Size    Elements assigned    Iterations       Time
_______________________________________________________________________________

ArraySegmentWrapper   1500        1500              1000000       396.3 ms
Array.Copy            1500        1500              1000000       4389.04 ms

正如你可以看到的巨大差异,我很清楚我将使用ArraySegment的代码。以下是基准测试代码。请注意,这可能有点 人们会争辩为什么“新”被放入循环中是有偏见的。我只是想重现我目前手头的情况尽可能地解决它而不需要移动大部分代码。这只是我的一天!

namespace ArraySegmentWrapped
{
    class Program
    {

        public static Stopwatch stopWatch = new Stopwatch();
        public static TimeSpan span = new TimeSpan();
        public static double totalTime = 0.0;
        public static int iterations = 1000000;

        static void Main(string[] args)
        {
            int size = 1500;
            int startIndex = 0;
            int endIndex = 1499;
            byte[] array1 = new byte[size];
            byte[] array2 = null;

            for (int index = startIndex; index < size; index++)
            {
                array1[index] = (byte)index;
            }

            ArraySegmentWrapper<byte> arraySeg;

            for (int index = 0; index < iterations; index++)
            {
                stopWatch.Start();
                arraySeg = new ArraySegmentWrapper<byte>(array1, startIndex, endIndex);            
                stopWatch.Stop();
                totalTime += stopWatch.Elapsed.TotalMilliseconds;
            }

            Console.WriteLine("ArraySegment:{0:F6}", totalTime / iterations);
            stopWatch.Reset();
            totalTime = 0.0;

            for (int index = 0; index < iterations; index++)
            {
                stopWatch.Start();
                array2 = new byte[endIndex - startIndex + 1];
                Array.Copy(array1, startIndex, array2, 0, endIndex);
                stopWatch.Stop();
                totalTime += stopWatch.Elapsed.TotalMilliseconds;
            }
            Console.WriteLine("Array.Copy:{0:F6}", totalTime / iterations);                        


        }
    }
// Code for ArraySegmentWrapper goes here    

}

访问基准(更新)  因此,在Thomas指出基准测试之后,并表示与ArraySegment相比,对简单数组的访问速度会更快,他完全正确。但是由于digEmAll指出我应该在发布模式下进行测试(对于在调试模式下测试的旧错误而感到遗憾),我将代码与上面几乎相同(迭代减少了两个零 - 无法等待很长时间才能输出到来,对不起)和一些修改,以访问相同数量的元素,以下是我得到的。

Construct             Size    Elements accessed    Iterations       Time
_______________________________________________________________________________

ArraySegmentWrapper   1500        1500              1000000       5268.3 ms
Array.Copy            1500        1500              1000000       4812.4 ms

结论是虽然assingment非常快,但是通过ArraySegments访问速度很慢。

4 个答案:

答案 0 :(得分:8)

Thomas Levesque's suggestion开始我以这种方式构建了一个简单的ArraySegmentWrapper<T>类:

static void Main(string[] args)
{
    int[] arr = new int[10];
    for (int i = 0; i < arr.Length; i++)
        arr[i] = i;

    // arr = 0,1,2,3,4,5,6,7,8,9

    var segment = new ArraySegmentWrapper<int>(arr, 2, 7);
    segment[0] = -1;
    segment[6] = -1;
    // now arr = 0,1,-1,3,4,5,6,7,-1,9


    // this prints: -1,3,4,5,6,7,-1
    foreach (var el in segment)
        Console.WriteLine(el);
}

<强>实施

public class ArraySegmentWrapper<T> : IList<T>
{
    private readonly ArraySegment<T> segment;

    public ArraySegmentWrapper(ArraySegment<T> segment)
    {
        this.segment = segment;
    }

    public ArraySegmentWrapper(T[] array, int offset, int count)
        : this(new ArraySegment<T>(array, offset, count))
    {
    }

    public int IndexOf(T item)
    {
        for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
            if (Equals(segment.Array[i], item))
                return i;
        return -1;
    }

    public void Insert(int index, T item)
    {
        throw new NotSupportedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    public T this[int index]
    {
        get
        {
            if (index >= this.Count)
                throw new IndexOutOfRangeException();
            return this.segment.Array[index + this.segment.Offset];
        }
        set
        {
            if (index >= this.Count)
                throw new IndexOutOfRangeException();
            this.segment.Array[index + this.segment.Offset] = value;
        }
    }

    public void Add(T item)
    {
        throw new NotSupportedException();
    }

    public void Clear()
    {
        throw new NotSupportedException();
    }

    public bool Contains(T item)
    {
        return this.IndexOf(item) != -1;
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
        {
            array[arrayIndex] = segment.Array[i];
            arrayIndex++;
        }
    }

    public int Count
    {
        get { return this.segment.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        throw new NotSupportedException();
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
            yield return segment.Array[i];
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

编辑:

正如@JeppeStigNielsen在评论中指出的那样,因为.NET 4.5 ArraySegment<T>实现了IList<T>

答案 1 :(得分:5)

我使用以下一组扩展方法来处理数组段:

    #region ArraySegment related methods

    public static ArraySegment<T> GetSegment<T>(this T[] array, int from, int count)
    {
        return new ArraySegment<T>(array, from, count);
    }

    public static ArraySegment<T> GetSegment<T>(this T[] array, int from)
    {
        return GetSegment(array, from, array.Length - from);
    }

    public static ArraySegment<T> GetSegment<T>(this T[] array)
    {
        return new ArraySegment<T>(array);
    }

    public static IEnumerable<T> AsEnumerable<T>(this ArraySegment<T> arraySegment)
    {
        return arraySegment.Array.Skip(arraySegment.Offset).Take(arraySegment.Count);
    }

    public static T[] ToArray<T>(this ArraySegment<T> arraySegment)
    {
        T[] array = new T[arraySegment.Count];
        Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
        return array;
    }

    #endregion

您可以按如下方式使用它们:

byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = input.GetSegment(0, 2);
byte[] segment = delimited.ToArray();

答案 2 :(得分:2)

C#(和.NET一般)不允许您创建“指向”另一个数组内部的标准数组引用。因此,您需要更改消费API以便它们可以处理ArraySegment实例,或者您需要创建数据副本,然后在对副本进行操作后复制更改。无论如何,这通常是一种更安全的方法,因为传递对阵列的引用会破坏绝缘,并且随着阵列的消费者数量的增加而更难以追踪错误。构建新的数组实例和复制值在.NET中相对便宜,只要数组的大小不是非常大,因此这里的性能影响通常可以忽略不计。

如果遇到性能问题且需要进行微优化,我建议使用不安全的C#代码(可以修复数组引用并传递指针)或将性能关键代码拉出来C ++ / CLI程序集,您可以在其中使用非托管内存进行计算。我建议首先分析代码,以验证这确实是你的瓶颈。我不能强调你不应该担心在.NET中分配新的内存,因为压缩GC堆的本质意味着频繁的小分配比在C中更便宜(内存分配必须适应可能的堆碎片) 。)

答案 3 :(得分:1)

查看我在此主题here上发布的答案。

基本上你所要做的就是将ArraySegment转换为IList以获得你期望的功能。