前置到C#数组

时间:2012-07-09 20:03:02

标签: c# arrays

如果在C#中填充了byte[] values,我想将值(byte)0x00添加到数组中。我假设这将需要创建一个新数组并添加旧数组的内容。速度是我的应用程序的一个重要方面。这样做的最佳方式是什么?

- 编辑 -

byte[]用于存储DSA(数字签名算法)参数。每个阵列只需要执行一次操作,但速度很重要,因为我可能会在许多不同的byte[]上执行此操作。

8 个答案:

答案 0 :(得分:36)

如果您只打算执行此操作,则没有太多选择。 Monroe's answer提供的代码应该没问题。

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values

但是,如果您要多次执行此操作,则可以选择更多选项。存在一个基本问题,即将数据添加到数组不是一种有效的操作,因此您可以选择使用备用数据结构。

LinkedList可以有效地预先添加数据,但是对于大多数任务来说它通常效率较低,因为它涉及更多的内存分配/释放并且还会丢失内存的本地化,因此它可能不是一个净赢。

双端队列(称为双端队列)对您来说是一个很棒的数据结构。您可以有效地添加到开头或结尾,并有效地访问结构中的任何位置(但您无法有效地插入除开头或结尾之外的某个位置)。这里的主要问题是.NET不提供deque的实现。您需要找到一个带有实现的第三方库。

您还可以通过跟踪“我需要预先添加的数据”(使用List / Queue /等)然后等待尽可能长时间地实际添加数据来复制时节省很多时间,以便您尽可能减少新数组的创建,以及限制现有元素的副本数量。

您还可以考虑是否可以调整结构,以便添加到结尾,而不是开始(即使您知道以后需要将其反转)。如果您在很短的时间内追加很多内容,可能值得将数据存储在List(可以有效地添加到 end )并添加到最后。根据您的需要,甚至可能值得创建一个类,它是List的包装器,并且隐藏了它被颠倒的事实。您可以制作一个将i映射到Count-i等的索引器,以便从外部看起来好像您的数据是正常存储的,即使内部List实际上存在数据倒退。

答案 1 :(得分:16)

好的,让我们来看看有关这个问题的性能问题。 这是不是答案,只是一个微基准标记,可以看出哪个选项效率更高。

所以,让我们设置一下场景:

  • 1,000,000项的字节数组,随机填充
  • 我们需要预先添加项目0x00

我们有3个选择:

  1. 手动创建和填充新数组
  2. 手动创建新阵列并使用Array.Copy(@Monroe)
  3. 创建列表,加载数组,插入项目并将列表转换为数组
  4. 以下是代码:

        byte[] byteArray = new byte[1000000];
    
        for (int i = 0; i < byteArray.Length; i++)
        {
            byteArray[i] = Convert.ToByte(DateTime.Now.Second);
        }
    
        Stopwatch stopWatch = new Stopwatch();
    
        //#1 Manually creating and populating a new array;
    
        stopWatch.Start();
    
        byte[] extendedByteArray1 = new byte[byteArray.Length + 1];
    
        extendedByteArray1[0] = 0x00;
    
        for (int i = 0; i < byteArray.Length; i++)
        {
            extendedByteArray1[i + 1] = byteArray[i];
        }
    
        stopWatch.Stop();
        Console.WriteLine(string.Format("#1: {0} ms", stopWatch.ElapsedMilliseconds));
        stopWatch.Reset();
    
        //#2 Using a new array and Array.Copy
    
        stopWatch.Start();
    
        byte[] extendedByteArray2 = new byte[byteArray.Length + 1];
        extendedByteArray2[0] = 0x00;                                
        Array.Copy(byteArray, 0, extendedByteArray2, 1, byteArray.Length);
    
        stopWatch.Stop();
        Console.WriteLine(string.Format("#2: {0} ms", stopWatch.ElapsedMilliseconds));
        stopWatch.Reset();
    
        //#3 Using a List
    
        stopWatch.Start();
    
        List<byte> byteList = new List<byte>();
        byteList.AddRange(byteArray);
        byteList.Insert(0, 0x00);
    
        byte[] extendedByteArray3 = byteList.ToArray();
    
        stopWatch.Stop();
        Console.WriteLine(string.Format("#3: {0} ms", stopWatch.ElapsedMilliseconds));
        stopWatch.Reset();
    
        Console.ReadLine();
    

    结果是:

    #1: 9 ms
    #2: 1 ms
    #3: 6 ms
    

    我已多次运行并获得不同的数字,但比例始终相同:#2始终是最有效的选择

    我的结论:数组比列表更有效(尽管它们提供的功能更少),并且某种方式Array.Copy实际上是优化的(尽管想要了解它)。

    我们将不胜感激。

    最好的问候。

    PS :这不是一个剑术,我们在Q&amp; A网站上学习和教学。 学习

答案 2 :(得分:13)

正如您所推测的,最快的方法是创建长度为+ 1的新数组并复制所有旧值。

如果您打算多次这样做,那么我建议使用List<byte>而不是byte[],因为在增加底层存储的同时重新分配和复制的成本会更有效地摊销;在通常情况下,List中的基础向量在每次向List添加或插入超过其当前容量时增长两倍。

...

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values

答案 3 :(得分:3)

.NET 4.7.1和更高版本中最简单,最干净的方法是使用无副作用的Prepend()

在序列的开头添加一个值。

示例

// Creating an array of numbers
var numbers = new[] { 1, 2, 3 };

// Trying to prepend any value of the same type
var results = numbers.Prepend(0);

// output is 0, 1, 2, 3
Console.WriteLine(string.Join(", ", results ));

答案 4 :(得分:2)

当我需要频繁附加数据但又希望O(1)随机访问单个元素时,我将使用一些过量分配的数组来快速添加新值。这意味着您需要将实际内容长度存储在另一个变量中,因为array.length将指示长度+填充。通过使用填充的一个时隙来附加新值,没有分配&amp;复制是必要的,直到你用完填充。实际上,分配是通过几个追加操作摊销的。有速度空间权衡,好像你有很多这些数据结构,你可以在程序中的任何时候使用相当数量的填充。

这种技术可用于前置。与追加一样,您可以在用户和实现之间引入一个接口或抽象:您可以有几个填充槽,以便偶尔需要新的内存分配。如上所述,您还可以实现一个前置接口,该接口具有可以反转索引的附加数据结构。

我将数据结构打包为一些通用集合接口的实现,这样界面看起来与用户相当(例如数组列表或其他东西)。

(另外,如果支持删除,一旦删除它们就可以清除元素,以帮助减少gc负载。)

重点是分别考虑实现和接口,因为解耦它们使您可以灵活地选择不同的实现或使用最小的接口隐藏实现细节。

您可以使用许多其他数据结构,具体取决于您对域的适用性。绳索或间隙缓冲;见What is best data structure suitable to implement editor like notepad?;特里也做了一些有用的事情。

答案 5 :(得分:1)

最好的选择取决于您将在以后对该系列进行的操作。如果这是唯一可以进行长度变化的编辑,那么最好的办法是创建一个带有一个额外插槽的新阵列,然后使用Array.Copy()完成剩下的工作。无需初始化第一个值,因为新的C#数组总是被清零:

byte[] PrependWithZero(byte[] input)
{
    var newArray = new byte[input.Length + 1];
    Array.Copy(input, 0, newArray, 1, input.Length);
    return newArray;
}

如果可能会发生其他长度变化的编辑,那么最高效的选项可能是一直使用List<byte>,只要添加不总是在开头。 (如果是这种情况,即使链接列表也可能不是您可以解雇的选项。):

var list = new List<byte>(input);
list.Insert(0, 0);

答案 6 :(得分:1)

我知道这是一篇非常古老的帖子,但实际上我喜欢使用lambda。当然,我的代码可能不是最有效的方式,但它的可读性和一行。我使用.Concat和ArraySegment的组合。

 string[] originalStringArray = new string[] { "1", "2", "3", "5", "6" };
 int firstElementZero = 0;
 int insertAtPositionZeroBased = 3;
 string stringToPrepend = "0";
 string stringToInsert = "FOUR"; // Deliberate !!!

 originalStringArray = new string[] { stringToPrepend }
            .Concat(originalStringArray).ToArray();

 insertAtPositionZeroBased += 1; // BECAUSE we prepended !!
 originalStringArray = new ArraySegment<string>(originalStringArray, firstElementZero, insertAtPositionZeroBased)       
                .Concat(new string[] { stringToInsert })
                .Concat(new ArraySegment<string>(originalStringArray, insertAtPositionZeroBased, originalStringArray.Length - insertAtPositionZeroBased)).ToArray();

答案 7 :(得分:0)

我知道这是一个超过4年的接受帖子,但对于那些可能相关的人来说 Buffer.BlockCopy 会更快。