c# 高级编程 7章164页 【Span】

441 阅读1分钟

如何高效地使用数组

  1. 使用Span<T>
  2. 使用ArrayPool

Span

  • 快速访问连续内存
  • 包括托管内存和非托管内存
  • 数组长字符串,常常会用Span<T>

Span与数组 - 创建Span和修改元素内容

  • new Span<数组元素类型>(数组) 创建Span实例的时候,不复制数组元素
  • Span有索引器[],访问Span元素
  • 由于不复制数组元素,因此修改Span的元素,也相当于直接修改了数组元素
        private static Span<int> IntroSpans()
        {
            int[] arr1 = { 2, 4, 6, 8, 10, 12 };
            var span1 = new Span<int>(arr1);
            span1[1] = 11;
            Console.WriteLine($"arr1[1] is changed via span1[1]: {arr1[1]}");
            Console.WriteLine();
            return span1;
        }

Span与数组 - 切片

  • 切片,就是指,访问数组的一部分
  • Span做数组切片时,不复制数组元素
  • 两个时机,可以做数组切片
    • 创建Span的时候,new Span<T>(数组, 数组起始index, 切片长度)
    • 调用Span的方法Slice(数组起始index, 切片长度)
        private static Span<int> CreateSlices(Span<int> span1)
        {
            Console.WriteLine(nameof(CreateSlices));
            int[] arr2 = { 3, 5, 7, 9, 11, 13, 15 };
            var span2 = new Span<int>(arr2);
            var span3 = new Span<int>(arr2, start: 3, length: 3);
            var span4 = span1.Slice(start: 2, length: 4);

            DisplaySpan("content of span3", span3);
            DisplaySpan("content of span4", span4);
            Console.WriteLine();
            return span2;
        }

        private static void DisplaySpan(string title, ReadOnlySpan<int> span)
        {
            Console.WriteLine(title);
            for (int i = 0; i < span.Length; i++)
            {
                Console.Write($"{span[i]}.");
            }
            Console.WriteLine();
        }

输出

CreateSlices
content of span3
9.11.13.
content of span4
6.8.10.12.

Span与数组 - 改变值

  • Clear(), 如果元素类型为int, 则用0来填充
  • Fill(), 用给定值来填充
  • CopyTo(), 从头上的位置开始粘贴填充,如果目标Span不够大,会报出ArgumentException
  • TryCopyTo(), 如果目标Span不够大,不会报出异常,而是返回false
        private static void ChangeValues(Span<int> span1, Span<int> span2)
        {
            //Clear()
            Console.WriteLine(nameof(ChangeValues));
            Span<int> span4 = span1.Slice(start: 4);
            span4.Clear();
            DisplaySpan("content of span1", span1);
            
            //Fill()
            Span<int> span5 = span2.Slice(start: 3, length: 3);
            span5.Fill(42);
            DisplaySpan("content of span2", span2);
            
            //CopyTo()
            span5.CopyTo(span1);
            DisplaySpan("content of span1", span1);

            //TryCopyTo()
            if (!span1.TryCopyTo(span4))
            {
                Console.WriteLine("Couldn't copy span1 to span4 because span4 is too small");
                Console.WriteLine($"length of span4: {span4.Length}, length of span1: {span1.Length}");
            }
            Console.WriteLine();
        }

输出

ChangeValues
content of span1
2.11.6.8.0.0.
content of span2
3.5.7.42.42.42.15.
content of span1
42.42.42.8.0.0.
Couldn't copy span1 to span4 because span4 is too small
length of span4: 2, length of span1: 6

Span与数组 - 只读Span

三种创建只读Span的方式:

  • 直接new一个ReadOnlySpan
  • 直接将Span赋给ReadOnlySpan
  • 直接将数组赋给ReadOnlySpan
        private static void ReadonlySpan(Span<int> span1)
        {
            Console.WriteLine(nameof(ReadonlySpan));
            int[] arr = span1.ToArray();
            
            //直接new一个ReadOnlySpan
            ReadOnlySpan<int> readOnlySpan1 = new ReadOnlySpan<int>(arr);
            DisplaySpan("readOnlySpan1", readOnlySpan1);
            
            //直接将Span赋给ReadOnlySpan
            ReadOnlySpan<int> readOnlySpan2 = span1;
            DisplaySpan("readOnlySpan2", readOnlySpan2);
            
            //直接将数组赋给ReadOnlySpan
            ReadOnlySpan<int> readOnlySpan3 = arr;
            DisplaySpan("readOnlySpan3", readOnlySpan3);
            Console.WriteLine();
        }

输出:

ReadonlySpan
readOnlySpan1
42.42.42.8.0.0.
readOnlySpan2
42.42.42.8.0.0.
readOnlySpan3
42.42.42.8.0.0.