Gzip

Base64 で書かれた gzip・zlib・生の deflate を貼り付けて中身を読み(どれかはバイトが示します)、テキストをそのいずれかに圧縮します。ブラウザ内で動作します。

入力
またはここにドロップ — ローカルで読み込まれ、送信されることはありません
出力
[
  {
    "name": "さくら",
    "city": "東京"
  },
  {
    "name": "蓮",
    "city": "大阪"
  },
  {
    "name": "陽菜",
    "city": "京都"
  },
  {
    "name": "大翔",
    "city": "札幌"
  },
  {
    "name": "結衣",
    "city": "福岡"
  },
  {
    "name": "悠真",
    "city": "名古屋"
  },
  {
    "name": "葵",
    "city": "横浜"
  },
  {
    "name": "湊",
    "city": "神戸"
  }
]

gzip として読みました。

圧縮前のバイト数
415
圧縮後のバイト数
186
変化
-55.2%
Base64 の文字数
248

3 つのラッパーに包まれた 1 つの圧縮と、deflate という語

gzip、zlib、生の deflate は、DEFLATE という 1 つの圧縮を 3 通りに包んだものです。DEFLATE そのものは RFC 1951 が定めています。バイトをブロックに区切り、各ブロックを符号として書くもので、符号の 1 つ 1 つは、1 バイトか、それより前に出てきたバイトの並びを写したものを表します。gzip のラッパーは RFC 1952 が定めるもので、前に少なくとも 10 バイトのヘッダーを、後ろに 8 バイト — 元のバイトの CRC-32 とその長さ — を置きます。zlib のラッパーは RFC 1950 が定めるもので、前に 2 バイトを、後ろに Adler-32 を置きます。そして生の deflate は、ラッパーをまったく持たない圧縮そのものです。中の圧縮データは 3 つとも同じでありうるので、違いはラッパーだけです。

名前がもつれるのは、deflate という語のところです。HTTP にとって Content-Encoding: deflate は zlib のラッパーを意味し、RFC 9110 は、それでもこの名前で生の deflate を送るサーバーがあると注記しています。ブラウザ自身の圧縮器は HTTP に従っていて、zlib のラッパーを deflate という名前で呼び、生の deflate を deflate-raw という名前で呼びます。.NET の DeflateStream と PHP の gzdeflate は生の deflate を書き、PHP の gzcompress と Python の zlib.compress は zlib のラッパーを書きます。ですから deflate というラベルの付いたバイトはどちらでもありえて、どちらなのかを言えるのはバイトだけです。

だからこのページは、解凍するときにはラッパーをバイトから読み取り、どれとして読んだかを、出てきたものの下に示します。「gzip として読みました」、「zlib として読みました」、「生の deflate として読みました」のいずれかです。決め手はバイトです。gzip は必ず 1f 8b で始まり、zlib の最初の 2 バイトは、2 つを合わせて 1 つの数として読むと 31 の倍数になり、生の deflate をそのどちらかで始めるエンコーダーはありません。.gz という名前のファイルに zlib が入っていれば zlib として読まれ、名前とバイトが食い違っているという知らせが付きます。3 つのどれでもないバイトは、解凍するものがないとして拒否されます。圧縮するときはラッパーをあなたが選び、別のものを選ぶまでは gzip です。

ペイロードの出どころと、H4sI と eJ の正体

圧縮されたバイトが開発者の手元に届く形はいくつかあります。Content-Encoding が gzip か deflate を示す HTTP レスポンスの本文として、ファイルとして、あるいは Base64 や 16 進数で書かれたテキストとしてです。gzip のストリームはどれも同じ 3 バイト、1f 8b 08 で始まります — 最初の 2 バイトが署名で、3 バイト目は圧縮方式の番号、つまり DEFLATE を表す番号です — そして 3 バイトはちょうど Base64 の 4 文字に当たるので、Base64 で書いた gzip は必ず H4sI で始まります。CloudWatch Logs のサブスクリプションが Lambda 関数や Kinesis ストリームに渡す各レコードのデータはこの形で、Helm がリリースを保存する形もこれです。リリースの JSON を gzip で圧縮し、それを Base64 で書くのです。Helm がリリースを Kubernetes の Secret に保管しているなら、Secret から直接読むと Base64 が二重になっています。Secret は自分のデータを独自に Base64 で保持するからです。その外側の Base64 は「Base64」ツールがデコードし、そこから、このページが読む H4sI… が出てきます。

zlib の 2 バイトのヘッダーは、圧縮方式、ウィンドウサイズ、そして書かれたときのレベルを示します。そのため zlib の既定のウィンドウサイズなら、その Base64 は 4 通りのどれかで始まります。既定のレベルなら eJ で、これは Python の zlib.compress が特に指定されない限り書くものです。それより低いレベルなら eA か eF、それより高いレベルなら eN です。生の deflate には署名がまったくなく、その Base64 は、最初のブロックがたまたまどう始まるかで決まります。

「圧縮データの表記」は、ページがそのバイトをテキストとしてどう読み書きするかを決めます。ページが開いたときの「Base64」か、「16 進数」です。この選択はどちらの方向にも適用され、ページがあなたの代わりにそれを変えることはありません。16 進数の数字はどれも Base64 の文字でもあるので、1f8b0800 はどちらで読んでもテキストであり、それぞれで別のバイトになるからです。Base64 は、標準のものでも URL セーフなものでも、パディングの有無を問わず、スペースや改行をまたいでも読まれ、書くときはパディング付きで書かれます。「URL セーフ」スイッチがオンなら、パディングなしの URL セーフで書かれます。16 進数は、スペースで区切った 2 桁ずつの組で書かれ、読むときは、スペース区切りでも続けて書いたものでも、前に 0x が付いていても、16 進ダンプでも受け付けます — そして 0x は、SQL Server の COMPRESS() が返す gzip のようなバイナリ値を T-SQL が書く形です。16 進数を Base64 として読んで失敗したとき、または Base64 を 16 進数として読んで 16 進数にない文字で拒否されたときに、どちらの場合でもテキスト全体がもう一方として読めるなら、知らせがそう伝え、横に切り替えるボタンが出ます。そのボタンを押すまで、何も切り替わりません。

ストリームを読む: メンバー、その後ろのバイト、早すぎる終わり

RFC 1952 では、gzip ファイルはメンバーの連なりです。メンバーとは、それぞれが自分のヘッダーとトレーラーを持つ完全な gzip ストリームのことで、それが次々に並びます。cat a.gz b.gz はそのようなファイルを作りますし、gzip -c file >> archive.gz で既存のファイルに追加しても同じで、これは GNU gzip のマニュアル自身の例です。gunzip はすべてのメンバーを読み、中身をつなげます。このページも同じように読みます。メンバーを 1 つずつ読んで確認し、中身をつなげて表示し、いくつ読んだかを知らせが伝えます。gzip を読むツールがすべてこうするわけではありません。ブラウザ自身の解凍器が従う標準は、gzip ストリームにメンバーを 1 つしか認めず、2 つ目をエラーとします。そして Python の zlib.decompress は、最初の 1 つのあとで何も言わずに止まります。

終わりの後ろにあって、どのメンバーも始めないバイトは、拒否されずに読み飛ばされます。その前にあるものはすべて完全で、確認済みだからです。そして知らせが、そのバイトがいくつあり、どのオフセットから始まり、すべてゼロかどうかを伝えます。そこにあるゼロはパディングです — GNU gzip のマニュアルでは、テープでブロックの終わりまで埋めるために書かれたものとして出てきます — そして gunzip はそれを黙って読み飛ばします。ほかのバイトは、末尾のゴミを無視したという警告を出して読み飛ばしますが、Python の gzip.decompress はそのファイルを拒否します。zlib ストリームの Adler-32 の後ろでも、生のストリームの最後のブロックの後ろでも同じです。ただし生の deflate には、確認するチェックサムがありません。

貼り付けが途中で切れていたり、ダウンロードが途中で止まったりして、終わりに達する前に終わっているストリームは、届いたところまで表示され、チェックサムは確認していないという知らせが付きます。壊れたストリームは拒否され、何も表示されません。これは gunzip より厳格です。gunzip は、誤りを見つけるチェックサムにたどり着く前に、それまでに解凍したものを書き出すからです。しかし、1 ビットが変わっても、何かがそれを明らかにするまで、しばらくは何事もなくデコードが進むことがあるので、破損が明らかになる前に出てきたものも、すでに誤っているかもしれません。位置も示しません。デコーダーが破損に気づく場所は、破損がある場所ではないからです。そして、事前設定の辞書を求める zlib ストリームは、それ専用の文で拒否されます。そのストリームは、圧縮器があらかじめ与えられていたバイトを識別するだけで、そのバイト自体は運んでいないので、それを読む手立てがないのです。

出てきたものは、「バイトの表記」で選んだとおり、UTF-8 の「テキスト」か「16 進数」で書かれます。それはしばしば JSON で、「JSON フォーマッター」がそれを整形して検証し、壊れている行と列を指し示します。gzip で圧縮された画像やアーカイブはテキストではないので、「テキスト」のときは、UTF-8 として読めないバイト列がそれぞれ U+FFFD として表示され、そうしたバイト列の数を数える知らせが、バイトを 16 進数で表示するボタンとともに出ます。どちらの場合も、「ダウンロード」はバイトそのものを保存します。結果の下の 1 行は、最初のメンバーのヘッダーが保存しているもの — 名前、UTC での時刻、コメント — を示し、保存された名前が「ダウンロード」の保存するファイルの名前になることはありません。このページが一度に解凍する上限を超える出力はそこで止まり、その大きさを示す知らせが付きます。また、gzip のトレーラーがその上限を超える長さを記しているときは、処理の途中でページがそう伝えます。

小さな入力が大きくなる理由と、Base64 が加えるもの

圧縮は繰り返しを見つけることで得をしますが、短いテキストには繰り返しがほとんどありません。一方で、ラッパーにはどれも独自のバイトがかかります。gzip のヘッダーとトレーラーは、ヘッダーがそれ以上何も保存しないときで合わせて 18 バイト、zlib では 6 バイトになり、生の deflate にはそれがありません。ただし DEFLATE は、ブロックの区切りを記すのに数ビットを使います。ですから 13 バイトの Hello, world! は、gzip では 33 バイト、zlib では 21 バイト、生の deflate では 15 バイトになります。結果の下のサイズの行は、「圧縮前のバイト数」と「圧縮後のバイト数」、そしてそのあいだの「変化」を示し、この gzip なら +153.8% です。そして結果が大きくなったときは、知らせがその理由を伝えます。JSON やログのように繰り返しの多いテキストは、そのコストを何倍にもして取り戻します。ページが開いたときの例、小さな JSON は、元の半分未満の大きさになります。

テキストとして書くと、バイトはさらに長くなります。Base64 は 3 バイトごとに 4 文字を使って 3 分の 1 長くなり — 「Base64」ツールのガイドが説明しているとおりです — その 33 バイトの gzip は 44 文字になります。16 進数は 1 バイトに 2 桁を使い、ページはそれをスペースで区切った組で書くので、同じ 33 バイトが 98 文字になります。サイズの行はこの文字数も「Base64 の文字数」や「16 進数の文字数」として数え、「データ容量変換」は、その行のどの数も KB や KiB に変換します。

圧縮: レベルなし、gzip -c とは違うバイト、そして Brotli なし

圧縮はブラウザ自身の仕事で、Compression 標準が定める CompressionStream を通して行われます。その標準は圧縮レベルを提供しないので、このページも提供しません。得られるのはブラウザの既定のレベルです。gzip -9 なら少し小さくなるかもしれませんが、大きく違うことはめったにありません。

バイトも gzip -c が書くものとは違います。ただし、どちらを解凍しても同じテキストになります。まずヘッダーが違います。GNU gzip は、-n を指定されない限りファイルの名前と時刻を保存し、パイプからの入力なら時刻をゼロとします。ヘッダーの 1 バイトには、-9 や -1 を使ったことの印を記録します。そして RFC 1952 が OS に割り当てている 10 番目のバイトには、Linux では Unix の番号である 03 を書きます。ブラウザに渡されるのはバイトだけなので、あなたのファイルの名前も時刻も、結果と一緒に出ていくことはありません。Chromium は名前を保存せず、時刻をゼロとします — これは gzip -n が選ぶのと同じです — そして 10 番目のバイトには Linux では 03 を書き、Windows の Chrome はそこに 0a を書きます。次に、圧縮データが違います。GNU gzip の圧縮器はブラウザのものとは別物なので、長いテキストでは、同じレベルでも両者は違うバイトを選びます。ですから gzip -c との食い違いは、それだけでは何も意味しません。大切なのは、どちらも解凍すると同じバイトになることです。

Brotli は、今のところここにはありません。Compression 標準はそれを挙げていますが、まだすべてのブラウザが書けるわけではなく、ブラウザが対応している場合にしかページが提供できない選択肢は、ブラウザごとに別のページを生むことになります。zstd は、その標準にはまったく入っていません。このページが読み書きするのは 3 つのラッパーに包まれた DEFLATE であり、それ以外の何物でもありません。

ファイルが入り、ファイルが出て、何もアップロードされない

どちらの方向でも、テキストボックスの代わりにファイルを使えます。ファイルを選択するか、ボックスにドロップしてください。そのバイトは、「圧縮データの表記」も「バイトの表記」も通さずに、そのまま読まれます。テキストボックスには上限がありますが、ここではファイルに上限はありません。ただし、非常に大きなファイルには、ブラウザがそれを保持しきれないかもしれないという警告が出ます。解凍するとき、「ダウンロード」は gunzip と同じように結果に名前を付けます。ファイルの名前から .gz を除き、.tgz は .tar にし、さらに .zz や .deflate も除きます。この 2 つは、このページがほかの 2 つのラッパーに付ける拡張子です。それ以外の名前や、貼り付けたものはすべて、bytes.bin として出てきます。圧縮するときは、ファイルの名前に .gz、.zz、.deflate のどれかを付けます — .zz は pigz が zlib に付ける名前です — 入力したテキストなら、text という語に付けます。

「入力として使う」は結果をボックスに移して方向を反転させるので、往復は 1 回のクリックで済みます。ファイルから来た結果や、ボックスには大きすぎる結果は、ファイルとして移ります。どちらの方向でも、ファイルはこのタブの中で読まれ、処理はページがあなた自身の端末上で起動する Web Worker の中で行われます。そのためにペイロードやファイルがアップロードされることはありません。

gunzip と Python で同じことをする

コマンドラインでは、base64 -d がペイロードをバイトに戻したあと、gunzip と zcat が、このページと同じようにすべてのメンバーを含めて gzip を読みます。ただし、どちらも zlib や生の deflate は読まず、gzip ではないとして拒否します。gzip -k はファイルを圧縮し、元のファイルをその横に残します。Python では、gzip.decompress が gzip のラッパーとその中のすべてのメンバーを読み、zlib.decompress は、wbits でどれかを指定されて、3 つのラッパーのどれでも読みます:

# ペイロード: デコードしてから解凍する
printf 'H4sIAAAAAAAAA/NIzcnJ11Eozy/KSVEEAObG5usNAAAA' | base64 -d | gunzip   # Hello, world!

# ファイル: 元を残したまま圧縮してから中身を表示し直す
printf 'Hello, world!' > hello.txt
gzip -k hello.txt
zcat hello.txt.gz                                  # Hello, world!

# メンバーが二つ: 二つ目を一つ目の後ろに足して一つとして読み戻す
printf 'Hello, ' | gzip > two.gz
printf 'world!' | gzip >> two.gz
zcat two.gz                                        # Hello, world!

# 同じことをスクリプトで: 全メンバーを読んでからラッパーを一つずつ
import base64, gzip, zlib
two = open('two.gz', 'rb').read()
zl = base64.b64decode('eJzzSM3JyddRKM8vyklRBAAgXgSK')
raw = base64.b64decode('80jNycnXUSjPL8pJUQQA')
gzip.decompress(two)                               # b'Hello, world!'
zlib.decompress(two, wbits=31)                     # b'Hello, '
zlib.decompress(zl, wbits=15)                      # b'Hello, world!'
zlib.decompress(raw, wbits=-15)                    # b'Hello, world!'
zlib.decompress(zl, wbits=47)                      # b'Hello, world!'

wbits の 31 は gzip のラッパーを求め、既定の 15 は zlib のラッパーを、-15 は生の deflate を求めます。どれにも含まれる 15 は最大のウィンドウサイズを表し、47 は gzip と zlib のうち、バイトが始まっているほうを受け付けます。ただし zlib.decompress はメンバーを 1 つしか読まず、残りを何も言わずに捨てます。上の 2 つのメンバーから b'Hello, ' だけを返すのはそのためで、gzip.decompress はすべてを読みます。Python は、このページより二つの点で厳格でもあります。gzip.decompress は終わりの後ろにあるゼロ以外のバイトを拒否し、どちらの関数も早く終わったストリームを拒否します。このページなら、出てきたものを表示するところです。

よくある質問

圧縮した結果が、入力したものより大きくなるのはなぜですか?
ラッパーにはそれぞれ独自のバイトがかかり、短いテキストには圧縮で取り除けるものがほとんどないからです。gzip は 18 バイト、zlib は 6 バイトを加え、そのうえ DEFLATE はブロックの区切りを記すので、長い JSON は縮むのに、数語のテキストは大きくなります。ページはそのことを知らせで伝えます。3 つのうちで最も小さいのは生の deflate です。Base64 で書けば、どの結果もさらに 3 分の 1 長くなります。
出力が gzip -c と一致しないのはなぜですか?
一致するという保証はどこにもないからです。gzip のヘッダーには、名前、時刻、レベルの印、OS を表すバイトを入れられ、GNU gzip とブラウザはそれを違うふうに埋めます。そのうえ両者は別々の圧縮器なので、長めのテキストでは、ヘッダーとトレーラーのあいだのデータさえ違うことがあります。同じテキストの 2 つの gzip ストリームは、どちらも解凍してそのテキストになるなら、どちらも正しいのです。ですから、解凍した結果を比べてください。「入力として使う」なら、このページの結果をすぐに元に戻せます。
ペイロードの先頭にある H4sI は何を意味しますか?
そのペイロードが Base64 で書かれた gzip だということです。gzip のストリームはどれも 1f 8b 08 というバイトで始まり、この 3 バイトは Base64 では H4sI になります。そのままここに貼り付けてください。eJ で始まるペイロードは、たいてい既定のレベルの zlib です。どちらでも始まらないものは、生の deflate かもしれず、まったく圧縮されていないかもしれません。どちらなのかはページが伝えます。
gzip は暗号化ですか?
いいえ。圧縮には鍵がないので、バイトを持っている人なら誰でも、ここでも gunzip でも解凍でき、すべてのバイトが元に戻ります。gzip で圧縮したペイロードの中のパスワードは、そのまま書き出したパスワードと同じくらい無防備です。
ペイロードやファイルはどこかにアップロードされますか?
いいえ。貼り付けたペイロードも、選択またはドロップしたファイルも、どちらもこのタブの中で読まれます。そこではページが起動する Web Worker が圧縮と解凍を行い、「ダウンロード」もその場で結果からファイルを作ります。どちらも、このサイトにもほかの誰にも送られません。

関連するツール

  • JSON フォーマッター

    JSON を検証して整形 — エラー位置も明示。

  • データ容量変換

    KB、MB、GB と KiB、MiB、GiB を相互変換。

  • Base64

    Base64 のエンコードとデコード — UTF-8 完全対応。

  • Base32

    Base32 と base32hex のエンコードとデコード — UTF-8 のテキストも 16 進数のバイトも。