2012-08-23 2 views
9

데이터베이스에 BLOB로 저장하려는 메모리에 큰 개체가 있습니다. 데이터베이스 서버가 대개 로컬이 아니기 때문에 저장하기 전에 압축하려고합니다.오브젝트를 직렬화하는 방법 + 타사 라이브러리없이 압축 한 다음 압축 해제 + 비 직렬화하는 방법은 무엇입니까?

이것은 내가 지금 무엇을 가지고 :

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

    return memoryStream.ToArray(); 
    } 
} 

내가 총 사령관과 같은 바이트를 압축 그러나이 50 % 이상으로 항상 크기를 줄인다. 위 코드를 사용하면 58MB에서 48MB로 압축되고 15MB보다 작은 파일은 더 커집니다.

제 3 자 우편 라이브러리를 사용해야합니까, 아니면 .NET 3.5에서 더 좋은 방법이 있습니까? 다른 문제는 무엇입니까?

편집 :

그냥 위의 코드에서 버그를 발견. 앤젤로가 고마워.

GZipStream 압축은 여전히 ​​크지 않습니다. 나는 TC 48 % 압축에 비해 gZipStream에 의해 평균 35 % 압축을 얻습니다.

은 내가 이전 버전 : 함께 얻고 있었다 바이트의 어떤 종류의 아무 생각이

EDIT2 : 나는 47 % 20 %에서 압축을 향상시키는 방법을 발견했다

. 하나 대신 2 개의 메모리 스트림을 사용해야했습니다! 아무도 이유를 설명 할 수 있을까요?

다음은 2 개의 메모리 스트림이 포함 된 코드로 더 나은 압축을 제공합니다 !!! 사용

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

나는 http : //www.icsharpcode를 사용합니다.그물/opensource/sharpziplib/Download.aspx 큰 성공. – Asken

답변

2

GZipStream을 .NET 3.5에서 압축 수준을 설정할 수 없습니다. 이 매개 변수는 .NET 4.5에서 처음 소개되었지만 더 나은 결과를 제공하는지 아니면 업그레이드가 적합한 지 알 수 없습니다. AFAIK 특허로 인해 내장 알고리즘이 매우 적합하지 않습니다. 그래서 3.5에서는 SDK7zip 또는 SharpZipLib이 제공하는 타사 라이브러리를 사용하는 것이 한 가지 방법입니다. 아마도 데이터의 더 나은 압축을 얻으려면 다른 libs를 약간 실험해야합니다.

+1

gzip과 deflate의 압축 알고리즘은 일반적으로 특허에 의해 방해받지 않습니다. 오래된 네이티브 .net 버전은 특허에 기인 한 것이 아니기 때문에 적게 최적화 되었기 때문에 덜 최적화되었습니다. –

1

기본 CompressionLevel 적어도 http://msdn.microsoft.com/en-us/library/as1ff51s에 따라, Optimal, 그래서 제 3 자 lib에 더 잘 될 것이라고 나에게 보인다 .. "열심히 노력"할 수 GZipStream을 알 수있는 방법이 없습니다.

개인적으로 GZipStream은 압축 측면에서 '우수'하다고 생각하지 않았습니다. 아마도 메모리 공간을 최소화하거나 속도를 최대화하려는 노력 일 것입니다. 그러나, WindowsXP/WindowsVista/Windows7이 네이티브 익스플로러에서 ZIP 파일을 처리하는 방식을 보는 것 - 글쎄 .. 빨리도 좋은 압축도 없다고 말할 수는 없다. Win7의 Explorer가 실제로 GZipStream을 사용한다면 놀라지 않을 것이다. 모두가 그것을 구현하고 프레임 워크에 넣었을 것입니다. 그래서 아마도 여러 곳에서 사용하고 있습니다. (즉, HTTP GZIP handling에 사용 된 것 같습니다.) 그래서 나는 그것으로부터 멀리 떨어져있을 것입니다. 효율적인 처리가 필요했습니다. 수년 전 .Net이 초창기에 좋은 지퍼 핸들러를 구입 한이 주제에 대해 심각한 연구를 한 적이 없었습니다.

편집 :

더 심판 :
http://dotnetzip.codeplex.com/workitem/7159 - 그러나으로 표시 "폐쇄/해결"2009 년 ... 어쩌면 당신이 그 코드에 뭔가 흥미로운 것을 발견 할 것이다?

ㅎ, 인터넷 검색의 몇 분 후에는 7zip과 일부 C#을 바인딩 노출 것 같습니다 : http://www.splinter.com.au/compressing-using-the-7zip-lzma-algorithm-in/

편집 # 2 :

단지 참고 .net4.5 아부 : https://stackoverflow.com/a/9808000/717732

11

너에 의 버그가 있으며 설명이 너무 길어서 실제 질문에 대답하지 않더라도 대답으로 제시합니다.

당신은 그렇지 않으면 당신은 당신이 역 직렬화 할 수 없습니다 압축 된 데이터를 생성하는 GZipStream을 닫은 후 memoryStream.ToArray()를 호출해야합니다.

고정 코드는 다음과 같습니다

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 청크의 기본 버퍼에 기록 또한 스트림의 마지막에 바닥 글을 추가하고이 단지 당신이 스트림을 닫습니다 순간에 실행된다.

당신은 쉽게 다음 코드 샘플을 실행하여이 증명할 수 :

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

자기 자신도 버그를 발견했습니다. 답변을 게시 해 주셔서 감사합니다. 방금 게시 게시 : – Marek

0

원래 질문은 .NET 3.5과 관련되었다. 3 년 후 .NET 4.5가 사용 될 가능성이 훨씬 높습니다. 내 대답은 4.5에만 유효합니다. 앞서 언급 한 다른 것들과 마찬가지로, 압축 알고리즘은 .NET 4.5에서 좋은 향상을 얻었습니다.

오늘, 일부 공간을 절약하기 위해 데이터 세트를 압축하려고했습니다. .NET4.5에 대한 원래의 질문과 비슷합니다. 그리고 몇 년 전 DoubleStream에서 동일한 트릭을 사용하는 것을 기억하기 때문에 방금 시도했습니다. 내 데이터 집합은 많은 해시 집합과 문자열/int/DateTime 속성이있는 사용자 지정 개체 목록이있는 컨테이너 개체입니다. 데이터 세트에는 약 45,000 개의 오브젝트가 들어 있으며 압축없이 직렬화하면 3500 kB 2 진 파일이 작성됩니다.

질문에 설명 된대로 단일 또는 이중 MemoryStream 또는 4.5에서 zlib를 사용하는 DeflateStream을 사용하여 GZipStream을 사용하면 항상 818KB의 파일이 생성됩니다. 그래서 더블 바이트 MemoryStream을 사용한 트릭보다 .NET 4.5에서는 쓸모가 없습니다.

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

을 그래서, 예를 들어, 다른은 TStream을 사용할 수 있습니다 :

은 결국, 내 일반적인 코드는 다음과 같다

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