Cara terbaik untuk menggabungkan dua atau lebih array byte di C #

239

Saya memiliki 3 byte array di C # yang harus saya gabungkan menjadi satu. Apa metode yang paling efisien untuk menyelesaikan tugas ini?

Superdumbell
sumber
3
Apa spesifik kebutuhan Anda? Apakah Anda mengambil penyatuan array atau Anda mempertahankan beberapa instance dengan nilai yang sama? Apakah Anda ingin item diurutkan, atau Anda ingin mempertahankan pemesanan di array awal? Apakah Anda mencari efisiensi dalam kecepatan atau dalam baris kode?
jason
Menyukainya, "terbaik" tergantung pada apa kebutuhan Anda.
Ady
7
Jika Anda dapat menggunakan LINQ, maka Anda hanya dapat menggunakan Concatmetode:IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne
1
Cobalah untuk lebih jelas dalam pertanyaan Anda. Pertanyaan samar ini telah menyebabkan banyak kebingungan di antara orang-orang yang cukup baik untuk meluangkan waktu untuk menjawab Anda.
Drew Noakes

Jawaban:

327

Untuk tipe primitif (termasuk byte), gunakan System.Buffer.BlockCopysebagai ganti System.Array.Copy. Lebih cepat.

Saya menghitung setiap metode yang disarankan dalam satu loop yang dieksekusi 1 juta kali menggunakan 3 array masing-masing 10 byte. Inilah hasilnya:

  1. Array Byte Baru menggunakan System.Array.Copy - 0,2187556 detik
  2. Array Byte Baru menggunakan System.Buffer.BlockCopy - 0,1406286 detik
  3. IEnumerable <byte> menggunakan operator hasil C # - 0,0781270 detik
  4. IEnumerable <byte> menggunakan LINQ's Concat <> - 0,0781270 detik

Saya meningkatkan ukuran setiap array menjadi 100 elemen dan menjalankan kembali tes:

  1. Array Byte Baru menggunakan System.Array.Copy - 0,2812554 detik
  2. Array Byte Baru menggunakan System.Buffer.BlockCopy - 0,2500048 detik
  3. IEnumerable <byte> menggunakan operator hasil C # - 0,0625012 detik
  4. IEnumerable <byte> menggunakan LINQ's Concat <> - 0,0781265 detik

Saya meningkatkan ukuran setiap array menjadi 1000 elemen dan menjalankan kembali tes:

  1. Array Byte Baru menggunakan System.Array.Copy - 1,0781457 detik
  2. Array Byte Baru menggunakan System.Buffer.BlockCopy - 1,0156445 detik
  3. IEnumerable <byte> menggunakan operator hasil C # - 0,0625012 detik
  4. IEnumerable <byte> menggunakan LINQ's Concat <> - 0,0781265 detik

Akhirnya, saya meningkatkan ukuran setiap array menjadi 1 juta elemen dan menjalankan ulang pengujian, mengeksekusi setiap loop hanya 4000 kali:

  1. Array Byte Baru menggunakan System.Array.Copy - 13,4533833 detik
  2. Array Byte Baru menggunakan System.Buffer.BlockCopy - 13,1096267 detik
  3. IEnumerable <byte> menggunakan operator hasil C # - 0 detik
  4. IEnumerable <byte> menggunakan LINQ's Concat <> - 0 detik

Jadi, jika Anda membutuhkan array byte baru, gunakan

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

Tapi, jika Anda bisa menggunakan metode IEnumerable<byte>, PASTI lebih suka metode LINQ Concat <>. Ini hanya sedikit lebih lambat dari operator hasil C #, tetapi lebih ringkas dan lebih elegan.

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

Jika Anda memiliki jumlah array yang sewenang-wenang dan menggunakan .NET 3.5, Anda dapat membuat System.Buffer.BlockCopysolusinya lebih umum seperti ini:

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

* Catatan: Blok di atas mengharuskan Anda menambahkan namespace berikut di bagian atas agar bisa berfungsi.

using System.Linq;

Untuk poin Jon Skeet mengenai iterasi dari struktur data berikutnya (byte array vs IEnumerable <byte>), saya menjalankan kembali tes waktu terakhir (1 juta elemen, 4000 iterasi), menambahkan loop yang mengulangi array penuh dengan masing-masing lulus:

  1. Array Byte Baru menggunakan System.Array.Copy - 78,20550510 detik
  2. Array Byte Baru menggunakan System.Buffer.BlockCopy - 77,89261900 detik
  3. IEnumerable <byte> menggunakan operator hasil C # - 551.7150161 detik
  4. IEnumerable <byte> menggunakan LINQ's Concat <> - 448.1804799 detik

Intinya adalah, SANGAT penting untuk memahami efisiensi penciptaan dan penggunaan struktur data yang dihasilkan. Dengan hanya berfokus pada efisiensi penciptaan dapat mengabaikan inefisiensi yang terkait dengan penggunaan. Salam, Jon.

Matt Davis
sumber
61
Tetapi apakah Anda benar-benar mengubahnya menjadi sebuah array di akhir, seperti pertanyaannya? Jika tidak, tentu saja lebih cepat - tetapi tidak memenuhi persyaratan.
Jon Skeet
18
Re: Matt Davis - Tidak masalah jika "persyaratan" Anda perlu mengubah IEnumerable menjadi sebuah array - semua yang Anda butuhkan adalah bahwa hasilnya benar-benar digunakan dalam beberapa fasion . Alasan mengapa tes kinerja Anda pada IEnumerable sangat rendah adalah karena Anda sebenarnya tidak melakukan apa-apa ! LINQ tidak melakukan pekerjaannya sampai Anda mencoba menggunakan hasilnya. Untuk alasan ini, saya menemukan jawaban Anda secara objektif salah dan dapat membuat orang lain menggunakan LINQ padahal sebenarnya mereka tidak perlu jika mereka peduli dengan kinerja.
csauve
12
Saya membaca seluruh jawaban termasuk pembaruan Anda, komentar saya berdiri. Saya tahu saya terlambat datang ke pesta, tetapi jawabannya terlalu menyesatkan dan setengahnya benar - benar salah .
csauve
14
Mengapa jawaban yang berisi informasi yang salah dan menyesatkan merupakan jawaban yang dipilih, dan diedit pada dasarnya benar - benar membatalkan pernyataan aslinya setelah seseorang (Jon Skeet) menunjukkan bahwa itu bahkan tidak menjawab pertanyaan OP?
MrCC
3
Jawaban yang menyesatkan. Bahkan edisi ini tidak menjawab pertanyaan.
Serge Profafilecebook
154

Banyak jawaban bagi saya tampaknya mengabaikan persyaratan yang dinyatakan:

  • Hasilnya harus berupa array byte
  • Itu harus seefisien mungkin

Keduanya bersama-sama mengesampingkan urutan byte LINQ - apa pun dengan yieldakan membuat tidak mungkin untuk mendapatkan ukuran akhir tanpa iterasi melalui seluruh urutan.

Jika itu bukan persyaratan nyata tentunya, LINQ bisa menjadi solusi yang sangat baik (atau IList<T>implementasinya). Namun, saya akan berasumsi bahwa Superdumbell tahu apa yang dia inginkan.

(EDIT: Saya baru saja berpikir lain. Ada perbedaan besar antara membuat salinan array dan membacanya dengan malas. Pertimbangkan apa yang terjadi jika Anda mengubah data di salah satu array "sumber" setelah memanggil Combine(atau apa pun) ) tetapi sebelum menggunakan hasilnya - dengan evaluasi malas, perubahan itu akan terlihat. Dengan salinan langsung, itu tidak akan. Situasi yang berbeda akan memerlukan perilaku yang berbeda - hanya sesuatu yang harus diperhatikan.)

Berikut adalah metode yang saya usulkan - yang sangat mirip dengan yang terkandung dalam beberapa jawaban lain, tentu saja :)

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

Tentu saja versi "params" membutuhkan pembuatan array array byte terlebih dahulu, yang memperkenalkan inefisiensi tambahan.

Jon Skeet
sumber
Jon, aku mengerti persis apa yang kamu katakan. Satu-satunya poin saya adalah bahwa kadang-kadang pertanyaan diajukan dengan implementasi tertentu sudah ada dalam pikiran tanpa menyadari bahwa ada solusi lain. Cukup memberikan jawaban tanpa menawarkan alternatif sepertinya merugikan saya. Pikiran?
Matt Davis
1
@Matt: Ya, menawarkan alternatif itu baik - tetapi perlu dijelaskan bahwa itu adalah alternatif daripada meneruskannya sebagai jawaban atas pertanyaan yang diajukan. (Saya tidak mengatakan bahwa Anda melakukan itu - jawaban Anda sangat bagus.)
Jon Skeet
4
(Meskipun saya pikir patokan kinerja Anda harus menunjukkan waktu yang dibutuhkan untuk pergi melalui semua hasil dalam setiap kasus, juga untuk menghindari memberikan evaluasi malas keuntungan yang tidak adil.)
Jon Skeet
1
Bahkan tanpa memenuhi persyaratan "hasil harus berupa array", cukup memenuhi persyaratan "hasil harus digunakan dalam beberapa fasion" akan membuat LINQ menjadi tidak optimal. Saya pikir persyaratan untuk dapat menggunakan hasilnya harus implisit!
csauve
2
@andleer: Selain yang lain, Buffer.BlockCopy hanya berfungsi dengan tipe primitif.
Jon Skeet
44

Saya mengambil contoh Matt's LINQ satu langkah lebih jauh untuk kebersihan kode:

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

Dalam kasus saya, arraynya kecil, jadi saya tidak peduli dengan kinerja.

Nate Barbettini
sumber
3
Solusi singkat dan sederhana, tes kinerja akan luar biasa!
Sebastian
3
Ini jelas, dapat dibaca, tidak memerlukan perpustakaan / pembantu eksternal, dan, dalam hal waktu pengembangan, cukup efisien. Hebat ketika kinerja run-time tidak kritis.
binki
28

Jika Anda hanya perlu array byte baru, gunakan yang berikut ini:

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

Atau, jika Anda hanya perlu satu IEnumerable, pertimbangkan untuk menggunakan operator hasil C # 2.0:

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;
}
FryGuy
sumber
Saya telah melakukan sesuatu yang mirip dengan opsi ke-2 Anda untuk menggabungkan aliran besar, bekerja seperti pesona. :)
Greg D
2
Pilihan kedua sangat bagus. +1.
R. Martinho Fernandes
11

Saya benar-benar mengalami beberapa masalah dengan menggunakan Concat ... (dengan array dalam 10-juta, itu benar-benar macet).

Saya menemukan berikut ini menjadi sederhana, mudah dan bekerja dengan cukup baik tanpa menabrak saya, dan berfungsi untuk jumlah array APAPUN (bukan hanya tiga) (Menggunakan LINQ):

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

Kelas memorystream melakukan pekerjaan ini dengan cukup baik untuk saya. Saya tidak bisa menjalankan kelas buffer secepat memorystream.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}
Andrew
sumber
3
Sebagai qwe menyatakan, saya melakukan tes dalam satu lingkaran 10.000.000 kali, dan MemoryStream keluar 290% lebih lambat dari Buffer.BlockCopy
esac
Dalam beberapa kasus, Anda mungkin melakukan iterasi lebih dari jumlah array tanpa pengetahuan sebelumnya tentang panjang array individu. Ini berfungsi dengan baik dalam skenario ini. BlockCopy bergantung pada memiliki array tujuan yang telah ditentukan sebelumnya
Sentinel
Seperti yang dikatakan @Sentinel, jawaban ini sangat cocok untuk saya karena saya tidak memiliki pengetahuan tentang ukuran hal-hal yang harus saya tulis dan memungkinkan saya melakukan hal-hal dengan sangat bersih. Itu juga bermain bagus dengan [ReadOnly] Span .NET Core 3! <byte>!
Air
Jika Anda menginisialisasi MemoryStream dengan ukuran akhir dari ukuran itu tidak akan dibuat kembali dan itu akan lebih cepat @ esac.
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;
    }
edd
sumber
Sayangnya ini tidak akan bekerja dengan semua tipe. Marshal.SizeOf () tidak akan dapat mengembalikan ukuran untuk banyak jenis (coba gunakan metode ini dengan array string dan Anda akan melihat pengecualian "Ketik 'System.String' tidak dapat dikerjakan sebagai struktur yang tidak dikelola; tidak ada ukuran yang berarti atau offset dapat dihitung ". Anda dapat mencoba membatasi parameter tipe ke tipe referensi saja (dengan menambahkan where T : struct), tetapi - tidak menjadi ahli dalam jeroan CLR - saya tidak bisa mengatakan apakah Anda mungkin mendapatkan pengecualian pada struct tertentu juga (mis. jika mengandung bidang tipe referensi)
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();
        }
    }
Peter Ertl
sumber
Jawaban Anda bisa lebih baik jika Anda telah memposting sedikit penjelasan tentang contoh kode ini.
SETELAH
1
itu menggabungkan array array byte menjadi satu array byte besar (seperti ini): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl
1

Dapat menggunakan obat generik untuk menggabungkan array. Kode berikut dapat dengan mudah diperluas ke tiga array. Dengan cara ini Anda tidak perlu menduplikasi kode untuk berbagai jenis array. Beberapa jawaban di atas tampaknya terlalu rumit bagi saya.

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;
    }
BajaPaul
sumber
0

Inilah generalisasi jawaban yang diberikan oleh @Jon Skeet. Pada dasarnya sama, hanya dapat digunakan untuk semua jenis array, tidak hanya byte:

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;
}
o_c
sumber
3
BAHAYA! Metode-metode ini tidak akan bekerja dengan tipe array apa pun dengan elemen lebih dari satu byte (hampir semuanya selain array byte). Buffer.BlockCopy () bekerja dengan jumlah byte, bukan jumlah elemen array. Alasannya dapat digunakan dengan mudah dengan array byte adalah bahwa setiap elemen array adalah byte tunggal, sehingga panjang fisik array sama dengan jumlah elemen. Untuk mengubah metode byte [] John menjadi metode umum Anda harus melipatgandakan semua offset dan panjang dengan panjang byte elemen array tunggal - jika tidak, Anda tidak akan menyalin semua data.
Daniel Scott
2
Biasanya untuk membuat ini bekerja Anda akan menghitung ukuran elemen tunggal menggunakan sizeof(...)dan mengalikannya dengan jumlah elemen yang ingin Anda salin, tetapi sizeof tidak dapat digunakan dengan tipe generik. Dimungkinkan - untuk beberapa jenis - untuk digunakan Marshal.SizeOf(typeof(T)), tetapi Anda akan mendapatkan kesalahan runtime dengan tipe tertentu (mis. String). Seseorang dengan pengetahuan yang lebih menyeluruh tentang cara kerja tipe CLR akan dapat menunjukkan semua kemungkinan jebakan di sini. Cukuplah untuk mengatakan bahwa menulis metode gabungan array generik [menggunakan BlockCopy] tidak sepele.
Daniel Scott
2
Dan akhirnya - Anda dapat menulis metode penggabungan array generik seperti ini di hampir persis cara yang ditunjukkan di atas (dengan kinerja yang sedikit lebih rendah) dengan menggunakan Array.Copy sebagai gantinya. Ganti saja semua panggilan Buffer.BlockCopy dengan panggilan 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();
Mehmet ÜNLÜ
sumber
Terima kasih atas kontribusinya. Karena sudah ada sejumlah jawaban berperingkat tinggi untuk ini dari lebih dari satu dekade yang lalu, akan berguna untuk menawarkan penjelasan tentang apa yang membedakan pendekatan Anda. Mengapa seseorang harus menggunakan ini alih-alih misalnya jawaban yang diterima?
Jeremy Caney
Saya suka menggunakan metode yang diperluas, karena ada kode yang jelas untuk dipahami. Kode ini memilih dua larik dengan indeks mulai dan menghitung dan concat. Metode ini juga diperluas. Jadi, ini untuk semua tipe array yang siap setiap saat
Mehmet ÜNLÜ
Itu masuk akal bagi saya! Apakah Anda keberatan mengedit pertanyaan Anda untuk memasukkan informasi itu? Saya pikir akan sangat berharga bagi pembaca di masa depan untuk memiliki muka, sehingga mereka dapat dengan cepat membedakan pendekatan Anda dari jawaban yang ada. Terima kasih!
Jeremy Caney
-1

Yang Anda perlukan untuk melewati daftar Byte Arrays dan fungsi ini akan mengembalikan Anda Array Bytes (Digabung). Ini adalah solusi terbaik menurut saya :).

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

Concat adalah jawaban yang tepat, tetapi karena alasan tertentu hal yang dikuasai adalah mendapatkan suara terbanyak. Jika Anda menyukai jawaban itu, mungkin Anda akan lebih menyukai solusi yang lebih umum ini:

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

yang akan membiarkan Anda melakukan hal-hal seperti:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();
Mark Maxham
sumber
5
Pertanyaannya secara khusus menanyakan solusi yang paling efisien . Enumerable.ToArray tidak akan menjadi sangat efisien, karena tidak dapat mengetahui ukuran array akhir untuk memulai - sedangkan teknik linting tangan bisa.
Jon Skeet