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

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

345835 次浏览

如果你只是需要一个新的字节数组,那么使用以下命令:

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;
}

或者,如果你只需要一个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;
}

对于基元类型(包括字节),使用System.Buffer.BlockCopy而不是System.Array.Copy。这是更快。

我在循环中对每个建议的方法进行计时,每个方法使用3个10字节的数组执行了100万次。以下是调查结果:

  1. 使用System.Array.Copy - 0.2187556秒的新字节数组
  2. 使用System.Buffer.BlockCopy - 0.1406286秒的新字节数组
  3. 字节>使用c# yield运算符- 0.0781270秒
  4. ienumerablebyte >使用LINQ的Concat< > - 0.0781270秒

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

  1. 使用System.Array.Copy - 0.2812554秒的新字节数组
  2. 使用System.Buffer.BlockCopy - 0.2500048秒的新字节数组
  3. 字节>使用c# yield运算符- 0.0625012秒
  4. ienumerablebyte >使用LINQ的Concat< > - 0.0781265秒

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

  1. 使用System.Array.Copy - 1.0781457秒的新字节数组
  2. 使用System.Buffer.BlockCopy - 1.0156445秒的新字节数组
  3. 字节>使用c# yield运算符- 0.0625012秒
  4. ienumerablebyte >使用LINQ的Concat< > - 0.0781265秒

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

  1. 使用System.Array.Copy - 13.4533833秒创建新的字节数组
  2. 使用System.Buffer.BlockCopy - 13.1096267秒创建字节数组
  3. 字节>使用c# yield运算符- 0秒
  4. ienumerablebyte >使用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);

但是,如果你可以使用IEnumerable<byte>肯定< em > < / em >更喜欢LINQ的Concat<>方法。它只比c#的yield操作符稍慢,但更简洁、更优雅。

IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);

如果你有任意数量的数组并且使用。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;
}

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

using System.Linq;

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

  1. 使用System.Array.Copy - 78.20550510秒的新字节数组
  2. 使用System.Buffer.BlockCopy - 77.89261900秒创建字节数组
  3. 字节>使用c# yield运算符- 551.7150161秒
  4. ienumerablebyte >使用LINQ的连接> - 448.1804799秒

重点是,< em >非常< / em >对于理解结果数据结构的创建和还有用法的效率是很重要的。仅仅关注创造的效率可能会忽略与使用相关的低效率。荣誉,乔恩。

Concat是正确答案,但出于某种原因,手摇的东西得到了最多的选票。如果你喜欢这个答案,也许你会更喜欢这个通解:

    IEnumerable<byte> Combine(params byte[][] arrays)
{
foreach (byte[] a in arrays)
foreach (byte b in a)
yield return b;
}

这可以让你做以下事情:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();

在我看来,许多答案都忽略了上述要求:

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

这两者一起排除了字节的LINQ序列-任何带有yield的东西都将不可能在不遍历整个序列的情况下获得最终大小。

当然,如果这些不是真正的的要求,LINQ可能是一个完美的解决方案(或IList<T>实现)。然而,我假设超级哑铃知道他想要什么。

(编辑:我刚刚有了另一个想法。复制数组和惰性读取数组之间有很大的语义差异。考虑一下,如果在调用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;
}

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

memorystream类为我很好地完成了这项工作。我无法让缓冲类运行得像内存流一样快。

using (MemoryStream ms = new MemoryStream())
{
ms.Write(BitConverter.GetBytes(22),0,4);
ms.Write(BitConverter.GetBytes(44),0,4);
ms.ToArray();
}
    public static bool MyConcat<T>(ref T[] base_arr, ref T[] add_arr)
{
try
{
int base_size = base_arr.Length;
int size_T = System.Runtime.InteropServices.Marshal.SizeOf(base_arr[0]);
Array.Resize(ref base_arr, base_size + add_arr.Length);
Buffer.BlockCopy(add_arr, 0, base_arr, base_size * size_T, add_arr.Length * size_T);
}
catch (IndexOutOfRangeException ioor)
{
MessageBox.Show(ioor.Message);
return false;
}
return true;
}
这里是@Jon Skeet提供的答案的概括。 它基本上是相同的,只是它可用于任何类型的数组,而不仅仅是bytes:

public static T[] Combine<T>(T[] first, T[] second)
{
T[] ret = new T[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 T[] Combine<T>(T[] first, T[] second, T[] third)
{
T[] ret = new T[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 T[] Combine<T>(params T[][] arrays)
{
T[] ret = new T[arrays.Sum(x => x.Length)];
int offset = 0;
foreach (T[] data in arrays)
{
Buffer.BlockCopy(data, 0, ret, offset, data.Length);
offset += data.Length;
}
return ret;
}

我以Matt的LINQ为例,进一步提高了代码的洁净度:

byte[] rv = a1.Concat(a2).Concat(a3).ToArray();

在我的例子中,数组很小,所以我不关心性能。

    public static byte[] Concat(params byte[][] arrays) {
using (var mem = new MemoryStream(arrays.Sum(a => a.Length))) {
foreach (var array in arrays) {
mem.Write(array, 0, array.Length);
}
return mem.ToArray();
}
}

实际上,我在使用Concat时遇到了一些问题…(对于1000万个数组,它实际上崩溃了)。

我发现以下是简单,容易,工作得很好,没有崩溃对我来说,它适用于任何数量的数组(不仅仅是三个)(它使用LINQ):

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

所有你需要传递的字节数组列表,这个函数将返回你字节数组(合并)。 这是我认为最好的解决方案:).

public static byte[] CombineMultipleByteArrays(List<byte[]> lstByteArray)
{
using (var ms = new MemoryStream())
{
using (var doc = new iTextSharp.text.Document())
{
using (var copy = new PdfSmartCopy(doc, ms))
{
doc.Open();
foreach (var p in lstByteArray)
{
using (var reader = new PdfReader(p))
{
copy.AddDocument(reader);
}
}


doc.Close();
}
}
return ms.ToArray();
}
}

可以使用泛型组合数组。下面的代码可以很容易地扩展为三个数组。这样就不需要为不同类型的数组复制代码。对我来说,上面的一些答案似乎过于复杂。

private static T[] CombineTwoArrays<T>(T[] a1, T[] a2)
{
T[] arrayCombined = new T[a1.Length + a2.Length];
Array.Copy(a1, 0, arrayCombined, 0, a1.Length);
Array.Copy(a2, 0, arrayCombined, a1.Length, a2.Length);
return arrayCombined;
}
    /// <summary>
/// Combine two Arrays with offset and count
/// </summary>
/// <param name="src1"></param>
/// <param name="offset1"></param>
/// <param name="count1"></param>
/// <param name="src2"></param>
/// <param name="offset2"></param>
/// <param name="count2"></param>
/// <returns></returns>
public static T[] Combine<T>(this T[] src1, int offset1, int count1, T[] src2, int offset2, int count2)
=> Enumerable.Range(0, count1 + count2).Select(a => (a < count1) ? src1[offset1 + a] : src2[offset2 + a - count1]).ToArray();