在C#中组合两个或多个字节数组的最佳方法

Sup*_*ell 230 c# arrays

我在C#中有3个字节的数组,我需要组合成一个.什么是最有效的方法来完成这项任务?

Mat*_*vis 317

对于原始类型(包括字节),请使用System.Buffer.BlockCopy而不是System.Array.Copy.它更快.

我使用3个10字节的数组,在一个循环中执行了100万次的每个建议方法.结果如下:

  1. 新的字节数组使用System.Array.Copy - 0.2187556秒
  2. 新的字节数组使用System.Buffer.BlockCopy - 0.1406286秒
  3. IEnumerable <byte>使用C#yield operator - 0.0781270秒
  4. IEnumerable <byte>使用LINQ的Concat <> - 0.0781270秒

我将每个数组的大小增加到100个元素并重新运行测试:

  1. 新的字节数组使用System.Array.Copy - 0.2812554秒
  2. 新的字节数组使用System.Buffer.BlockCopy - 0.2500048秒
  3. IEnumerable <byte>使用C#yield operator - 0.0625012秒
  4. IEnumerable <byte>使用LINQ的Concat <> - 0.0781265秒

我将每个数组的大小增加到1000个元素并重新运行测试:

  1. 新的字节数组使用System.Array.Copy - 1.0781457秒
  2. 新的字节数组使用System.Buffer.BlockCopy - 1.0156445秒
  3. IEnumerable <byte>使用C#yield operator - 0.0625012秒
  4. IEnumerable <byte>使用LINQ的Concat <> - 0.0781265秒

最后,我将每个数组的大小增加到100万个元素并重新运行测试,每个循环执行4000次:

  1. 新的字节数组使用System.Array.Copy - 13.4533833秒
  2. 新的字节数组使用System.Buffer.BlockCopy - 13.1096267秒
  3. IEnumerable <byte>使用C#yield运算符 - 0秒
  4. IEnumerable <byte>使用LINQ的Concat <> - 0秒

因此,如果您需要一个新的字节数组,请使用

byte[] rv = new byte[a1.Length + a2.Length + a3.Length];
System.Buffer.BlockCopy(a1, 0, rv, 0, a1.Length);
System.Buffer.BlockCopy(a2, 0, rv, a1.Length, a2.Length);
System.Buffer.BlockCopy(a3, 0, rv, a1.Length + a2.Length, a3.Length);
Run Code Online (Sandbox Code Playgroud)

但是,如果你可以使用IEnumerable<byte>,那么DEFINITELY更喜欢LINQ的Concat <>方法.它只比C#yield运算符略慢,但更简洁,更优雅.

IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);
Run Code Online (Sandbox Code Playgroud)

如果你有任意数量的数组并使用.NET 3.5,你可以使System.Buffer.BlockCopy解决方案更通用,如下所示:

private byte[] Combine(params byte[][] arrays)
{
    byte[] rv = new byte[arrays.Sum(a => a.Length)];
    int offset = 0;
    foreach (byte[] array in arrays) {
        System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
        offset += array.Length;
    }
    return rv;
}
Run Code Online (Sandbox Code Playgroud)

*注意:上面的块要求您在顶部添加以下命名空间才能工作.

using System.Linq;
Run Code Online (Sandbox Code Playgroud)

对于Jon Skeet关于后续数据结构迭代(字节数组与IEnumerable <byte>)的观点,我重新运行了最后的定时测试(100万个元素,4000次迭代),添加了一个遍历整个数组的循环通过:

  1. 新的字节数组使用System.Array.Copy - 78.20550510秒
  2. 新的字节数组使用System.Buffer.BlockCopy - 77.89261900秒
  3. IEnumerable <byte>使用C#yield运算符 - 551.7150161秒
  4. IEnumerable <byte>使用LINQ的Concat <> - 448.1804799秒

关键是,理解结果数据结构的创建和使用的效率非常重要.仅仅关注创建的效率可能会忽略与使用相关的低效率.荣誉,乔恩.

  • 但是问题是否真的要在最后将其转换为数组?如果没有,当然它更快 - 但它没有满足要求. (60认同)
  • Re:Matt Davis - 如果你的"要求"需要将IEnumerable转换为数组并不重要 - 你的要求所需要的只是结果实际上*用于某些时尚*.你对IEnumerable进行性能测试的原因是因为*你实际上并没有做任何事情*!在您尝试使用结果之前,LINQ不会执行任何工作.出于这个原因,我发现你的答案客观上是不正确的,如果他们关心性能,他们绝对不应该使用LINQ. (15认同)
  • 为什么包含错误和误导性信息的答案是最高投票的答案,并且在某人(Jon Skeet)指出它甚至没有回答OP问题之后被编辑为基本完全**使其原始陈述失效**? (14认同)
  • 我读了整个答案,包括你的更新,我的评论代表.我知道我迟到了加入派对,但答案非常误导,上半部分显然是假的*. (11认同)
  • 误导性答案.即便是版本也没有回答这个问题. (3认同)
  • 是不是(懒惰)函数式编程盛大?;-) (2认同)
  • 一般来说,如果您的性能测试显示 0 或接近 0(如 IEnumerable 结果所示),则您的测试可能存在问题。 (2认同)
  • 如果产生这些结果的代码无法公开审查,这些数字与虚构一样好. (2认同)
  • @MattDavis,你也可以发布你的测试代码吗?我不能得到与你相同的结果比例. (2认同)

Jon*_*eet 146

在我看来,许多答案都忽略了规定的要求:

  • 结果应该是一个字节数组
  • 它应该尽可能高效

这两个一起排除了一个LINQ字节序列 - 任何东西yield都会使得无法在不迭代整个序列的情况下获得最终大小.

如果那些当然不是真正的要求,LINQ可能是一个非常好的解决方案(或IList<T>实现).但是,我会假设Superdumbell知道他想要什么.

(编辑:我刚才有另外一个想法.制作数组副本和懒散地阅读它们之间存在很大的语义差异.考虑一下如果在调用Combine(或者其他)之后更改其中一个"源"数组中的数据会发生什么)方法但在使用结果之前 - 使用延迟评估,该变化将是可见的.使用立即复制,它不会.不同的情况将要求不同的行为 - 只需注意一些事情.)

以下是我提出的方法 - 与其他一些答案中包含的方法非常相似,当然:)

public static byte[] Combine(byte[] first, byte[] second)
{
    byte[] ret = new byte[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

public static byte[] Combine(byte[] first, byte[] second, byte[] third)
{
    byte[] ret = new byte[first.Length + second.Length + third.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    Buffer.BlockCopy(third, 0, ret, first.Length + second.Length,
                     third.Length);
    return ret;
}

public static byte[] Combine(params byte[][] arrays)
{
    byte[] ret = new byte[arrays.Sum(x => x.Length)];
    int offset = 0;
    foreach (byte[] data in arrays)
    {
        Buffer.BlockCopy(data, 0, ret, offset, data.Length);
        offset += data.Length;
    }
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

当然,"params"版本需要首先创建一个字节数组的数组,这会带来额外的低效率.

  • +1.答案至少应满足要求. (11认同)
  • (虽然我认为你的表现基准应该显示在每种情况下完成所有结果所花费的时间,以避免给懒惰评估带来不公平的优势.) (4认同)
  • @Matt:是的,提供替代方案是好的 - 但值得解释的是,它们“是”替代方案,而不是将它们冒充为所问问题的答案。(我并不是说你这样做了——你的回答非常好。) (2认同)
  • @andleer:除了其他任何东西,Buffer.BlockCopy只适用于基本类型. (2认同)

Nat*_*ini 37

为了代码清洁,我进一步采用了Matt的LINQ示例:

byte[] rv = a1.Concat(a2).Concat(a3).ToArray();
Run Code Online (Sandbox Code Playgroud)

就我而言,阵列很小,所以我不关心性能.

  • 简短而简单的解决方案,性能测试会很棒! (3认同)
  • 这绝对清晰,可读,不需要外部库/助手,并且在开发时间方面非常有效.当运行时性能不重要时很棒. (3认同)

Fry*_*Guy 27

如果您只需要一个新的字节数组,请使用以下命令:

byte[] Combine(byte[] a1, byte[] a2, byte[] a3)
{
    byte[] ret = new byte[a1.Length + a2.Length + a3.Length];
    Array.Copy(a1, 0, ret, 0, a1.Length);
    Array.Copy(a2, 0, ret, a1.Length, a2.Length);
    Array.Copy(a3, 0, ret, a1.Length + a2.Length, a3.Length);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您只需要一个IEnumerable,请考虑使用C#2.0 yield运算符:

IEnumerable<byte> Combine(byte[] a1, byte[] a2, byte[] a3)
{
    foreach (byte b in a1)
        yield return b;
    foreach (byte b in a2)
        yield return b;
    foreach (byte b in a3)
        yield return b;
}
Run Code Online (Sandbox Code Playgroud)

  • 第二个选择很棒.+1. (2认同)

00j*_*0jt 10

我实际上遇到了使用Concat的一些问题...(在1000万的阵列中,它实际上已经崩溃了).

我发现以下内容简单,容易并且运行良好而不会崩溃,它适用于任意数量的数组(不仅仅是三个)(它使用LINQ):

public static byte[] ConcatByteArrays(params byte[][]  arrays)
{
    return arrays.SelectMany(x => x).ToArray();
}
Run Code Online (Sandbox Code Playgroud)


And*_*rew 6

memorystream类对我来说非常好.我无法让缓冲类以与memorystream一样快的速度运行.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

  • 正如qwe所说,我在循环中进行了10,000,000次测试,并且MemoryStream的出现比Buffer.BlockCopy低290%. (3认同)