Mam 3 bajty tablic w języku C #, które muszę połączyć w jeden. Jaka byłaby najbardziej wydajna metoda wykonania tego zadania?
Mam 3 bajty tablic w języku C #, które muszę połączyć w jeden. Jaka byłaby najbardziej wydajna metoda wykonania tego zadania?
Odpowiedzi:
W przypadku typów pierwotnych (w tym bajtów) użyj System.Buffer.BlockCopyzamiast System.Array.Copy. To jest szybsze.
Ustawiłem każdą z sugerowanych metod w pętli wykonanej 1 milion razy, stosując 3 tablice po 10 bajtów każda. Oto wyniki:
System.Array.Copy - 0,2187556 sekundSystem.Buffer.BlockCopy - 0,1406286 sekundZwiększyłem rozmiar każdej tablicy do 100 elementów i ponownie uruchomiłem test:
System.Array.Copy - 0,2812554 sekundSystem.Buffer.BlockCopy - 0,2500048 sekundZwiększyłem rozmiar każdej tablicy do 1000 elementów i ponownie uruchomiłem test:
System.Array.Copy - 1.0781457 sekundSystem.Buffer.BlockCopy - 1.0156445 sekundNa koniec zwiększyłem rozmiar każdej tablicy do 1 miliona elementów i ponownie uruchomiłem test, wykonując każdą pętlę tylko 4000 razy:
System.Array.Copy - 13,4533833 sekundSystem.Buffer.BlockCopy - 13.1096267 sekundWięc jeśli potrzebujesz nowej tablicy bajtów, użyj
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);
Ale jeśli możesz użyć IEnumerable<byte>, ZDECYDOWANIE wolisz metodę Concat <> LINQ. Jest tylko nieco wolniejszy niż operator plonu C #, ale jest bardziej zwięzły i bardziej elegancki.
IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);
Jeśli masz dowolną liczbę tablic i korzystasz z .NET 3.5, możesz uczynić to System.Buffer.BlockCopyrozwiązanie bardziej ogólnym:
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;
}
* Uwaga: Powyższy blok wymaga dodania następującej przestrzeni nazw u góry, aby działał.
using System.Linq;
Do punktu Jona Skeeta dotyczącego iteracji kolejnych struktur danych (tablica bajtów vs. IEnumerable <byte>) ponownie uruchomiłem ostatni test synchronizacji (1 milion elementów, 4000 iteracji), dodając pętlę, która iteruje pełną tablicę z każdym przechodzić:
System.Array.Copy - 78,20550510 sekundSystem.Buffer.BlockCopy - 77,89261900 sekundChodzi o to, że BARDZO ważne jest zrozumienie wydajności zarówno tworzenia, jak i wykorzystania wynikowej struktury danych. Skupienie się na wydajności kreacji może przeoczyć nieefektywność związaną z użyciem. Kudos, Jon.
Wydaje mi się, że wiele odpowiedzi ignoruje podane wymagania:
Te dwa razem wykluczają sekwencję bajtów LINQ - wszystko, yieldco może uniemożliwić uzyskanie ostatecznego rozmiaru bez iteracji przez całą sekwencję.
Jeśli to nie są prawdziwe wymagania, LINQ może być idealnie dobrym rozwiązaniem (lub IList<T>implementacją). Zakładam jednak, że Superdumbell wie, czego chce.
(EDYCJA: Właśnie pomyślałem. Jest duża różnica semantyczna między tworzeniem kopii tablic a leniwym ich czytaniem. Zastanów się, co się stanie, jeśli zmienisz dane w jednej z tablic „źródłowych” po wywołaniu Combine(lub cokolwiek innego) ), ale przed użyciem wyniku - przy leniwej ocenie, zmiana będzie widoczna. W przypadku natychmiastowej kopii nie będzie. Różne sytuacje będą wymagały innego zachowania - po prostu coś, o czym należy pamiętać.)
Oto moje proponowane metody - które są bardzo podobne do tych zawartych w niektórych innych odpowiedziach, na pewno :)
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;
}
Oczywiście wersja „params” wymaga najpierw utworzenia tablicy tablic bajtów, co wprowadza dodatkową nieefektywność.
Wziąłem przykład Matta o LINQ o krok dalej w kwestii czystości kodu:
byte[] rv = a1.Concat(a2).Concat(a3).ToArray();
W moim przypadku tablice są małe, więc nie martwię się o wydajność.
Jeśli potrzebujesz nowej tablicy bajtów, skorzystaj z następujących opcji:
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;
}
Alternatywnie, jeśli potrzebujesz tylko jednego IEnumerable, rozważ użycie operatora wydajności 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;
}
Właściwie napotkałem pewne problemy z używaniem Concat ... (z tablicami na 10 milionach, faktycznie się zawiesił).
Odkryłem, że poniższe są proste, łatwe i działają wystarczająco dobrze, nie powodując awarii, i działa na KAŻDEJ liczbie tablic (nie tylko trzech) (używa LINQ):
public static byte[] ConcatByteArrays(params byte[][] arrays)
{
return arrays.SelectMany(x => x).ToArray();
}
Klasa memorystream wykonuje tę pracę dla mnie całkiem nieźle. Nie mogłem uruchomić klasy bufora tak szybko, jak memorystream.
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), ale - nie będąc ekspertem w wewnętrznych CLR - nie mogłem powiedzieć, czy możesz uzyskać wyjątki również dla niektórych struktur (np. jeśli zawierają pola typu odniesienia)
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();
}
}
Może używać rodzajów ogólnych do łączenia tablic. Poniższy kod można łatwo rozszerzyć do trzech tablic. W ten sposób nigdy nie trzeba duplikować kodu dla różnych typów tablic. Niektóre z powyższych odpowiedzi wydają mi się zbyt skomplikowane.
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;
}
Oto uogólnienie odpowiedzi udzielonej przez @Jona Skeeta. Jest w zasadzie taki sam, tylko można go używać do dowolnego typu tablicy, nie tylko bajtów:
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(...)i pomnożyć go przez liczbę elementów, które chcesz skopiować, ale sizeof nie można używać z typem ogólnym. Możliwe jest - w przypadku niektórych typów - użycie Marshal.SizeOf(typeof(T)), ale w przypadku niektórych typów (np. Łańcuchów) pojawią się błędy w czasie wykonywania. Ktoś z bardziej dogłębną znajomością wewnętrznego działania typów CLR będzie w stanie wskazać tutaj wszystkie możliwe pułapki. Wystarczy powiedzieć, że napisanie ogólnej metody konkatenacji tablic [przy użyciu BlockCopy] nie jest trywialne.
/// <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();
Wystarczy przekazać listę tablic bajtów, a ta funkcja zwróci tablicę bajtów (scalone). Myślę, że to najlepsze rozwiązanie :).
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 jest właściwą odpowiedzią, ale z jakiegoś powodu najwięcej głosów zdobywa się na liście kontrolnej. Jeśli podoba ci się ta odpowiedź, być może chciałbyś jeszcze bardziej ogólne rozwiązanie:
IEnumerable<byte> Combine(params byte[][] arrays)
{
foreach (byte[] a in arrays)
foreach (byte b in a)
yield return b;
}
co pozwoli ci robić takie rzeczy jak:
byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();