【问题标题】:Append two or more byte arrays in C#在 C# 中附加两个或多个字节数组
【发布时间】:2009-05-21 20:58:43
【问题描述】:

在 C# 中附加两个字节数组是否有最好的(见下文)方法?

假装我有完全的控制权,我可以使第一个字节数组足够大以容纳最后的第二个字节数组并使用Array.CopyTo 函数。或者我可以遍历单个字节并进行分配。

有没有更好的方法?我无法想象像将字节数组转换为字符串并加入它们并将它们转换回来之类的事情会比上述任何一种方法都好。

就最佳/更好而言(按顺序):

  1. 最快
  2. RAM 消耗最少

一个限制是我必须在 .NET 2.0 框架中工作。

推荐的两个选择是 MemoryStream 和 BlockCopy。我进行了 10,000,000 次循环的简单速度测试 3 次,得到以下结果:

平均 3 次运行 10,000,000 次循环(以毫秒为单位):

  • 块复制时间:1154,范围为 13 毫秒
  • MemoryStream GetBuffer 时间:1470,范围为 14 毫秒
  • MemoryStream ToArray 时间:1895,范围为 3 毫秒
  • CopyTo Time:2079,范围为 19 毫秒
  • 逐字节时间:2203,范围为10毫秒

ListAddRange 超过 1000 万次循环的结果: 列表时间:16694

相对 RAM 消耗(1 是基线,越高越差):

  • 逐字节:1
  • 块复制:1
  • 复制到:1
  • MemoryStream GetBuffer:2.3
  • MemoryStream ToArray:3.3
  • 列表:4.2

测试表明,一般来说,除非你正在做很多字节副本 [我是],否则查看字节副本并不值得关注 [e.g. 1000 万次运行产生的差异高达 1.1 秒]。

【问题讨论】:

标签: c# bytearray


【解决方案1】:

你想要BlockCopy

根据this blog post,它比Array.CopyTo 快。

【讨论】:

  • 是的 - Array.CopyTo 是 O(n)。不幸的是,MS 根本没有针对字节数组进行优化……
  • BlockCopy 在速度上击败了 MemoryStream(没有可靠的 RAM 测试),因此选择作为答案。
  • BlockCopy 也可以将二维数组复制到一维数组中,反之亦然。 Array.Copy 不能。
【解决方案2】:

您还可以使用带有 MemoryStream 的方法。假设 b1 和 b2 是两个字节数组,您可以通过以下方式使用 MemoryStream 获得一个新的 b3:

var s = new MemoryStream();
s.Write(b1, 0, b1.Length);
s.Write(b2, 0, b2.Length);
var b3 = s.ToArray();

这应该可以在没有 LINQ 的情况下工作,实际上速度要快得多。

【讨论】:

  • 我喜欢它的代码要小一些。也许更容易理解。干净:)
【解决方案3】:

创建一个新的MemoryStream,将一个与合并后的大小完全相同的缓冲区传递给构造函数。写入单个数组,然后最后使用缓冲区:

byte[] deadBeef = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF};
byte[] baadF00d = new byte[] { 0xBA, 0xAD, 0xF0, 0x0D};
int newSize = deadBeef.Length + baadF00d.Length;
var ms = new MemoryStream(new byte[newSize], 0, newSize, true, true);
ms.Write(deadBeef, 0, deadBeef.Length);
ms.Write(baadF00d, 0, baadF00d.Length);
byte[] merged = ms.GetBuffer();

.NET 中的许多低级 I/O 函数采用字节数组和偏移量。这样做是为了防止不必要的复制。如果这对性能敏感,请确保您确实需要合并数组,否则只需使用缓冲区和偏移量。

【讨论】:

  • 如果你走这条路,使用 ms.ToArray() 而不是 ms.GetBuffer() - 即使你传递了明确的长度,内部缓冲区仍然有可能通过 GetBuffer() 返回将有比指定更多的字节。也就是说:不要依赖MemoryStream的内部实现。
  • ToArray 将创建一个会损害性能的副本。如果您使用明确的缓冲区大小走这条路线,它将完全是您指定的大小。如果你尝试变大,你会得到一个 NotSupportedException。
【解决方案4】:

另一种选择,虽然我还没有测试过它在速度和内存消耗方面的表现如何,但 LINQ 方法是否可行:

byte[] combined = bytesOne.Concat(bytesTwo).Concat(bytesThree).ToArray();

...其中 bytesOne、bytesTwo 和 bytesThree 是字节数组。由于 Concat 使用延迟执行,因此它不应该创建任何中间数组,并且它不应该复制原始数组,直到它在最后构造最终的合并数组。

编辑:LINQBridge 将允许您在 2.0 框架中使用 LINQ-to-Objects(这是一个示例)。如果你不想依赖这个,我理解,但这是一种选择。

【讨论】:

  • 谢谢,不幸的是,我仅限于 2.0。我已经更新了我的问题以说明这一点。
【解决方案5】:

如果您的数组的大小会不时更改,那么您最好首先使用List<T>。然后就可以直接调用列表的AddRange()方法了。

否则,Array.Copy() 或 Array.CopyTo() 与您可能看到的其他任何东西一样好。

【讨论】:

    【解决方案6】:

    您是否教过如何使用 List 或 ArrayList 代替 Array?使用这些类型,它们可以通过 InsertRange 增长或缩小和追加

    【讨论】:

      【解决方案7】:

      你需要输出实际上是一个字节数组吗?

      如果没有,您可以自己创建一个“智能光标”(类似于 LINQ 所做的):创建一个自定义 IEnumerator,它将首先迭代第一个数组,然后继续第二个数组而不会中断。

      这将在 2.0 框架中快速运行(因为数组的连接几乎没有成本),并且使用的 RAM 不会超过数组已经消耗的内存。

      【讨论】:

        【解决方案8】:

        使第一个数组足够大以包含第二个数组并使用 Array.CopyTo 的第一个选项最终与手动迭代每个项目并进行分配大致相同。 Array.CopyTo() 只是让它更简洁。

        与上面相比,转换为字符串并返回数组将非常慢。并且可能会使用更多内存。

        【讨论】:

          猜你喜欢
          • 2010-09-29
          • 1970-01-01
          • 2012-10-08
          • 2017-06-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-03
          • 1970-01-01
          相关资源
          最近更新 更多