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

Best way to combine two or more byte arrays in C#

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


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

我对每个建议的方法进行了计时,分别使用3个10字节的数组,执行了100万次循环。结果如下:

  • 使用System.Array.Copy的新字节数组-0.2187556秒
  • 使用System.Buffer.BlockCopy的新字节数组-0.1406286秒
  • 使用C#收益运算符的IEnumerable -0.0781270秒
  • 使用LINQ的Concat <>的IEnumerable -0.0781270秒
  • 我将每个数组的大小增加到100个元素,然后重新运行测试:

  • 使用System.Array.Copy的新字节数组-0.2812554秒
  • 使用System.Buffer.BlockCopy的新字节数组-0.2500048秒
  • 使用C#收益运算符的IEnumerable -0.0625012秒
  • 使用LINQ的Concat <>的IEnumerable -0.0781265秒
  • 我将每个数组的大小增加到1000个元素,然后重新运行测试:

  • 使用System.Array.Copy的新字节数组-1.0781457秒
  • 使用System.Buffer.BlockCopy的新字节数组-1.0156445秒
  • 使用C#收益运算符的IEnumerable -0.0625012秒
  • 使用LINQ的Concat <>的IEnumerable -0.0781265秒
  • 最后,我将每个数组的大小增加到一百万个元素,然后重新运行测试,每个循环仅执行4000次:

  • 使用System.Array.Copy的新字节数组-13.4533833秒
  • 使用System.Buffer.BlockCopy的新字节数组-13.1096267秒
  • 使用C#yield运算符的IEnumerable -0秒
  • 使用LINQ的Concat <>的IEnumerable -0秒
  • 因此,如果您需要一个新的字节数组,请使用

    1
    2
    3
    4
    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,则一定更喜欢LINQ的Concat <>方法。它仅比C#yield运算符稍慢,但更简洁,更优雅。

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

    如果您具有任意数量的数组,并且正在使用.NET 3.5,则可以使System.Buffer.BlockCopy解决方案更加通用,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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;
    }

    *注意:上面的代码块要求您在顶部添加以下名称空间才能起作用。

    1
    using System.Linq;

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

  • 使用System.Array.Copy的新字节数组-78.20550510秒
  • 使用System.Buffer.BlockCopy的新字节数组-77.89261900秒
  • 使用C#yield操作符的IEnumerable -551.7150161秒
  • 使用LINQ的Concat <>的IEnumerable -448.1804799秒
  • 关键是,了解创建数据结构的效率和使用结果的结构非常重要。仅关注创作的效率可能会忽略与使用相关的效率低下。乔恩·库德斯


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

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

    这两个一起排除了LINQ字节序列-带有yield的任何内容都将使得在不迭代整个序列的情况下无法获得最终大小。

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

    (编辑:我刚刚有了另一种想法。在复制数组和延迟读取它们之间存在很大的语义差异。请考虑一下,如果在调用Combine之后更改"源"数组之一中的数据会发生什么情况(或其他任何方法),但在使用结果之前-使用惰性评估,该更改将可见。使用即时复制,则不会显示。不同的情况将要求不同的行为-只是一些需要注意的事情。)

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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"版本需要首先创建一个字节数组数组,这会带来额外的效率低下。


    为了使代码更整洁,我将Matt的LINQ示例更进一步:

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

    就我而言,数组很小,因此我不关心性能。


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

    1
    2
    3
    4
    5
    6
    7
    8
    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操作符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }


    我实际上在使用Concat时遇到了一些问题...(数组在1000万中,实际上崩溃了)。

    我发现以下内容很简单,容易并且可以很好地工作而不会崩溃,并且它适用于任意数量的数组(不只是三个)(它使用LINQ):

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

    memorystream类对我来说做得很好。我无法使缓冲区类像内存流一样快地运行。

    1
    2
    3
    4
    5
    6
    using (MemoryStream ms = new MemoryStream())
    {
      ms.Write(BitConverter.GetBytes(22),0,4);
      ms.Write(BitConverter.GetBytes(44),0,4);
      ms.ToArray();
    }


    1
    2
    3
    4
    5
    6
    7
    8
        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();
            }
        }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        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;
        }


    可以使用泛型来组合数组。以下代码可以轻松扩展为三个数组。这样,您无需为不同类型的数组重复代码。以上某些答案对我来说似乎过于复杂。

    1
    2
    3
    4
    5
    6
    7
    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;
        }

    这是@Jon Skeet提供的答案的概括。
    它基本相同,只是可用于任何类型的数组,而不仅限于字节:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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;
    }


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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();
                }
            }


    康卡特是正确的答案,但由于某种原因,手动操作获得了最多的选票。如果您喜欢该答案,也许您会更希望此更通用的解决方案:

    1
    2
    3
    4
    5
    6
        IEnumerable<byte> Combine(params byte[][] arrays)
        {
            foreach (byte[] a in arrays)
                foreach (byte b in a)
                    yield return b;
        }

    这会让您执行以下操作:

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