2017-12-10 6 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は、zip形式の文書の最初の行であるコマンドラインに書かれた唯一548

の長さを有します。彼らは文字化けしていません。私は残りの部分をどうやって得るのか分かりません。

+0

これは小さいファイルでも動作することを確認しましたか?言って、メガバイトか?元の圧縮されていないファイルがUTF-8でエンコードされていることは確かですか? 'outmemstream'の内容をファイルに書き込んで、そこにあるものを確認しましたか?あなたは記憶がなくなっていないと確信していますか?そのコードは、2ギガバイト以上のRAM(memstreamの場合は280MB、おそらく圧縮されていないデータの場合は2倍、作成している文字列の場合は2倍)を食べるようです。 –

+0

@JimMischel実際、それはそれより悪いです。圧縮されたデータはASCIIテキストのようです。圧縮されていないデータの場合は1.2GB、文字列表現の場合は2倍です。 – Corey

答えて

2

.NET GZipStreamは、ファイル内の最初のレコードのすべてである平文の最初の548バイトをアンパックします。 7Zipはファイル全体を1.2GBの出力ファイルに抽出しますが、レコードセパレータを使用しないプレーンテキスト(約130万行分)であり、7Zipでファイルをテストすると1,441バイトと報告されます。

私はいくつかを調べて、このことを直接解凍する単一の圧縮ライブラリを見つけることができませんでした。

ファイル内で少しキャストした後、1,441バイトが通常はgzipファイルの最後の4バイトであるISIZEの値であることがわかりました。これは圧縮データに追加された8バイトフッターレコードの一部ですチャンク。

あなたが持っているのは、一緒に連結された.gzファイルの大きなセットです。そして、それはお尻の完全な痛みですが、あなたがこれにアプローチする方法はいくつかあります。

最初に、圧縮ファイルのgzipヘッダーシグネチャバイトをスキャンします:0x1F0x8Bです。これらのファイルを見つけると、(通常は)各.gzファイルの開始点がストリーム内にあります。ファイルのオフセットのリストを作成し、ファイルの各チャンクを抽出して解凍することができます。

もう1つの選択肢は、入力ストリームから消費されたバイト数を報告するライブラリを使用することです。ほとんどすべてのデコンプレッサーは何らかのバッファリングを使用するので、入力ストリームは消費されたバイト数よりもはるかに多く移動するので、これを直接推測することは困難です。しかし、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"}}} 

これが後に2行の空白行を含む1441バイトを占めているレコードです。ただ、完全を期すために...

TotalInプロパティは、圧縮されたバイトの数は、gzipヘッダとフッタを含まない、読み取り


を返します。上記のコードでは、ヘッダーとフッターのサイズに一定の18バイトを使用します。これはGZipの最小サイズです。これはこのファイルでは機能しますが、連結されたGZipファイルを扱う人なら誰でもヘッダに大きなデータがあることが分かります。

  • 直接gzipヘッダを解析し、解凍するDeflateStreamを使用:あなたは2つのオプションがあります。この場合

  • TotalIn + 18バイトから始まるGZipシグネチャバイトをスキャンします。

どちらも遅くなくても機能します。圧縮解除コードでバッファリングが行われているので、各セグメントの後ろでストリームを後ろ向きに探索しなければならないので、追加のバイトを読み込んでも遅くなることはありません。

+0

私が言及しなかった1つのこと:それは有効なgzipファイルではない可能性。そこでは良い捜査。 –

+0

@JimMischelありがとう...それは面白いパズルだった。 7Zipがすべてを抽出したという事実は少し混乱しました。効率的であるためにそれらの男を嫌う:P – Corey

0

これは有効なgzipストリームで、gzipで圧縮解除できます。標準(RFC 1952)では、有効なgzipストリームの連結も有効なgzipストリームです。あなたのファイルは118,644(!)アトミックgzipストリームの連結です。最初のアトミックgzipストリームの長さは382バイトで、圧縮されていないバイトは548バイトになります。それはあなたが得ているすべてです。

それは最初のものの解凍を完了した後に、それは他の原子のgzipストリームを探していないという点で、どうやらGzipStreamクラスはバグがあり、そしてあなたが自分だけのループでそれを行うことができますRFC 1952を遵守されていませんあなたが入力ファイルの終わりに達するまで。

ファイル内の各gzipストリームのサイズが小さいことは、どちらかといえば非効率です。ローリングするには、圧縮機より多くのデータが必要です。そのデータが1つのアトミックgzipストリームとして圧縮されている場合は、283,063,949バイトの代わりに195,606,385バイトに圧縮されます。それはあなたがそこに持っている1ピース当たり平均10Kバイトに数百とは対照的に、サイズがより大きいメガバイト以上である限り、多くのピースであってもほぼ同じサイズに圧縮されます。

関連する問題