如何在C#中连接两个数组?

361
int[] x = new int [] { 1, 2, 3};
int[] y = new int [] { 4, 5 };

int[] z = // your answer here...

Debug.Assert(z.SequenceEqual(new int[] { 1, 2, 3, 4, 5 }));

目前我使用的是

int[] z = x.Concat(y).ToArray();

有更简单或更高效的方法吗?


要小心使用Concat方法。在这篇帖子 Array Concatenation in C# 中解释了:

var z = x.Concat(y).ToArray();

对于大型数组来说效率不高。这意味着Concat方法仅适用于中等大小的数组(最多10000个元素)。


8
“efficient”指的是什么?这个代码已经足够简短了,所以我想你指的是CPU/RAM方面的效率? - TToni
4
不,通过Reflector的快速查看可以发现它使用了双倍大小的缓冲区。 - erikkallen
请明确一点,我需要将z定义为int[]类型。 - hwiechers
5
我并不是特别关心效率。(我说过更容易或更有效率的选择。)我提出这个问题是为了了解其他人如何处理这个常见的任务。 - hwiechers
24个回答

421
var z = new int[x.Length + y.Length];
x.CopyTo(z, 0);
y.CopyTo(z, x.Length);

11
为其辩护,C# 倾向于使用功能更强大的列表(lists),相对于数组而言。似乎仅在与非托管 C++ 进行交互调用时才需要使用数组。 - Levi Fuller
1
@LeviFuller C# 中另一个使用数组的地方是在可变数量的 params 参数中。 - ChrisW
3
许多系统例程返回数组而不是列表,这有点奇怪。例如,System.IO.Directory.GetFiles() 返回一个字符串数组。 - orion elenzil
11
这并不奇怪。一个数组是不可变的,而列表则不是。此外,相比于数组,列表使用更多的内存,除非调用了TrimExcess(在ToList中没有调用)来释放多余的内存。 - CSharpie
5
在访问数据时,数组比列表更快,因为列表只是在数组外层封装了一层,并且在调用索引器时存在开销。 - dmitry1100
显示剩余4条评论

113

试一下这个:

List<int> list = new List<int>();
list.AddRange(x);
list.AddRange(y);
int[] z = list.ToArray();

8
甚至可以使用 List<int> list = new List<int>(x); - Matthew Scharley
12
这样做比x.Concat(y)更有效率是为什么?虽然它可以正常工作,但我想知道有没有可以使它更好的方法? - Mike Two
14
为避免在调用AddRange时可能发生的重新调整大小,您可能希望将第一行更改为List<int> list = new List<int>(x.Length + y.Length); - Mike Two
8
这个问题要求提供更高效的解决方案。我知道标题听起来好像任何组合都可以,但实际上这个问题远远不止如此。看了一些答案,我觉得有些人只是回答了标题而已。因此,我认为如果这个答案值得赞同,就应该提及效率,因为这似乎是问题的重点。 - Mike Two
2
事实证明,AddRange 实际上是一个相当昂贵的过程,因此这个论坛上的第一个答案应该是首选方法:http://www.dotnetperls.com/insertrange - Liam
显示剩余8条评论

61

您可以编写扩展方法:

public static T[] Concat<T>(this T[] x, T[] y)
{
    if (x == null) throw new ArgumentNullException("x");
    if (y == null) throw new ArgumentNullException("y");
    int oldLen = x.Length;
    Array.Resize<T>(ref x, x.Length + y.Length);
    Array.Copy(y, 0, x, oldLen, y.Length);
    return x;
}

然后:

int[] x = {1,2,3}, y = {4,5};
int[] z = x.Concat(y); // {1,2,3,4,5}

1
难道不已经有一个适用于任何IEnumerable的扩展方法了吗? - Mike Two
2
是的,对于大多数情况我很乐意使用它。但是它们有很多开销。这取决于情况;98%的时间开销都没问题。但如果你处于2%的情况下,那么一些直接的memcopy/array工作会很方便。 - Marc Gravell
1
@nawfal,CopyCopyTo 更快吗?能详细解释一下吗? - skrebbel
1
@skrebbel 我之前的评论不准确。当时我进行了一些测试,发现复制速度更快。但现在看来它们只是相等的。我当时可能发现的是,总体而言,Marc的方法更有效,因为他将相同的实例传回,而在Zed的方法中,他正在创建一个新数组。抱歉 :) - nawfal
1
@Shimmy 不会的。在这个方法内部,x只是一个局部变量,将x作为ref传递给resize方法将创建一个新数组并更改(局部变量)x指向它。或者换句话说:传递到resize中的x和扩展方法内部的x是同一个变量,但是x不是作为ref传递到扩展方法中的,因此x与调用此扩展的范围内的变量不同。 - AnorZaken

49

这就是它:

using System.Linq;

int[] array1 = { 1, 3, 5 };
int[] array2 = { 0, 2, 4 };

// Concatenate array1 and array2.
int[] result1 = array1.Concat(array2).ToArray();

13
您的意思是int[] result = array1.ToList().Concat(array2.ToList()).toArray(); 我相信您不能直接在数组上应用Concat方法。 - Michail Michailidis
5
这个解决方案——z = x.Concat(y)——在上面的原始问题中提到过。 - Jon Schneider
3
没有使用 toArray() 的情况下会发生以下错误:无法隐式将类型 'System.Collections.Generic.IEnumerable<string>' 转换为 'string[]'。存在显式转换 (是否缺少强制转换?) - Tibor Udvari
5
这不是一个直接的答案。OP询问了int[] result = ?,而你在使用var时掩盖了你答案中的问题,因为你的结果将会是IEnumerable<int>,而不是int[]。(这就是我不喜欢在方法返回值上使用var的原因之一)。 - David S.
2
这个方法是问题中使用的方法,所以这个答案没有提供新信息。如果没有 .ToArray() 调用,这段代码将不会返回一个真正的数组,因此它也是一个错误的答案。 - Mani Gandham
显示剩余2条评论

45

我选择了一种更通用的解决方案,可以连接任意数量的相同类型的一维数组。(我一次连接3个以上。)

我的函数:

public static T[] ConcatArrays<T>(params T[][] list)
{
    var result = new T[list.Sum(a => a.Length)];
    int offset = 0;
    for (int x = 0; x < list.Length; x++)
    {
        list[x].CopyTo(result, offset);
        offset += list[x].Length;
    }
    return result;
}

使用方法:

int[] a = new int[] { 1, 2, 3 };
int[] b = new int[] { 4, 5, 6 };
int[] c = new int[] { 7, 8 };
var y = ConcatArrays(a, b, c); //Results in int[] {1,2,3,4,5,6,7,8}

好函数,谢谢!将“params T[][]”更改为“this T[][]”,使其成为扩展。 - Mark

14

使用Buffer.BlockCopy比使用Array.CopyTo更加高效(快速)。

int[] x = new int [] { 1, 2, 3};
int[] y = new int [] { 4, 5 };

int[] z = new int[x.Length + y.Length];
var byteIndex = x.Length * sizeof(int);
Buffer.BlockCopy(x, 0, z, 0, byteIndex);
Buffer.BlockCopy(y, 0, z, byteIndex, y.Length * sizeof(int));

我编写了一个简单的测试程序来“热身Jitter”,在我的机器上以发布模式编译并在没有附加调试器的情况下运行。

对于问题中的示例进行了1000万次迭代

Concat花费了3088ms

CopyTo花费了1079ms

BlockCopy花费了603ms

如果我将测试数组更改为从0到99的两个序列,则会得到类似于以下结果的结果

Concat花费了45945ms

CopyTo花费了2230ms

BlockCopy花费了1689ms

从这些结果可以断言,CopyToBlockCopy方法比Concat方法更有效率,并且如果性能是目标,则BlockCopy优于CopyTo

需要说明的是,如果不考虑性能或迭代次数较少,请选择您认为最容易的方法。Buffer.BlockCopy还提供了某些超出本问题范围的类型转换实用程序。


我尝试将其转换为VB代码,但没有成功。显然,VB.NET没有sizeOf函数。 - Tyler Wayne
1
@TylerWayne 这是真的,很糟糕。你可以用字面量4替换sizeOf(int)。也许声明一个常量。 - Jodrell

14

我知道OP只是稍微关心一下性能问题。较大的数组可能会得到不同的结果(请参见@kurdishTree)。而且通常并不重要(@jordan.peoples)。尽管如此,我还是很好奇,因此失去了理智(正如@TigerShark所解释的那样)......我的意思是,我根据原始问题和所有答案编写了一个简单的测试......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace concat
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] x = new int [] { 1, 2, 3};
            int[] y = new int [] { 4, 5 };


            int itter = 50000;
            Console.WriteLine("test iterations: {0}", itter);

            DateTime startTest = DateTime.Now;
            for(int  i = 0; i < itter; i++)
            {
                int[] z;
                z = x.Concat(y).ToArray();
            }
            Console.WriteLine ("Concat Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks );

            startTest = DateTime.Now;
            for(int  i = 0; i < itter; i++)
            {
                var vz = new int[x.Length + y.Length];
                x.CopyTo(vz, 0);
                y.CopyTo(vz, x.Length);
            }
            Console.WriteLine ("CopyTo Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks );

            startTest = DateTime.Now;
            for(int  i = 0; i < itter; i++)
            {
                List<int> list = new List<int>();
                list.AddRange(x);
                list.AddRange(y);
                int[] z = list.ToArray();
            }
            Console.WriteLine("list.AddRange Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] z = Methods.Concat(x, y);
            }
            Console.WriteLine("Concat(x, y) Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] z = Methods.ConcatArrays(x, y);
            }
            Console.WriteLine("ConcatArrays Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] z = Methods.SSConcat(x, y);
            }
            Console.WriteLine("SSConcat Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int k = 0; k < itter; k++)
            {
                int[] three = new int[x.Length + y.Length];

                int idx = 0;

                for (int i = 0; i < x.Length; i++)
                    three[idx++] = x[i];
                for (int j = 0; j < y.Length; j++)
                    three[idx++] = y[j];
            }
            Console.WriteLine("Roll your own Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);


            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] z = Methods.ConcatArraysLinq(x, y);
            }
            Console.WriteLine("ConcatArraysLinq Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] z = Methods.ConcatArraysLambda(x, y);
            }
            Console.WriteLine("ConcatArraysLambda Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                List<int> targetList = new List<int>(x);
                targetList.Concat(y);
            }
            Console.WriteLine("targetList.Concat(y) Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);

            startTest = DateTime.Now;
            for (int i = 0; i < itter; i++)
            {
                int[] result = x.ToList().Concat(y.ToList()).ToArray();
            }
            Console.WriteLine("x.ToList().Concat(y.ToList()).ToArray() Test Time in ticks: {0}", (DateTime.Now - startTest).Ticks);
        }
    }
    static class Methods
    {
        public static T[] Concat<T>(this T[] x, T[] y)
        {
            if (x == null) throw new ArgumentNullException("x");
            if (y == null) throw new ArgumentNullException("y");
            int oldLen = x.Length;
            Array.Resize<T>(ref x, x.Length + y.Length);
            Array.Copy(y, 0, x, oldLen, y.Length);
            return x;
        }

        public static T[] ConcatArrays<T>(params T[][] list)
        {
            var result = new T[list.Sum(a => a.Length)];
            int offset = 0;
            for (int x = 0; x < list.Length; x++)
            {
                list[x].CopyTo(result, offset);
                offset += list[x].Length;
            }
            return result;
        }


        public static T[] SSConcat<T>(this T[] first, params T[][] arrays)
        {
            int length = first.Length;
            foreach (T[] array in arrays)
            {
                length += array.Length;
            }
            T[] result = new T[length];
            length = first.Length;
            Array.Copy(first, 0, result, 0, first.Length);
            foreach (T[] array in arrays)
            {
                Array.Copy(array, 0, result, length, array.Length);
                length += array.Length;
            }
            return result;
        }

        public static T[] ConcatArraysLinq<T>(params T[][] arrays)
        {
            return (from array in arrays
                    from arr in array
                    select arr).ToArray();
        }

        public static T[] ConcatArraysLambda<T>(params T[][] arrays)
        {
            return arrays.SelectMany(array => array.Select(arr => arr)).ToArray();
        }
    }

}

结果如下:

结果为:

输入图像描述

自己动手制作获胜。


公平地说,使用方法的方法可能会在我的系统上增加大约10,000个时钟周期。 - amalgamate
3
我在Visual Studio 2013的发布模式下运行了你的代码,并发现如果被测试的数组不像你那么小(比如1000个元素),那么使用CopyTo会更快,速度大约比“Roll your own”快三倍。 - Mr. Ree
@Mr.Ree 是的,我的数组确实很小。谢谢。如果块复制做得更好,我会很感兴趣... - amalgamate
1
感谢您的完整研究。但是在方法 targetList.Concat(y) 中有一个小错误。这个指令并没有真正执行任何操作,它只是创建了一个枚举器。正确的解决方案应该是类似于 var z = targetList.Concat(y).ToArray(); 的东西。我在 .NET 6 中对您的代码进行了测试,发现 CopyTo 是最好的方法。 - Oleg Nesterov

9
你可以将ToArray()调用取消。在调用Concat之后,你需要它成为一个数组的原因是什么?
调用Concat会创建两个数组的迭代器。它不会创建新的数组,因此你没有为新数组使用更多的内存。当你调用ToArray时,你实际上创建了一个新的数组并占用了新数组的内存。
所以如果你只需要轻松地遍历这两个数组,那就直接调用Concat即可。

虽然这是有用的建议,但它不是一个答案。它应该是一条评论。 - Paul Childs

8

以下是我的答案:

int[] z = new List<string>()
    .Concat(a)
    .Concat(b)
    .Concat(c)
    .ToArray();

这个方法可以在初始化级别使用,例如用于定义静态数组的静态连接:

public static int[] a = new int [] { 1, 2, 3, 4, 5 };
public static int[] b = new int [] { 6, 7, 8 };
public static int[] c = new int [] { 9, 10 };

public static int[] z = new List<string>()
    .Concat(a)
    .Concat(b)
    .Concat(c)
    .ToArray();

然而,它有两个需要考虑的限制:
  • Concat 方法创建了一个同时遍历两个数组的迭代器:它不会创建新的数组,因此在使用内存方面十分高效;但是,随后使用ToArray 将抵消此优势,因为它实际上会创建一个新的数组并占用该新数组的内存。
  • 正如@Jodrell所说,对于大型数组,使用Concat将非常低效:它只适用于中等大小的数组。

如果必须要追求性能,则应使用以下方法:

/// <summary>
/// Concatenates two or more arrays into a single one.
/// </summary>
public static T[] Concat<T>(params T[][] arrays)
{
    // return (from array in arrays from arr in array select arr).ToArray();

    var result = new T[arrays.Sum(a => a.Length)];
    int offset = 0;
    for (int x = 0; x < arrays.Length; x++)
    {
        arrays[x].CopyTo(result, offset);
        offset += arrays[x].Length;
    }
    return result;
}

或者(对于喜欢一行代码的粉丝):
int[] z = (from arrays in new[] { a, b, c } from arr in arrays select arr).ToArray();

虽然后一种方法更加优雅,但前一种方法在性能方面肯定更好。

如需更多信息,请参见我博客上的这篇文章


8
晚来的答案 :-).
public static class ArrayExtention
    {

        public static T[] Concatenate<T>(this T[] array1, T[] array2)
        {
            T[] result = new T[array1.Length + array2.Length];
            array1.CopyTo(result, 0);
            array2.CopyTo(result, array1.Length);
            return result;
        }

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接