C #에서 둘 이상의 바이트 배열을 결합하는 가장 좋은 방법


238

C #에는 3 바이트 배열이 있는데 하나로 결합해야합니다. 이 작업을 완료하는 가장 효율적인 방법은 무엇입니까?


3
구체적으로 요구 사항은 무엇입니까? 배열의 합집합을 취하거나 동일한 값의 여러 인스턴스를 보존하고 있습니까? 항목을 정렬 하시겠습니까, 아니면 초기 배열의 순서를 유지 하시겠습니까? 속도 또는 코드 라인에서 효율성을 찾고 있습니까?
— Jason

"최고"는 당신의 요구 사항에 달려 있습니다.
— Ady

7
LINQ를 사용할 수 있다면 다음 Concat방법을 사용할 수 있습니다 .IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
— casperOne

1
귀하의 질문에 대해 더 명확하게 노력하십시오. 이 모호한 질문은 시간을내어 답변을받을만큼 좋은 사람들 사이에서 많은 혼란을 야기했습니다.
— Drew Noakes

답변:


326

기본 유형 (바이트 포함)의 경우을 System.Buffer.BlockCopy대신 사용하십시오 System.Array.Copy. 더 빠릅니다.

루프에서 제안 된 각 메소드의 시간을 각각 10 바이트의 3 개의 배열을 사용하여 백만 번 실행했습니다. 결과는 다음과 같습니다.

  1. System.Array.Copy -0.2187556 초를 사용하는 새로운 바이트 배열
  2. System.Buffer.BlockCopy -0.1406286 초를 사용하는 새로운 바이트 배열
  3. C # yield 연산자를 사용하는 IEnumerable <byte>-0.0781270 초
  4. LINQ의 Concat <>을 사용한 IEnumerable <byte>-0.0781270 초

각 배열의 크기를 100 개 요소로 늘리고 테스트를 다시 실행했습니다.

  1. System.Array.Copy -0.2812554 초를 사용하는 새로운 바이트 배열
  2. System.Buffer.BlockCopy -0.2500048 초를 사용하는 새로운 바이트 배열
  3. C # yield 연산자를 사용하는 IEnumerable <byte>-0.0625012 초
  4. LINQ의 Concat <>을 사용한 IEnumerable <byte>-0.0781265 초

각 배열의 크기를 1000 요소로 늘리고 테스트를 다시 실행했습니다.

  1. System.Array.Copy -1.0781457 초를 사용하는 새로운 바이트 배열
  2. System.Buffer.BlockCopy -1.0156445 초를 사용하는 새로운 바이트 배열
  3. C # yield 연산자를 사용하는 IEnumerable <byte>-0.0625012 초
  4. LINQ의 Concat <>을 사용한 IEnumerable <byte>-0.0781265 초

마지막으로, 1 개 백만 요소 각 어레이의 크기를 증가시키고 각각의 루프를 실행하는 시험을 다시 실행 단지 4000 시간 :

  1. System.Array.Copy -13.4533833 초를 사용하는 새로운 바이트 배열
  2. System.Buffer.BlockCopy -13.1096267 초를 사용하는 새로운 바이트 배열
  3. C # yield 연산자를 사용하는 IEnumerable <byte>-0 초
  4. LINQ의 Concat <>을 사용한 IEnumerable <byte>-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>, 확실히했던 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;

후속 데이터 구조 (바이트 배열 대 IEnumerable <byte>)의 반복에 관한 Jon Skeet의 요점에 대해, 마지막 타이밍 테스트 (1 백만 요소, 4000 반복)를 다시 실행하여 각 배열마다 전체 배열을 반복하는 루프를 추가합니다 통과하다:

  1. System.Array.Copy -78.20550510 초를 사용하는 새로운 바이트 배열
  2. System.Buffer.BlockCopy -77.89261900 초를 사용하는 새로운 바이트 배열
  3. C # yield 연산자를 사용하는 IEnumerable <byte>-551.7150161 초
  4. LINQ의 Concat <>을 사용한 IEnumerable <byte>-448.1804799 초

요점은 결과 데이터 구조 의 생성 및 사용 의 효율성을 이해하는 것이 매우 중요하다는 것입니다 . 창작의 효율성에 집중하는 것만으로도 사용과 관련된 비 효율성을 간과 할 수 있습니다. 쿠도스, 존


61
그러나 실제로 질문이 요구하는 것처럼 마지막에 배열로 변환합니까? 그렇지 않다면 물론 빠르지 만 요구 사항을 충족시키지 못합니다.
— Jon Skeet

18
Re : Matt Davis – "요구 사항"이 IEnumerable을 배열로 변환해야하는지 여부는 중요하지 않습니다. 요구 사항 만 있으면 결과가 실제로 fasion에서 사용 된다는 것 입니다. IEnumerable에서 성능 테스트가 너무 낮은 이유 는 실제로 아무것도하지 않기 때문입니다 ! LINQ는 결과를 사용하려고 시도 할 때까지 작업을 수행하지 않습니다. 이런 이유로 나는 당신의 대답이 객관적으로 부정확하다고 생각하고 다른 사람들이 성능에 관심이 없다면 절대해서는 안될 때 LINQ를 사용하게 할 수 있습니다.
— csauve

12
귀하의 업데이트를 포함한 전체 답변을 읽었으며 내 의견은 그대로 있습니다. 나는 내가 파티에 늦게 참여하고 있다는 것을 알고 있지만, 그 대답은 엄청나게 오도되고 상반기는 특허 적으로 거짓 입니다.
— csauve

14
허위 및 오도 정보가 포함 된 답변이 최고 투표 답변 인 이유는 무엇입니까 (Jon Skeet) 누군가 OP 질문에 답변조차하지 않았다고 지적한 후에 원래 진술 을 완전히 무효화하도록 편집 되었습니까?
— MrCC

3
오해의 소지가 있습니다. 판조차도 질문에 대답하지 않습니다.
— Serge Profafilecebook

154

많은 답변이 명시된 요구 사항을 무시하는 것 같습니다.

  • 결과는 바이트 배열이어야합니다
  • 가능한 한 효율적이어야합니다

이 두 가지가 함께 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;
}

물론 "params"버전은 바이트 배열을 먼저 생성해야하므로 추가적인 비효율이 발생합니다.


존, 나는 당신이 무슨 말을하는지 정확하게 이해합니다. 내 유일한 요점은 때로는 다른 솔루션이 존재한다는 것을 깨닫지 않고 이미 특정 구현을 염두에두고 질문을하는 것입니다. 대안을 제시하지 않고 단순히 답변을 제공하는 것은 나에게 불만처럼 보입니다. 생각?
— Matt Davis

1
@ 매트 : 예, 대안을 제공하는 것은 좋다 - 그러나 그것의 가치들이 있다는 설명 이다 대안이 질문 존재가 질문에 대한 답변으로 그들을 전달보다는. (나는 당신이 그렇게 말한 것이 아닙니다-당신의 대답은 매우 좋습니다.)
— Jon Skeet

4
(저는 성능 벤치 마크에서 게으른 평가에 불공정 한 이점을 제공하지 않기 위해 각 경우에 모든 결과를 처리하는 데 걸린 시간을 보여야한다고 생각합니다.)
— Jon Skeet

1
"결과는 배열이어야 함"의 요구 사항을 충족하지 않더라도 "결과는 일부 fasion에서 사용해야합니다"라는 요구 사항을 충족하면 LINQ가 최적이 아닙니다. 결과를 사용하려면 요구 사항이 암시 적이어야한다고 생각합니다!
— csauve

2
@andleer : Buffer.BlockCopy는 다른 것 외에도 기본 유형에서만 작동합니다.
— Jon Skeet

44

코드 청결을 위해 Matt의 LINQ 예제를 한 단계 더 발전 시켰습니다.

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

필자의 경우 배열이 작으므로 성능에 대해 걱정하지 않습니다.


3
짧고 간단한 솔루션, 성능 테스트는 좋을 것입니다!
— Sebastian

3
이것은 명확하고 읽기 쉽고 외부 라이브러리 / 헬퍼가 필요하지 않으며 개발 시간면에서 매우 효율적입니다. 런타임 성능이 중요하지 않은 경우에 좋습니다.
— binki

28

단순히 새로운 바이트 배열이 필요한 경우 다음을 사용하십시오.

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

큰 스트림을 병합하기 위해 두 번째 옵션과 비슷한 작업을 수행했으며 매력처럼 작동했습니다. :)
— Greg D

2
두 번째 옵션은 훌륭합니다. +1.
— R. Martinho Fernandes

11

실제로 Concat을 사용하는 데 몇 가지 문제가 발생했습니다 ...

나는 다음이 간단하고 쉽고, 나에게 충돌하지 않고 충분히 잘 작동한다는 것을 알았으며 3 개가 아닌 여러 배열 (LINQ 사용)에서 작동합니다.

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

6

메모리 스트림 클래스는이 작업을 꽤 잘 수행합니다. 버퍼 스트림을 메모리 스트림만큼 빠르게 실행할 수 없었습니다.

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

3
QWE가 언급 한 바와 같이, 나는 루프에서 10,000,000 번 테스트를했고, MemoryStream을가 Buffer.BlockCopy에 비해 290퍼센트 느려질 나왔다
— ESAC

경우에 따라 개별 배열 길이를 모른 채 열거 가능한 배열을 반복 할 수 있습니다. 이것은이 시나리오에서 잘 작동합니다. BlockCopy는 대상 어레이가 미리 생성되어 있어야합니다
— Sentinel

@Sentinel이 말했듯 이이 답변은 필자가 작성해야 할 물건의 크기를 알지 못하고 매우 깨끗하게 할 수 있기 때문에 나에게 완벽합니다. 또한 .NET Core 3의 [ReadOnly] Span <byte>에서도 훌륭합니다!
— 물

크기의 최종 크기로 MemoryStream을 초기화하면 다시 생성되지 않으며 @esac가 더 빠릅니다.
— 도노 남

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

불행히도 이것은 모든 유형에서 작동하지는 않습니다. Marshal.SizeOf ()는 많은 유형의 크기를 반환 할 수 없습니다 (문자열 배열과 함께이 방법을 사용하면 "System.String"유형은 관리되지 않는 구조로 마샬링 할 수 없습니다. 의미있는 크기 또는 오프셋을 계산할 수 있습니다. "유형 매개 변수를 참조 유형으로 만 제한하여 (을 추가하여 where T : struct) 시도 할 수는 있지만 CLR 내부 전문가가 아닌 경우-특정 구조체에서 예외가 발생할 수 있는지 여부를 알 수 없습니다 (예 : 참조 유형 필드를 포함하는 경우)
— Daniel Scott

2
    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();
        }
    }

이 코드 샘플의 기능에 대한 간단한 설명을 게시 한 경우 답변이 더 나을 수 있습니다.
— AFract

1
[1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 바이트 배열을 하나의 큰 바이트 배열로 연결합니다. , 6,7]
— Peter Ertl

1

제네릭을 사용하여 배열을 결합 할 수 있습니다. 다음 코드는 3 개의 배열로 쉽게 확장 할 수 있습니다. 이렇게하면 다른 유형의 배열에 대해 코드를 복제 할 필요가 없습니다. 위의 답변 중 일부는 나에게 너무 복잡해 보입니다.

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

0

@Jon Skeet이 제공하는 답변의 일반화는 다음과 같습니다. 기본적으로 동일하며 바이트뿐만 아니라 모든 유형의 배열에 대해서만 사용할 수 있습니다.

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

3
위험! 이 메소드는 1 바이트보다 긴 요소 (바이트 배열 이외의 모든 것)가있는 배열 유형에서는 속성을 사용할 수 없습니다. Buffer.BlockCopy ()는 여러 배열 요소가 아닌 바이트 수로 작동합니다. 바이트 배열과 함께 쉽게 사용할 수있는 이유는 배열의 모든 요소가 단일 바이트이므로 배열의 실제 길이는 요소 수와 동일하기 때문입니다. John의 byte [] 메소드를 일반 메소드로 변환하려면 단일 배열 요소의 바이트 길이로 모든 오프셋과 길이를 곱해야합니다. 그렇지 않으면 모든 데이터를 복사하지는 않습니다.
— Daniel Scott

2
일반적 으로이 작업을 수행하려면 단일 요소의 크기를 계산하고 sizeof(...)복사하려는 요소 수로 곱하지만 sizeof는 일반 유형과 함께 사용할 수 없습니다. 일부 유형의 경우을 사용할 수 Marshal.SizeOf(typeof(T))있지만 특정 유형 (예 : 문자열)의 런타임 오류가 발생합니다. CLR 유형의 내부 작동에 대해 더 철저한 지식을 가진 사람은 여기에서 가능한 모든 함정을 지적 할 수 있습니다. [BlockCopy를 사용하여] 일반 배열 연결 방법을 작성하는 것은 쉬운 일이 아닙니다.
— Daniel Scott

2
마지막으로 Array.Copy를 대신 사용하여 위와 거의 동일한 방식으로 성능을 약간 낮추어 이와 같은 일반적인 배열 연결 방법을 작성할 수 있습니다. 모든 Buffer.BlockCopy 호출을 Array.Copy 호출로 바꾸십시오.
— Daniel Scott

0
    /// <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();

기부 해 주셔서 감사합니다. 이미 10 년 전부터 이에 대한 높은 평가를받은 다수의 답변이 있으므로, 귀하의 접근 방식을 구별하는 것에 대한 설명을 제공하는 것이 유용 할 것입니다. 왜 누군가가 예를 들어 받아 들인 대답 대신 이것을 사용해야합니까?
— Jeremy Caney

이해하기 쉬운 코드가 있기 때문에 확장 메서드를 사용하는 것이 좋습니다. 이 코드는 시작 색인과 개수 및 연결이있는 두 개의 배열을 선택합니다. 또한이 방법이 확장되었습니다. 따라서 이것은 항상 준비된 모든 어레이 유형에 적용됩니다.
— Mehmet ÜNLÜ

그것은 나에게 좋은 의미입니다! 해당 정보를 포함하도록 질문을 편집 하시겠습니까? 나는 미래의 독자들에게 그것을 미리 이해하는 것이 가치가 있다고 생각하므로, 그들은 당신의 접근법을 기존의 답변과 빠르게 구별 할 수 있습니다. 감사합니다!
— Jeremy Caney

-1

바이트 배열 목록을 전달하면이 함수는 바이트 배열 (병합)을 반환합니다. 이것은 내가 생각하는 최고의 솔루션입니다 :).

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

-5

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();

5
이 질문은 특히 가장 효율적인 솔루션을 요구합니다 . Enumerable.ToArray는 최종 배열의 크기를 알 수 없기 때문에 매우 효율적이지 않습니다. 반면에 수작업 기술은 가능합니다.
— Jon Skeet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.