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.