2012-08-23 8 views
9

Mam duży obiekt w pamięci, który chcę zapisać jako blob w bazie danych. Chcę skompresować go przed zapisaniem, ponieważ serwer bazy danych zwykle nie jest lokalny.Jak serializować obiekt + skompresować go, a następnie rozpakować + deserializować bez biblioteki innej firmy?

To jest to, co mam w tej chwili:

using (var memoryStream = new MemoryStream()) 
{ 
    using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress)) 
    { 
    BinaryFormatter binaryFormatter = new BinaryFormatter(); 
    binaryFormatter.Serialize(gZipStream, obj); 

    return memoryStream.ToArray(); 
    } 
} 

Jednak kiedy zip same bajty z Total Commander to zmniejszają wielkość zawsze o 50% co najmniej. Z powyższym kodem kompresuje 58MB do 48MB, a wszystko poniżej 15MB staje się jeszcze większe.

Czy powinienem używać zewnętrznej biblioteki zip lub czy jest lepszy sposób na zrobienie tego w .NET 3.5. Jakieś inne alternatywy dla mojego problemu?

EDIT:

Właśnie znalazłeś błąd w kodzie powyżej. Angelo dzięki za poprawkę.

Kompresja GZipStream nadal nie jest świetna. Mam średnią kompresję 35% według gZipStream w porównaniu do kompresji 48% TC.

nie mam pojęcia jakie bajtów Byłem wysiadając z poprzedniej wersji :)

EDIT2:

znalazłem sposobu poprawy kompresji od 20% do 47%. Musiałem użyć dwóch strumieni pamięci zamiast jednego! Czy ktoś może wyjaśnić, dlaczego tak się dzieje?

Oto kod z 2 strumieniami pamięci, który ma znacznie lepszą kompresję !!!

using (MemoryStream msCompressed = new MemoryStream()) 
using (GZipStream gZipStream = new GZipStream(msCompressed, CompressionMode.Compress)) 
using (MemoryStream msDecompressed = new MemoryStream()) 
{ 
    new BinaryFormatter().Serialize(msDecompressed, obj); 
    byte[] byteArray = msDecompressed.ToArray(); 

    gZipStream.Write(byteArray, 0, byteArray.Length); 
    gZipStream.Close(); 
    return msCompressed.ToArray(); 
} 
+1

Używam http: //www.icsharpcode.net/opensource/sharpziplib/Download.aspx z dużym sukcesem. – Asken

Odpowiedz

2

GZipStream z .NET 3.5 nie pozwala ci ustawić poziomu kompresji. Ten parametr został wprowadzony w .NET 4.5, ale nie wiem, czy da ci lepszy wynik, czy aktualizacja będzie dla ciebie odpowiednia. Wbudowany algorytm nie jest zbyt optymalny, ze względu na patenty AFAIK. Więc w 3.5 jest tylko jeden sposób, aby uzyskać lepszą kompresję, należy użyć biblioteki innej firmy, takiej jak SDK dostarczonej przez 7zip lub SharpZipLib. Prawdopodobnie powinieneś trochę poeksperymentować z różnymi bibliotekami, aby uzyskać lepszą kompresję danych dla swoich.

+1

Algorytmy kompresji w gzip i deflate są obecnie nieobciążone patentami. Stare rodzime wersje .net są mniej optymalne, ponieważ zostały zoptymalizowane mniej, nie z powodu patentów. –

1

Domyślna CompressionLevel stosowany jest Optimal, przynajmniej według http://msdn.microsoft.com/en-us/library/as1ff51s, więc nie ma sposobu, aby powiedzieć GZipStream do „try harder” .. Wydaje mi się, że dla 3rd party lib byłoby lepiej.

Osobiście nigdy nie uważałem, że GZipStream jest "dobry" pod względem kompresji - prawdopodobnie starają się zminimalizować ślad pamięci lub maksymalizować prędkość. Jednak widząc, jak WindowsXP/WindowsVista/Windows7 radzi sobie z plikami ZIP natywnie w Eksploratorze - dobrze ... Nie mogę powiedzieć ani, że jest szybki, ani też nie ma dobrej kompresji .. Nie byłbym zaskoczony, gdyby Explorer w Win7 faktycznie korzystał z GZipStream - w sumie wdrożyli go i wprowadzili w ramy, więc prawdopodobnie używają go w wielu miejscach (tzn. wydaje się, że są używane w HTTP GZIP handling), więc trzymałem się z dala od niego, potrzebowałem wydajnego przetwarzania. Nigdy nie poczyniłem żadnych poważnych badań w tym temacie, ponieważ moja firma kupiła ładnego zip-handler wiele lat temu, kiedy sieć .Net była w początkach.

EDIT:

Więcej bibl:
http://dotnetzip.codeplex.com/workitem/7159 - ale oznaczone jako "zamknięte/rozwiązany" w 2009 r .. może znajdziesz coś ciekawego w tym kodzie?

heh, po kilku minutach googlowania, wydaje się, że 7Zip eksponuje kilka wiązań C#: http://www.splinter.com.au/compressing-using-the-7zip-lzma-algorithm-in/

Edit # 2:

tylko FYI Abou .net4.5: https://stackoverflow.com/a/9808000/717732

11

Masz mieć błąd w kodzie i wyjaśnienie jest zbyt długie na komentarz, więc przedstawiam go jako odpowiedź, mimo że nie odpowiada na twoje prawdziwe pytanie.

Trzeba zadzwonić memoryStream.ToArray() tylko po zamykania GZipStream inaczej podczas tworzenia skompresowanych danych, które nie będą w stanie deserializowania.

kod Fixed następująco:

using (var memoryStream = new System.IO.MemoryStream()) 
{ 
    using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress)) 
    { 
    BinaryFormatter binaryFormatter = new BinaryFormatter(); 
    binaryFormatter.Serialize(gZipStream, obj); 
    } 
    return memoryStream.ToArray(); 
} 

GZipStream pisze do podstawowej buforu w kawałki i również dołącza stopkę na końcu strumienia i jest wykonywana tylko w chwili zamknięcia strumienia.

Można łatwo udowodnić, uruchamiając Poniższy przykładowy kod:

byte[] compressed; 
int[] integers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

var mem1 = new MemoryStream(); 
using (var compressor = new GZipStream(mem1, CompressionMode.Compress)) 
{ 
    new BinaryFormatter().Serialize(compressor, integers); 
    compressed = mem1.ToArray(); 
} 

var mem2 = new MemoryStream(compressed); 
using (var decompressor = new GZipStream(mem2, CompressionMode.Decompress)) 
{ 
    // The next line will throw SerializationException 
    integers = (int[])new BinaryFormatter().Deserialize(decompressor); 
} 
+0

Znaleźliśmy też błąd. Dziękujemy za przesłanie odpowiedzi! Właśnie publikowałam edycję :) – Marek

0

Oryginalny pytanie było związane z .NET 3.5. Trzy lata później, .NET 4.5 jest znacznie bardziej prawdopodobne, moja odpowiedź jest ważna tylko dla 4.5. Jak wspomnieliśmy wcześniej, algorytm kompresji uzyskał dobre usprawnienia dzięki .NET 4.5

Dzisiaj chciałem skompresować mój zestaw danych, aby zaoszczędzić trochę miejsca. Tak podobne, jak oryginalne pytanie, ale dla .NET4.5. A ponieważ pamiętam, że używałem tej samej sztuczki z podwójnym MemoryStream wiele lat temu, po prostu spróbowałem. Mój zestaw danych jest obiektem kontenerowym z wieloma hashsetami i listami niestandardowych obiektów o właściwościach string/int/DateTime. Zestaw danych zawiera około 45 000 obiektów, a gdy serializowany jest bez kompresji, tworzy plik binarny o wielkości 3500 kB.

Teraz z GZipStream, z pojedynczym lub podwójnym strumieniem danych MemoryStream, jak opisano w pytaniu, lub z DeflateStream (który używa zlib w 4.5), zawsze otrzymuję plik o wielkości 818 kB. Więc po prostu chcę nalegać tutaj, niż sztuczka z podwójnym MemoryStream się bezużyteczne z .NET 4.5.

Ostatecznie mój kod generyczny jest następująca:

 public static byte[] SerializeAndCompress<T, TStream>(T objectToWrite, Func<TStream> createStream, Func<TStream, byte[]> returnMethod, Action catchAction) 
     where T : class 
     where TStream : Stream 
    { 
     if (objectToWrite == null || createStream == null) 
     { 
      return null; 
     } 
     byte[] result = null; 
     try 
     { 
      using (var outputStream = createStream()) 
      { 
       using (var compressionStream = new GZipStream(outputStream, CompressionMode.Compress)) 
       { 
        var formatter = new BinaryFormatter(); 
        formatter.Serialize(compressionStream, objectToWrite); 
       } 
       if (returnMethod != null) 
        result = returnMethod(outputStream); 
      } 
     } 
     catch (Exception ex) 
     { 
      Trace.TraceError(Exceptions.ExceptionFormat.Serialize(ex)); 
      catchAction?.Invoke(); 
     } 
     return result; 
    } 

tak, że mogę używać różnych TStream, np

public static void SerializeAndCompress<T>(T objectToWrite, string filePath) where T : class 
    { 
     //var buffer = SerializeAndCompress(collection); 
     //File.WriteAllBytes(filePath, buffer); 
     SerializeAndCompress(objectToWrite,() => new FileStream(filePath, FileMode.Create), null,() => 
     { 
      if (File.Exists(filePath)) 
       File.Delete(filePath); 
     }); 
    } 

    public static byte[] SerializeAndCompress<T>(T collection) where T : class 
    { 
     return SerializeAndCompress(collection,() => new MemoryStream(), st => st.ToArray(), null); 
    } 
Powiązane problemy