İki veya daha fazla bayt dizisini C # ile birleştirmenin en iyi yolu


239

Ben bir birleştirmek gerekir C # 3 bayt dizilerim var. Bu görevi tamamlamak için en etkili yöntem ne olurdu?


3
Özellikle gereksinimleriniz nelerdir? Dizilerin birleşimini mi alıyorsunuz veya aynı değere sahip birden çok örneği mi koruyorsunuz? Öğelerin sıralanmasını mı istiyorsunuz yoksa ilk dizilerdeki sıralamayı mı korumak istiyorsunuz? Hızda veya kod satırlarında verimlilik mi arıyorsunuz?
Jason

Love it, "en iyi" gereksinimlerinizin ne olduğuna bağlıdır.
Ady

7
LINQ kullanabiliyorsanız, sadece şu Concatyöntemi kullanabilirsiniz :IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne

1
Lütfen sorularınızda daha net olmaya çalışın. Bu belirsiz soru, size cevap vermek için zaman ayıracak kadar iyi olan insanlar arasında çok karışıklığa neden oldu.
Drew Noakes

Yanıtlar:


327

İlkel türler için (bayt dahil) System.Buffer.BlockCopyyerine kullanın System.Array.Copy. O daha hızlı.

Önerilen yöntemlerin her birini, her biri 10 baytlık 3 dizi kullanarak 1 milyon kez yürütülen bir döngüde zamanladım. Sonuçlar burada:

  1. Yeni Bayt Dizisi kullanma System.Array.Copy - 0.2187556 saniye
  2. Yeni Byte Dizisi kullanarak System.Buffer.BlockCopy - 0.1406286 saniye
  3. I # C # verim operatörü kullanılarak sayısız <bayt> - 0.0781270 saniye
  4. I LINQ Concat <> - 0.0781270 saniye kullanarak sayısız <bayt>

Her dizinin boyutunu 100 öğeye çıkardım ve testi yeniden çalıştırdım:

  1. Yeni Byte Dizisi kullanarak System.Array.Copy - 0.2812554 saniye
  2. Yeni Byte Dizisi kullanarak System.Buffer.BlockCopy - 0.2500048 saniye
  3. I # C # verim operatörü kullanarak sayısız <bayt> - 0.0625012 saniye
  4. LINQ Concat <> ile sayısız <bayt> - 0.0781265 saniye

Her dizinin boyutunu 1000 öğeye çıkardım ve testi yeniden çalıştırdım:

  1. Yeni Byte Dizisi System.Array.Copy - 1.0781457 saniye
  2. Yeni Byte Dizisi System.Buffer.BlockCopy - 1.0156445 saniye
  3. I # C # verim operatörü kullanarak sayısız <bayt> - 0.0625012 saniye
  4. LINQ Concat <> ile sayısız <bayt> - 0.0781265 saniye

Son olarak, her dizinin boyutunu 1 milyon elemente artırdım ve testi tekrarladım, her döngüyü sadece 4000 kez yürüttüm:

  1. Yeni Byte Dizisi kullanarak System.Array.Copy - 13.4533833 saniye
  2. Yeni Byte Array System.Buffer.BlockCopy - - 13.1096267 saniye kullanarak
  3. I # C # verim operatörü kullanan <bayt> - 0 saniye
  4. IEnumerable <bayt> LINQ Concat <> - 0 saniye kullanarak

Bu nedenle, yeni bir bayt dizisine ihtiyacınız varsa şunu kullanın:

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

Ancak, bir kullanabiliyorsanız IEnumerable<byte>, LINQ'nun Concat <> yöntemini KESİNLİKLE tercih edin. C # verim operatöründen sadece biraz daha yavaştır, ancak daha özlü ve daha zariftir.

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

Rasgele sayıda diziniz varsa ve .NET 3.5 kullanıyorsanız System.Buffer.BlockCopyçözümü şu şekilde daha genel yapabilirsiniz :

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

* Not: Yukarıdaki blok, çalışması için en üstte aşağıdaki ad alanını eklemenizi gerektirir.

using System.Linq;

Jon Skeet'in sonraki veri yapılarının yinelenmesi ile ilgili noktasına (bayt dizisi ile IEnumerable <bayt>), son zamanlama testini (1 milyon öğe, 4000 yineleme) yeniden çalıştırdım ve her bir dizi ile tam dizi üzerinde yinelenen bir döngü ekledim geçmek:

  1. Yeni Byte Dizisi kullanarak System.Array.Copy - 78.20550510 saniye
  2. Yeni Bayt Dizisi kullanarak System.Buffer.BlockCopy - 77.89261900 saniye
  3. I # C # verim operatörü kullanarak sayısız <bayt> - 551.7150161 saniye
  4. LINQ Concat <> - 448.1804799 saniye kullanarak sayısız <bayt>

Mesele şu ki, ortaya çıkan veri yapısının hem yaratılmasının hem de kullanımının verimliliğini anlamak ÇOK önemlidir . Sadece yaratılışın verimliliğine odaklanmak, kullanımla ilgili verimsizliği göz ardı edebilir. Kudos, Jon.


61
Ama aslında sorunun gerektirdiği gibi onu sonunda bir diziye mi dönüştürüyorsunuz? Değilse, elbette daha hızlı - ama gereksinimleri karşılamıyor.
Jon Skeet

18
Re: Matt Davis - "Gereksinimlerinizin" IEnumerable bir dizi haline dönüştürmek gerekiyorsa önemli değil - tüm ihtiyaçlarınızı sonuç aslında bazı fasion kullanılan olmasıdır . IEnumerable'daki performans testlerinizin bu kadar düşük olmasının nedeni, aslında hiçbir şey yapmamanızdır ! LINQ, siz sonuçları kullanmaya çalışana kadar çalışmalarını gerçekleştirmez. Bu nedenle cevabınızı objektif olarak yanlış buluyorum ve başkalarının performansı önemsemeleri halinde kesinlikle LINQ kullanmasına neden olabilirim.
csauve

12
Güncellemeniz dahil tüm cevabı okudum, yorumum duruyor. Partiye geç katıldığımı biliyorum, ama cevap çok yanıltıcı ve ilk yarı patentli olarak yanlış .
csauve

14
Yanlış ve yanıltıcı bilgi içeren cevap neden en çok oylanan cevaptır ve birisi (Jon Skeet) OP sorusuna bile cevap vermediğine işaret ettikten sonra orijinal ifadesini temel olarak tamamen geçersiz kılmak için düzenlenmiştir ?
MrCC

3
Yanıltıcı cevap. Baskı bile soruyu cevaplamıyor.
Serge Profafilecebook

154

Cevapların çoğu bana belirtilen gereklilikleri görmezden geliyor gibi geliyor:

  • Sonuç bir bayt dizisi olmalıdır
  • Mümkün olduğunca verimli olmalı

Bu ikisi birlikte bir LINQ bayt dizisini göz ardı eder - olan her şey yield, tüm diziyi tekrarlamadan son boyutu elde etmeyi imkansız hale getirecektir.

Bunlar elbette gerçek gereksinimler değilse , LINQ mükemmel bir çözüm (veya IList<T>uygulama) olabilir. Ancak, Superdumbell'in ne istediğini bildiğini varsayacağım.

(DÜZENLEME: Başka bir düşüncem vardı. Dizilerin bir kopyasını oluşturmak ve tembel bir şekilde okumak arasında büyük bir anlamsal fark var. Combine(Veya herhangi bir şeyi çağırdıktan sonra) ) yöntemini kullanabilirsiniz, ancak sonucu kullanmadan önce - tembel değerlendirme ile, bu değişiklik görünür olacaktır. Hemen bir kopya ile, olmayacaktır. Farklı durumlar farklı davranışlar gerektirecektir - sadece farkında olunması gereken bir şey.)

İşte benim önerilen yöntemler - diğer cevapların bazılarında bulunanlara çok benzer, kesinlikle :)

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

Tabii ki "params" sürümü, ilk önce bir dizi bayt dizisi oluşturmayı gerektirir ve bu da ekstra verimsizlik getirir.


Jon, tam olarak ne dediğini anlıyorum. Benim tek noktam, bazen diğer çözümlerin var olduğunu fark etmeden akılda tutulmuş belirli bir uygulama ile sorular sorulmasıdır. Sadece alternatifler sunmadan bir cevap vermek bana bir kötülük gibi geliyor. Düşünceler?
Matt Davis

1
@Matt: Evet, alternatifleri sunan iyidir - ama onun değerinde onlar açıklayan olan alternatifler soru varlık soruların cevap olarak onları aktarmak yerine. (Bunu yaptığınızı söylemiyorum - cevabınız çok iyi.)
Jon Skeet

4
(Her ne kadar performans ölçütünüzün tembel değerlendirmeye haksız bir avantaj vermekten kaçınmak için her durumda tüm sonuçlardan geçmek için harcanan zamanı göstermesi gerektiğini düşünüyorum.)
Jon Skeet

1
"Sonuç bir dizi olmalı" gereksinimini karşılamasanız bile, "bazı fasionlarda sonuç kullanılmalıdır" gereksinimini karşılamak LINQ'yu en uygun hale getirmez. Sonucu kullanabilmek için bu gereksinimin örtük olması gerektiğini düşünüyorum!
csauve

2
@andleer: Buffer.BlockCopy yalnızca ilkel türlerle çalışır.
Jon Skeet

44

Kod temizliği için Matt'in LINQ örneğini bir adım ileri götürdüm:

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

Benim durumumda, diziler küçük, bu yüzden performansla ilgili değilim.


3
Kısa ve basit bir çözüm, bir performans testi harika olurdu!
Sebastian

3
Bu kesinlikle açık, okunabilir, harici kütüphaneler / yardımcılar gerektirmez ve geliştirme süresi açısından oldukça etkilidir. Çalışma zamanı performansı kritik olmadığında harika.
binki

28

Yeni bir bayt dizisine ihtiyacınız varsa, aşağıdakileri kullanın:

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

Alternatif olarak, yalnızca tek bir IEnumerable'a ihtiyacınız varsa, C # 2.0 verim operatörünü kullanmayı düşünün:

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

Büyük akarsuları birleştirmek için 2. seçeneğinize benzer bir şey yaptım, bir cazibe gibi çalıştı. :)
Greg D

2
İkinci seçenek harika. +1.
R. Martinho Fernandes

11

Concat'ı kullanmakla ilgili bazı sorunlar yaşadım ... (10 milyondaki dizilerle, aslında çöktü).

Aşağıdakileri basit, kolay ve üzerime çarpmadan yeterince iyi buldum ve HERHANGİ dizi sayısı için çalışıyor (sadece üç değil) (LINQ kullanıyor):

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

6

Memorystream sınıfı bu işi benim için gayet güzel yapıyor. Arabellek sınıfını bellek akışı kadar hızlı çalıştıramadım.

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

3
Qwe'nin belirttiği gibi, 10.000.000 kez bir döngüde bir test yaptım ve MemoryStream Buffer'dan% 290 daha zayıf çıktı.BlockCopy
esac

Bazı durumlarda, münferit dizi uzunlukları hakkında önceden bilgi sahibi olmadan bir dizi dizi üzerinde yineleme yapıyor olabilirsiniz. Bu senaryoda bu iyi çalışır. BlockCopy bir hedef dizinin önceden oluşturulmasına dayanır
Sentinel

@Sentinel'in söylediği gibi, bu cevap benim için mükemmel çünkü yazmam gereken şeylerin boyutu hakkında hiçbir bilgim yok ve işleri çok temiz yapmama izin veriyor. Ayrıca .NET Core 3'ün [ReadOnly] Span <bayt> ile iyi oynuyor!
Su

MemoryStream'i boyutun son boyutuyla başlatırsanız, yeniden oluşturulmaz ve @esac daha hızlı olur.
Tono Nam

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

Ne yazık ki bu tüm tiplerle çalışmaz. Marshal.SizeOf (), birçok tür için bir boyut döndüremez (bu yöntemi dizelerin dizileriyle kullanmayı deneyin ve "Type 'System.String" özelliğinin yönetilmeyen bir yapı olarak sıralanamayacağını; anlamlı boyut veya Type parametresini yalnızca referans türleriyle (ekleyerek where T : struct) sınırlamayı deneyebilirsiniz , ancak - CLR'nin iç kısımlarında uzman olmamakla birlikte - belirli yapılarda da istisnalar alıp alamayacağınızı söyleyemedim. (örneğin, referans türü alanları içeriyorsa)
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();
        }
    }

Bu kod örneğinin ne işe yaradığına dair küçük bir açıklama gönderirseniz, yanıtınız daha iyi olabilir.
AFract

1
bir bayt dizisi dizisini büyük bir bayt dizisine birleştirir (böyle): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl

1

Dizileri birleştirmek için jenerikleri kullanabilir. Aşağıdaki kod kolayca üç diziye genişletilebilir. Bu şekilde, farklı diziler için hiçbir zaman kodu çoğaltmanız gerekmez. Yukarıdaki cevaplardan bazıları benim için aşırı karmaşık görünüyor.

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 tarafından verilen cevabın genelleştirilmesi. Temel olarak aynıdır, sadece baytlar için değil, sadece herhangi bir dizi türü için kullanılabilir:

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
TEHLİKE! Bu yöntemler, bir bayttan daha uzun öğelere (bayt dizileri dışında hemen hemen her şey) sahip herhangi bir dizi türüyle özellik çalışmaz. Buffer.BlockCopy (), dizi öğesi sayısıyla değil, bayt miktarlarıyla çalışır. Bir bayt dizisiyle kolayca kullanılabilmesinin nedeni, dizinin her öğesinin tek bir bayt olmasıdır, bu nedenle dizinin fiziksel uzunluğu, öğe sayısına eşittir. John'un byte [] yöntemlerini genel yöntemlere dönüştürmek için, tüm ofsetleri ve uzunlukları tek bir dizi öğesinin bayt uzunluğuna göre çoğaltmanız gerekir - aksi takdirde tüm verileri kopyalamazsınız.
Daniel Scott

2
Normalde bu işi yapmak için tek bir öğenin boyutunu kullanarak sizeof(...)kopyalar ve kopyalamak istediğiniz öğe sayısıyla çarparsınız, ancak sizeof genel bir türle kullanılamaz. Bazı türler için - kullanmak mümkündür Marshal.SizeOf(typeof(T)), ancak belirli türlerde (örneğin dizeler) çalışma zamanı hataları alırsınız. CLR tiplerinin iç çalışması hakkında daha kapsamlı bilgiye sahip olan biri, burada tüm olası tuzaklara işaret edebilecektir. Genel bir dizi birleştirme yöntemi [BlockCopy kullanarak] yazmanın önemsiz olmadığını söylemek yeterlidir.
Daniel Scott

2
Ve son olarak - bunun yerine Array.Copy kullanarak neredeyse tam olarak yukarıda gösterildiği gibi (biraz daha düşük performansla) genel bir dizi birleştirme yöntemi yazabilirsiniz. Tüm Buffer.BlockCopy çağrılarını Array.Copy çağrılarıyla değiştirin.
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();

Katkınız için teşekkürler. On yıldan uzun bir süre önce buna çok sayıda yüksek puanlı cevap olduğu için, yaklaşımınızı neyin farklı kıldığına dair bir açıklama sunmak yararlı olacaktır. Birisi bunu neden kabul edilen cevap yerine kullanmalı?
Jeremy Caney

Anlamak için net bir kod olduğundan, genişletilmiş yöntemleri kullanmayı seviyorum. Bu Kod, başlangıç ​​dizini ve sayım ve birleşik olan iki dizi seçer. Ayrıca bu yöntem genişletildi. Bu, her zaman için hazır olan tüm dizi türleri içindir
Mehmet ÜNLÜ

Bu bana mantıklı geliyor! Sorunuzu bu bilgileri içerecek şekilde düzenlemenin bir sakıncası var mı? Gelecekte okuyanlar için bu ön plana sahip olmanın değerli olacağını düşünüyorum, böylece yaklaşımınızı mevcut cevaplardan hızlı bir şekilde ayırt edebilirler. Teşekkür ederim!
Jeremy Caney

-1

Bayt Dizileri listesini geçmek için ihtiyacınız olan tek şey bu işlev size Bayt Dizisi (Birleştirilmiş) döndürür. Bu bence en iyi çözüm :).

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 doğru cevaptır, ancak bir nedenden dolayı kilitli bir şey en fazla oyu alıyor. Bu yanıtı beğendiyseniz, belki de bu daha genel çözümü daha da çok istersiniz:

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

hangi gibi şeyler yapmanıza izin verir:

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

5
Soru özellikle en verimli çözümü ister . Numaralandırılabilir.
Jon Skeet
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.