【问题标题】:Initialize a byte array to a certain value, other than the default null? [duplicate]将字节数组初始化为某个值,而不是默认的null? [复制]
【发布时间】:2011-09-03 06:05:03
【问题描述】:

我正忙着将一个用 C++ 完成的旧项目改写为 C#。

我的任务是重写程序,使其功能尽可能接近原始程序。

在一堆文件处理过程中,之前编写此程序的开发人员创建了一个结构,其中包含大量字段,这些字段对应于文件必须写入的设置格式,因此所有这些工作已经为我完成了。

这些字段都是字节数组。然后 C++ 代码所做的是使用 memset 将整个结构设置为所有空格字符 (0x20)。一行代码。很简单。

这是非常重要的,因为该文件最终进入的实用程序需要这种格式的文件。我必须做的是将此结构更改为 C# 中的一个类,但我找不到一种方法可以轻松地将这些字节数组中的每一个初始化为所有空格字符。

我最终不得不在类构造函数中这样做:

//Initialize all of the variables to spaces.
int index = 0;
foreach (byte b in UserCode)
{
    UserCode[index] = 0x20;
    index++;
}

这很好用,但我确信必须有更简单的方法来做到这一点。当数组在构造函数中设置为UserCode = new byte[6] 时,字节数组会自动初始化为默认的空值。有没有办法让它在声明时变成所有空格,这样当我调用我的类的构造函数时,它会像这样立即初始化?还是一些类似memset 的功能?

【问题讨论】:

    标签: c# .net arrays initialization


    【解决方案1】:

    如果你需要初始化一个小数组,你可以使用:

    byte[] smallArray = new byte[] { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
    

    如果你有一个更大的数组,那么你可以使用:

    byte[] bitBiggerArray Enumerable.Repeat(0x20, 7000).ToArray();
    

    这很简单,下一个男孩/女孩也很容易阅读。并且在 99.9% 的时间内都足够快。 (通常是 BestOption™)

    但是,如果您真的需要超快的速度,那么使用 P/invoke 调用优化的 memset 方法是给您的: (这里包含在一个很好用的类中)

    public static class Superfast
    {
        [DllImport("msvcrt.dll",
                  EntryPoint = "memset",
                  CallingConvention = CallingConvention.Cdecl,
                  SetLastError = false)]
        private static extern IntPtr MemSet(IntPtr dest, int c, int count);
    
        //If you need super speed, calling out to M$ memset optimized method using P/invoke
        public static byte[] InitByteArray(byte fillWith, int size)
        {
            byte[] arrayBytes = new byte[size];
            GCHandle gch = GCHandle.Alloc(arrayBytes, GCHandleType.Pinned);
            MemSet(gch.AddrOfPinnedObject(), fillWith, arrayBytes.Length);
            gch.Free();
            return arrayBytes;
        }
    }
    

    用法:

    byte[] oneofManyBigArrays =  Superfast.InitByteArray(0x20,700000);
    

    【讨论】:

    • 嘿先生!我已经测试了你的解决方案。它速度很快,但会导致内存泄漏。在使用 .Alloc 方法和 GCHandleType.Pinned 类型参数时,您应该记住在 GCHandle 上使用 .Free 来释放资源。您可以在文档中阅读更多内容:docs.microsoft.com/pl-pl/dotnet/api/…
    • @KacperWerema 泄漏这不好!随意编辑我的答案。 (我现在无法访问 PC 来验证代码)
    • 虽然没有像 Array.Copy 的 memcpy 那样的 .NET memset 解决方案,但很烦人...... For 循环和 LINQ 在大规模上都很糟糕。
    【解决方案2】:

    您可以使用Enumerable.Repeat()

    Enumerable.Repeat 生成一个包含一个重复值的序列。

    初始化为 0x20 的 100 项数组:

    byte[] arr1 = Enumerable.Repeat((byte)0x20,100).ToArray();
    

    【讨论】:

    • Thorsten 的答案中是否需要 .ToArray()?
    • 不确定,它可能会隐式执行。 (我没有运行 vs2010 来测试它)
    • Enumerable.Repeat() 返回一个 IEnumerable,因此需要显式调用 ToArray()。
    • 还需要将要重复的元素强制转换为byte 以获得一个字节数组,而不是在这种情况下出现的Int32 数组。又名byte[] arr1 = Enumerable.Repeat((byte)0x20, 100).ToArray();
    【解决方案3】:

    这个函数比用于填充数组的 for 循环快得多。

    Array.Copy 命令是一种非常快速的内存复制功能。这个函数利用了这一点,通过重复调用 Array.Copy 命令并将我们复制的大小加倍直到数组已满。

    我在我的博客https://grax32.com/2013/06/fast-array-fill-function-revisited.html 上讨论了这个问题(链接更新于 2019 年 12 月 16 日)。另请参阅提供此扩展方法的 Nuget 包。 http://sites.grax32.com/ArrayExtensions/

    请注意,只需在方法声明中添加单词“this”,即public static void ArrayFill<T>(this T[] arrayToFill ...

    ,这很容易成为扩展方法
    public static void ArrayFill<T>(T[] arrayToFill, T fillValue)
    {
        // if called with a single value, wrap the value in an array and call the main function
        ArrayFill(arrayToFill, new T[] { fillValue });
    }
    
    public static void ArrayFill<T>(T[] arrayToFill, T[] fillValue)
    {
        if (fillValue.Length >= arrayToFill.Length)
        {
            throw new ArgumentException("fillValue array length must be smaller than length of arrayToFill");
        }
    
        // set the initial array value
        Array.Copy(fillValue, arrayToFill, fillValue.Length);
    
        int arrayToFillHalfLength = arrayToFill.Length / 2;
    
        for (int i = fillValue.Length; i < arrayToFill.Length; i *= 2)
        {
            int copyLength = i;
            if (i > arrayToFillHalfLength)
            {
                copyLength = arrayToFill.Length - i;
            }
    
            Array.Copy(arrayToFill, 0, arrayToFill, i, copyLength);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用 Parallel 类(.NET 4 和更高版本)加快初始化并简化代码:

      public static void PopulateByteArray(byte[] byteArray, byte value)
      {
          Parallel.For(0, byteArray.Length, i => byteArray[i] = value);
      }
      

      当然可以同时创建数组:

      public static byte[] CreateSpecialByteArray(int length, byte value)
      {
          var byteArray = new byte[length];
          Parallel.For(0, length, i => byteArray[i] = value);
          return byteArray;
      }
      

      【讨论】:

      • 注意:并行类需要 .NET 4+
      • 你测试过这个的性能吗?看起来你会从其他工作中窃取线程。并且您将在头上进行线程管理。好的,如果这是您的代码当时唯一要做的事情,但如果您同时发生其他事情,则不是。
      • @DarcyThomas 线程来自线程池。当然,这取决于正在进行的“其他工作”。如果没有其他事情发生,它比传统循环快 (#ofCPUs-1) 倍。
      • 很容易证明Parallel 类对于这个基本的简单任务来说是一个非常低效的矫枉过正。
      • @ajeh 你是对的。我用更复杂的初始化测试了一次,它在 4 核机器上快了大约 3 倍。在实际应用程序中,我总是在使用 Parallel 类之前进行性能测试。
      【解决方案5】:

      最快的方法是使用 api:

      bR = 0xFF;

      RtlFillMemory(pBuffer, nFileLen, bR);

      使用指向缓冲区的指针、要写入的长度和编码字节。我认为在托管代码中执行此操作的最快方法(慢得多)是创建一小块初始化字节,然后使用 Buffer.Blockcopy 在循环中将它们写入字节数组。我把它放在一起但没有测试过,但你明白了:

      long size = GetFileSize(FileName);
      // zero byte
      const int blocksize = 1024;
      // 1's array
      byte[] ntemp = new byte[blocksize];
      byte[] nbyte = new byte[size];
      // init 1's array
      for (int i = 0; i < blocksize; i++)
          ntemp[i] = 0xff;
      
      // get dimensions
      int blocks = (int)(size / blocksize);
      int remainder = (int)(size - (blocks * blocksize));
      int count = 0;
      
      // copy to the buffer
      do
      {
          Buffer.BlockCopy(ntemp, 0, nbyte, blocksize * count, blocksize);
          count++;
      } while (count < blocks);
      
      // copy remaining bytes
      Buffer.BlockCopy(ntemp, 0, nbyte, blocksize * count, remainder);
      

      【讨论】:

      • 这是绝对的冠军。它适用于任何 ASCII 值。
      • 但是如果将数组设置为0x00Array.Clear() 几乎可以和更简单。
      • 这比导入memset() 还要快20%左右
      • 如果size 低于blocksize,则抛出
      【解决方案6】:

      这是标记为答案的帖子中代码的更快版本。

      我执行的所有基准测试表明,仅包含数组填充之类的简单 for 循环在递减时通常比 if 快两倍它正在增加。

      此外,数组 Length 属性已作为参数传递,因此无需从数组属性中检索它。它还应该预先计算并分配给局部变量。 涉及属性访问器的循环边界计算将在每次循环迭代之前重新计算边界值。

      public static byte[] CreateSpecialByteArray(int length)
      {
          byte[] array = new byte[length];
      
          int len = length - 1;
      
          for (int i = len; i >= 0; i--)
          {
              array[i] = 0x20;
          }
      
          return array;
      }
      

      【讨论】:

        【解决方案7】:

        只是为了扩展我的答案,一种更简洁的多次执行此操作的方法可能是:

        PopulateByteArray(UserCode, 0x20);
        

        调用:

        public static void PopulateByteArray(byte[] byteArray, byte value)
        {
            for (int i = 0; i < byteArray.Length; i++)
            {
                byteArray[i] = value;
            }
        }
        

        这具有高效的 for 循环的优点(提到 gwiazdorrr 的答案)以及一个漂亮整洁的调用,如果它被大量使用。而且一目了然的可读性比我个人认为的枚举要多得多。 :)

        【讨论】:

          【解决方案8】:

          对于小数组,使用数组初始化语法:

          var sevenItems = new byte[] { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
          

          对于较大的数组,使用标准的for 循环。这是最易读和最有效的方法:

          var sevenThousandItems = new byte[7000];
          for (int i = 0; i < sevenThousandItems.Length; i++)
          {
              sevenThousandItems[i] = 0x20;
          }
          

          当然,如果您经常需要这样做,那么您可以创建一个帮助方法来帮助您保持代码简洁:

          byte[] sevenItems = CreateSpecialByteArray(7);
          byte[] sevenThousandItems = CreateSpecialByteArray(7000);
          
          // ...
          
          public static byte[] CreateSpecialByteArray(int length)
          {
              var arr = new byte[length];
              for (int i = 0; i < arr.Length; i++)
              {
                  arr[i] = 0x20;
              }
              return arr;
          }
          

          【讨论】:

          • 嗯...不错的建议。这确实比Enumerable 方法更有效、更易读。感谢您的意见。
          • 你可能也想把它变成一个扩展方法。这样你就可以像byte[] b = new byte[5000].Initialize(0x20); 一样调用它。扩展方法将被声明为public static byte[] Initialize(this byte[] array, byte defaultValue) 并包含for 循环。它应该返回数组。
          • 这怎么合法但是新字节{4,3,2};抛出错误说 byte 没有实现可枚举类型?
          • for 循环应该使用递减操作。我已经进行了广泛的基准测试,当主体只有一个简单的指令(例如填充数组元素)时,递减 for 循环的速度通常是递增 for 循环的两倍。
          • @advocate:初始化new byte {4, 3, 2} 缺少方括号[] 来声明一个数组。此外,您的常量需要可转换为 byte,而 4、3 和 2 等数字 (ints) 则不能。所以它必须是:new byte[] { (byte) 4, (byte) 3, (byte) 2},或十六进制语法。
          【解决方案9】:
          var array = Encoding.ASCII.GetBytes(new string(' ', 100));
          

          【讨论】:

          • 只是一个问题,数组现在是否包含使用 new string(...) 产生的空终止符?
          • @Neil:实际上,您的问题没有答案,因为 new string() 不会产生空终止符(.NET 可见)。在 .NET 中,我们不考虑它,也不担心它。它根本不存在。
          • 工作正常,即使填充 0x00 字节:Encoding.ASCII.GetBytes(new string((char)0, 100));
          • 有趣的是,我可以使用许多值,但不能高于 0x7F。如果我使用 0x80 或更高,则缓冲区将填充 0x3F。所以这只是较低的 128 ASCII。这比约翰的回答慢了近 10 倍。
          • @ajeh:那是因为 ASCII 字符集只是“较低”的 128.values,0x00-0x7F。 “上”ASCII 值 (0x80-0xFF) 是扩展 ASCII,.Net Encoding.ASCII 为未知/扩展值返回 0x3F(或“?”)。
          【解决方案10】:

          在我之前的人给了你答案。我只想指出您滥用 foreach 循环。看,因为你必须增加索引标准“for循环”不仅更紧凑,而且更高效(“foreach”在后台做了很多事情):

          for (int index = 0; index < UserCode.Length; ++index)
          {
              UserCode[index] = 0x20;
          }
          

          【讨论】:

          • 你可能是对的。一个星期六的下午,我正在实现代码的这个特定部分(没有加班费;(),我的大脑就在那个时候,我只是在敲打代码以使其工作。从那以后它一直困扰着我,我现在才回来看看吧。
          • 如果你在一台 OoO 执行的机器上运行,将缓冲区大小除以 2 或 4 等,然后分配 buf[i]buf[i+1] 等会快得多,提高 2 倍在当前的 i5 和 i7 上。但仍然没有约翰的回答那么快。
          【解决方案11】:

          【讨论】:

          • 有趣的链接表明当前赞成的答案实际上比大尺寸的循环效率低。
          • 好点,但是这些字段都相当小,因为它们每个都只从数据库中读取一个值。我喜欢 Enumerable 方法,因为该程序必须处理和生成相当多的文件,并且它们都是以这种方式完成的,因此它使代码更加紧凑。
          • @DeVil:如果您想要紧凑的代码,您可以轻松地创建一个带有诸如 PopulateByteArray(byte[] array, byte value) 之类的签名的方法,然后将您的代码放入其中。我会说这可能比重复 Enumerable.Repeat 更简洁,并且还具有效率更高的优势。 ;-)
          • 同意。看来我接受Enumerable.Repeat 方法可能有点草率。
          【解决方案12】:

          您可以使用collection initializer

          UserCode = new byte[]{0x20,0x20,0x20,0x20,0x20,0x20};
          

          如果值不相同,这将比Repeat 工作得更好。

          【讨论】:

          • 适用于小型阵列,但绝对不适用于大型阵列。 :)
          • 确实如此。我知道这种初始化方式,但是有很多字段,它们的大小都不同。这种方法会比我的循环更痛苦。
          【解决方案13】:

          首先使用它来创建数组:

          byte[] array = Enumerable.Repeat((byte)0x20, <number of elements>).ToArray();
          

          &lt;number of elements&gt; 替换为所需的数组大小。

          【讨论】:

          • 这不如OP的原始解决方案。这仍然涉及在单独的步骤中创建和填充数组。事实上,它通常最终会创建、填充然后丢弃几个(可能很多)中间数组,而不是仅仅分配一个数组然后填充它。
          • 有趣的是,@PompolutZ 发现 stackoverflow.com/questions/1897555/… 的问题表明这不如循环有效,这可能真的很有意义,因为这不仅仅是设置一些值。它可能更简单(这是被问到的),但我不知道这意味着更好。 :) 一如既往地测试性能(如果相关)。 ;-)
          • @LukeH/@Chris:我阅读了 PompolutZ 在他的第二个链接中找到的性能分析。有趣的是,简单的for 循环对于大量数组元素和迭代来说效率更高。在 OP 的场景中,性能应该不是问题——他要求比循环“更简单”的东西 ;-)
          • 确实如此。我主要关心的是更紧凑的代码;如果我必须对程序必须生成和处理的每个文件执行此方法并保持原样,我将不得不复制和粘贴大量循环。我确信有一些方法可以在 C# 中实现这种文件处理,这会使这个问题没有实际意义,但是我在这里的时间安排非常紧张,所以模仿它在旧版本中的完成方式要方便得多代码。正如我在另一条评论中提到的,这些数组都非常小,但数量很多,所以Enumerable 方法是最紧凑的。
          • 似乎这会生成一个int数组,而不是请求的字节数组。
          猜你喜欢
          • 2020-10-26
          • 1970-01-01
          • 2014-02-22
          • 2010-10-18
          • 1970-01-01
          • 2014-12-29
          • 1970-01-01
          • 1970-01-01
          • 2017-12-08
          相关资源
          最近更新 更多