2017-12-10 8 views
3

나는 수백 MB의 .gz 파일을 다운로드하려고하는데, C#에서 매우 긴 문자열로 바꾸려고합니다.MemoryStream의 GZipStream은 단지 수백 바이트 만 반환합니다.

using (var memstream = new MemoryStream(new WebClient().DownloadData(url))) 
using (GZipStream gs = new GZipStream(memstream, CompressionMode.Decompress)) 
using (var outmemstream = new MemoryStream()) 
{ 
    gs.CopyTo(outmemstream); 
    string t = Encoding.UTF8.GetString(outmemstream.ToArray()); 
    Console.WriteLine(t); 
} 

내 테스트 URL : https://commoncrawl.s3.amazonaws.com/crawl-data/CC-MAIN-2017-47/segments/1510934803848.60/wat/CC-MAIN-20171117170336-20171117190336-00002.warc.wat.gz

memstream이 프로그램이 초기화 라인에 약 15 초 동안 남아 283063949.의 길이를 가지고 있으며, 내 네트워크는 동안 낭패되고, 의미가 있습니다.

outmemstream는 압축 된 문서의 첫 번째 라인 인 라인 명령 만 작성 548

의 길이를 갖는다. 그들은 왜곡되지 않습니다. 나는 나머지를 얻는 방법을 모르겠다.

+0

작은 파일에서도 작동하는지 확인 했습니까? 메가 바이트 정도 되니? 압축되지 않은 원본 파일이 UTF-8로 인코딩 된 것이 확실합니까? 'outmemstream'의 내용을 파일에 써서 거기에 무엇이 있는지 보았습니까? 기억이 안 나올거야? 이 코드는 2 기가 바이트 이상의 RAM (memstream의 경우 280 MB, 압축되지 않은 데이터의 경우 2 배, 작성중인 문자열의 경우 2 배)을 초과하여 먹는 것처럼 보입니다. –

+0

@JimMischel 사실 그것은 그것보다 더 나쁩니다. 압축 된 데이터는 ASCII 텍스트로 보입니다. 비 압축 데이터의 경우 1.2GB, 문자열 표현의 경우 2 배. – Corey

답변

2

.NET GZipStream은 파일의 첫 번째 레코드 인 일반 텍스트의 첫 번째 548 바이트의 압축을 풉니 다. 7Zip은 전체 파일을 1.2GB 출력 파일로 추출하지만 레코드 구분 기호가없는 일반 텍스트 (약 130 만 라인 상당)이며 7Zip에서 파일을 테스트 할 때 1,441 바이트를보고합니다.

몇 가지 사항을 확인하고 직접 압축을 풀 수있는 압축 라이브러리를 찾을 수 없습니다.

파일에서 약 캐스팅 한 후 1,441 바이트가 일반적으로 압축 된 데이터에 추가 된 8 바이트 바닥 글 레코드의 일부인 gzip 파일의 마지막 4 바이트 인 ISIZE의 값이라는 것을 알았습니다 덩어리.

큰 숫자의 .gz 파일이 함께 연결되어있는 것으로 나타났습니다. 그게 엉덩이에 완전히 고통 스럽지만, 당신이 이것을 접근 할 수있는 몇 가지 방법이 있습니다.

첫 번째는 gzip 헤더 서명 바이트 : 0x1F0x8B에 대한 압축 파일을 스캔하는 것입니다. 이 파일을 찾으면 (일반적으로) 스트림에 각 .gz 파일의 시작 부분이 있습니다. 파일의 오프셋 목록을 작성한 다음 파일의 각 청크를 추출하고 압축을 풀 수 있습니다.

또 다른 옵션은 입력 스트림에서 소비 한 바이트 수를보고하는 라이브러리를 사용하는 것입니다. 거의 모든 압축 해제 기가 일종의 버퍼링을 사용하기 때문에 입력 스트림이 소비 된 바이트 수보다 훨씬 더 많이 이동하므로 직접 추측하기가 어렵습니다. 그러나 DotNetZip 스트림은 실제 사용 된 입력 바이트를 제공 할 것이며,이 바이트를 사용하여 다음 시작 위치를 파악할 수 있습니다. 이렇게하면 파일을 스트림으로 처리하고 각 파일을 개별적으로 추출 할 수 있습니다.

어느 쪽이든 빠르지는 않습니다.

다음은 DotNetZip 라이브러리를 사용하여, 두 번째 옵션에 대한 방법입니다 :

public static IEnumerable<byte[]> UnpackCompositeFile(string filename) 
{ 
    using (var fstream = File.OpenRead(filename)) 
    { 
     long offset = 0; 
     while (offset < fstream.Length) 
     { 
      fstream.Position = p; 
      byte[] bytes = null; 
      using (var ms = new MemoryStream()) 
      using (var unpack = new Ionic.Zlib.GZipStream(fstream, Ionic.Zlib.CompressionMode.Decompress, true)) 
      { 
       unpack.CopyTo(ms); 
       bytes = ms.ToArray(); 
       // Total compressed bytes read, plus 10 for GZip header, plus 8 for GZip footer 
       offset += unpack.TotalIn + 18; 
      } 
      yield return bytes; 
     } 
    } 
} 

그것은 빠른 추한 아니다 (전체 파일의 압축을 나에게 약 48 초 걸렸습니다)하지만이 일 것으로 보인다. 각 byte[] 출력은 스트림의 단일 압축 파일을 나타냅니다. 이것들은 System.Text.Encoding.UTF8.GetString(...)으로 문자열로 바꿀 수 있고 그 다음 의미를 추출하기 위해 구문 분석 될 수 있습니다.

파일의 마지막 항목은 다음과 같습니다

WARC/1.0 
WARC-Type: metadata 
WARC-Target-URI: https://zverek-shop.ru/dljasobak/ruletka_sobaki/ruletka-tros_standard_5_m_dlya_sobak_do_20_kg 
WARC-Date: 2017-11-25T14:16:01Z 
WARC-Record-ID: <urn:uuid:e19ef645-b057-4305-819f-7be2687c3f19> 
WARC-Refers-To: <urn:uuid:df5de410-d4af-45ce-b545-c699e535765f> 
Content-Type: application/json 
Content-Length: 1075 

{"Container":{"Filename":"CC-MAIN-20171117170336-20171117190336-00002.warc.gz","Compressed":true,"Offset":"904209205","Gzip-Metadata":{"Inflated-Length":"463","Footer-Length":"8","Inflated-CRC":"1610542914","Deflate-Length":"335","Header-Length":"10"}},"Envelope":{"Format":"WARC","WARC-Header-Length":"438","Actual-Content-Length":"21","WARC-Header-Metadata":{"WARC-Target-URI":"https://zverek-shop.ru/dljasobak/ruletka_sobaki/ruletka-tros_standard_5_m_dlya_sobak_do_20_kg","WARC-Warcinfo-ID":"<urn:uuid:283e4862-166e-424c-b8fd-023bfb4f18f2>","WARC-Concurrent-To":"<urn:uuid:ca594c00-269b-4690-b514-f2bfc39c2d69>","WARC-Date":"2017-11-17T17:43:04Z","Content-Length":"21","WARC-Record-ID":"<urn:uuid:df5de410-d4af-45ce-b545-c699e535765f>","WARC-Type":"metadata","Content-Type":"application/warc-fields"},"Block-Digest":"sha1:4SKCIFKJX5QWLVICLR5Y2BYE6IBVMO3Z","Payload-Metadata":{"Actual-Content-Type":"application/metadata-fields","WARC-Metadata-Metadata":{"Metadata-Records":[{"Value":"1140","Name":"fetchTimeMs"}]},"Actual-Content-Length":"21","Trailing-Slop-Length":"0"}}} 

이이 후 두 개의 빈 라인을 포함하여 1441 바이트를 차지하는 기록이다.그냥 완성도를 위해서


...

TotalIn 속성은 압축 된 바이트 수는 Gzip으로 머리글과 바닥 글을 포함하지 않는 읽기 반환합니다. 위의 코드에서 GZip에 대한 최소 크기 인 머리글 및 바닥 글 크기에 상수 18 바이트를 사용합니다. 이 파일에서 작동하는 동안 연결 된 GZip 파일을 다루는 다른 사용자는 헤더에 큰 데이터를 추가하는 데이터가 있음을 발견 할 수 있습니다. 위의 데이터는 작동하지 않습니다.

  • 직접 Gzip으로 헤더를 구문 분석 및 압축 해제 DeflateStream를 사용 :이 경우

    은 두 가지 옵션이 있습니다.
  • TotalIn + 18 바이트부터 시작하는 GZip 서명 바이트를 검색합니다.

지나치게 속도를 늦추지 않고 작동해야합니다. 압축 해제 코드에서 버퍼링이 일어나기 때문에 각 세그먼트 후에 스트림을 거꾸로 탐색해야하므로 일부 추가 바이트를 읽는 것이 너무 느려지지는 않습니다.

+0

언급하지 않은 한가지 : 유효한 gzip 파일이 아닐 가능성. 거기에서 좋은 수사. –

+0

@JimMischel 감사합니다 ... 흥미로운 퍼즐이었습니다. 7Zip이 모든 것을 추출했다는 사실은 조금 혼란 스러웠습니다. 저들을 효율적으로 지내라 : P – Corey

0

유효한 gzip 스트림이며 gzip으로 압축 할 수 없습니다. 표준 (RFC 1952)에 따라, 유효한 gzip 스트림의 연결 또한 유효한 gzip 스트림입니다. 파일은 118,644 개 (!)의 원자 gzip 스트림을 연결 한 것입니다. 첫 번째 아톰 gzip 스트림은 382 바이트 길이이며 압축되지 않은 바이트가 548 개가됩니다. 그게 다야.

분명히 GzipStream 클래스는 첫 번째 압축 풀기를 완료 한 후에 다른 원자 gzip 스트림을 찾지 않아서 RFC 1952를 준수하지 않는 버그가 있습니다. 루프에서 직접 수행 할 수 있습니다 , 당신이 입력 파일의 끝에 도달 할 때까지.

참고로 파일의 각 gzip 스트림의 크기는 다소 비효율적입니다. 압축기는 롤링하기 위해 필요한 것보다 많은 데이터가 필요합니다. 해당 데이터가 단일 원자 gzip 스트림으로 압축되면 283,063,949 바이트 대신 195,606,385 바이트로 압축됩니다. 조각이 여러 개있는 경우에도 크기가 대략 메가 바이트 이상인 경우 거의 동일 크기로 압축됩니다 (조각 당 평균 10K 바이트에 비해 수백 개가 더 낫습니다).

관련 문제