Ben bir birleştirmek gerekir C # 3 bayt dizilerim var. Bu görevi tamamlamak için en etkili yöntem ne olurdu?
Ben bir birleştirmek gerekir C # 3 bayt dizilerim var. Bu görevi tamamlamak için en etkili yöntem ne olurdu?
Yanıtlar:
İ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:
System.Array.Copy - 0.2187556 saniyeSystem.Buffer.BlockCopy - 0.1406286 saniyeHer dizinin boyutunu 100 öğeye çıkardım ve testi yeniden çalıştırdım:
System.Array.Copy - 0.2812554 saniyeSystem.Buffer.BlockCopy - 0.2500048 saniyeHer dizinin boyutunu 1000 öğeye çıkardım ve testi yeniden çalıştırdım:
System.Array.Copy - 1.0781457 saniyeSystem.Buffer.BlockCopy - 1.0156445 saniyeSon 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:
System.Array.Copy - 13.4533833 saniyeSystem.Buffer.BlockCopy - - 13.1096267 saniye kullanarakBu 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:
System.Array.Copy - 78.20550510 saniyeSystem.Buffer.BlockCopy - 77.89261900 saniyeMesele ş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.
Cevapların çoğu bana belirtilen gereklilikleri görmezden geliyor gibi geliyor:
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.
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.
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;
}
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();
}
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();
}
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;
}
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)
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();
}
}
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;
}
@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;
}
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.
/// <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();
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();
}
}
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();