Gzip
Вставьте gzip, zlib или сырой deflate в Base64, чтобы прочитать содержимое — какой из трёх, скажут байты, — или сожмите текст в любой из них, в браузере.
[
{
"name": "Анна",
"city": "Москва"
},
{
"name": "Иван",
"city": "Казань"
},
{
"name": "Мария",
"city": "Новосибирск"
},
{
"name": "Дмитрий",
"city": "Екатеринбург"
},
{
"name": "Ольга",
"city": "Санкт-Петербург"
},
{
"name": "Сергей",
"city": "Самара"
},
{
"name": "Елена",
"city": "Сочи"
},
{
"name": "Михаил",
"city": "Владивосток"
}
]Прочитано как gzip.
- Несжатых байтов
- 547
- Сжатых байтов
- 233
- Изменение
- -57,4 %
- Символов Base64
- 312
Одно сжатие в трёх обёртках и слово deflate
gzip, zlib и сырой deflate — это одно сжатие, DEFLATE, обёрнутое тремя способами. RFC 1951 определяет сам DEFLATE: байты разрезаются на блоки, и каждый блок записывается кодами, каждый из которых обозначает байт или отрезок, скопированный из того, что было раньше. Обёртка gzip, RFC 1952, ставит впереди заголовок длиной не меньше десяти байт, а позади — восемь байт: CRC-32 исходных байтов и их длину; обёртка zlib, RFC 1950, ставит впереди два байта, а позади — Adler-32; сырой deflate — это сжатие вовсе без обёртки. Сжатые данные внутри могут быть одними и теми же во всех трёх, так что обёртка и составляет всю разницу.
Именно на слове deflate названия и путаются. Для HTTP Content-Encoding: deflate означает обёртку zlib, а RFC 9110 отмечает, что некоторые серверы всё равно отправляют под этим именем сырой deflate. Собственный компрессор браузера следует HTTP: обёртку zlib он называет именем deflate, а сырой deflate — именем deflate-raw. DeflateStream из .NET и gzdeflate из PHP пишут сырой deflate, тогда как gzcompress из PHP и zlib.compress из Python пишут обёртку zlib. Так что байты с пометкой deflate могут оказаться и тем и другим, и сказать, чем именно, могут только сами байты.
Поэтому при распаковке страница определяет обёртку по байтам и под результатом сообщает, какую прочитала: «Прочитано как gzip», «Прочитано как zlib» или «Прочитано как сырой deflate». Решают байты: gzip всегда начинается с 1f 8b, первые два байта zlib, прочитанные вместе как одно число, кратны 31, а сырой deflate ни один кодировщик не начинает ни с того, ни с другого. Файл с расширением .gz, в котором лежит zlib, читается как zlib, с замечанием, что имя и байты расходятся, а байты, не относящиеся ни к одной из трёх обёрток, отклоняются: распаковывать нечего. При сжатии обёртку выбираете вы, и это gzip, пока вы не выберете другую.
Откуда берутся полезные нагрузки и что такое H4sI и eJ
Сжатые байты попадают к разработчику в нескольких видах: как тело ответа HTTP, чей Content-Encoding называет gzip или deflate, как файл или как текст, записанный в Base64 или шестнадцатеричным кодом. Каждый поток gzip начинается с одних и тех же трёх байтов, 1f 8b 08 — двух байтов сигнатуры и номера его метода сжатия, то есть DEFLATE, — а три байта — это ровно четыре символа Base64, поэтому gzip, записанный в Base64, всегда начинается с H4sI. Именно это подписка CloudWatch Logs передаёт функции Lambda или потоку Kinesis в данных каждой записи, и так Helm хранит релиз: его JSON сжимается gzip, а затем записывается в Base64. Когда Helm держит релиз в Secret Kubernetes, чтение прямо из Secret даёт Base64 внутри Base64, поскольку Secret хранит свои данные в собственном Base64; инструмент Base64 декодирует этот внешний слой в H4sI…, который читает эта страница.
Два байта заголовка zlib указывают его метод, его окно и уровень, на котором он записан, поэтому при окне zlib по умолчанию его Base64 начинается одним из четырёх способов: eJ на уровне по умолчанию — это то, что пишет zlib.compress из Python, если не попросить иного, — eA или eF на уровнях ниже него и eN на тех, что выше. У сырого deflate нет никакой сигнатуры, и то, с чего начинается его Base64, зависит лишь от того, как начинается его первый блок.
«Сжатые байты как» определяет, как страница читает и пишет эти байты в виде текста: «Base64», с которым страница открывается, или «Шестнадцатеричный код». Этот выбор действует в обоих направлениях, и страница никогда не меняет его за вас, потому что каждая шестнадцатеричная цифра — это ещё и символ Base64, так что 1f8b0800 — текст в обоих и разные байты в каждом. Base64 читается в стандартном алфавите или в URL-безопасном, с дополнением или без него, сквозь пробелы и переводы строк, а пишется с дополнением или, когда включена опция «URL-безопасный», в URL-безопасном алфавите без дополнения. Шестнадцатеричный код пишется парами через пробел, а читается через пробел или слитно, с 0x впереди или в виде дампа — а с 0x T-SQL записывает двоичное значение, например gzip, который возвращает COMPRESS() из SQL Server. Когда шестнадцатеричный код, прочитанный как Base64, не читается или Base64, прочитанный как шестнадцатеричный код, отклоняется на символе, которого в шестнадцатеричном коде нет, и в обоих случаях весь текст читается другим способом, об этом сообщает замечание рядом с кнопкой, которая переключает; ничего не переключается, пока вы её не нажмёте.
Чтение потока: члены, байты после него, ранний конец
RFC 1952 делает файл gzip последовательностью членов — целых потоков gzip, идущих один за другим, у каждого из которых свои заголовок и трейлер. Такой файл получается командой cat a.gz b.gz, а также дописыванием в существующий файл командой gzip -c file >> archive.gz — это собственный пример руководства GNU gzip; gunzip читает каждый член и склеивает их содержимое. Эта страница читает их так же: каждый член читается и проверяется, их содержимое показывается подряд, а замечание сообщает, сколько их прочитано. Так поступает не всякая программа, читающая gzip. Стандарт, по которому распаковывает сам браузер, допускает в потоке gzip один член и считает второй ошибкой, а zlib.decompress из Python останавливается после первого, не говоря ни слова.
Байты после конца, не начинающие нового члена, пропускаются, а не отклоняются, поскольку всё, что перед ними, полно и проверено, а замечание сообщает, сколько их, с какого смещения они начинаются и все ли они нулевые. Нули там — это дополнение (руководство GNU gzip встречает их на магнитных лентах, где ими дописывают блок до конца), и gunzip пропускает их молча; другие байты он пропускает с предупреждением, что мусор в конце проигнорирован, тогда как gzip.decompress из Python отклоняет такой файл. То же верно и после Adler-32 потока zlib, и после последнего блока потока сырого deflate, хотя сырой deflate не несёт контрольной суммы, которую можно было бы проверить.
Поток, который кончается раньше своего конца, — например, обрезанная вставка или скачивание, прерванное на полпути, — показывается настолько, насколько доходит, с замечанием, что контрольная сумма не проверена. Повреждённый поток отклоняется, и из него ничего не показывается. Это строже, чем gunzip, который выводит то, что успел распаковать, прежде чем дойдёт до контрольной суммы, которая покажет, что это неверно; но изменённый бит может какое-то время молча декодироваться, прежде чем что-нибудь его выдаст, так что вышедшее до того, как проявилось повреждение, уже может быть неверным. Позиция тоже не сообщается, поскольку место, где декодер замечает повреждение, — не то место, где оно находится. А поток zlib, которому нужен заранее заданный словарь, отклоняется отдельной фразой: поток указывает, какими байтами был заранее подготовлен его компрессор, но сам их не несёт, так что читать его не с чем.
Результат записывается так, как выбрано в переключателе «Байты как»: вариант «Текст» — в UTF-8, вариант «Шестнадцатеричный код» — шестнадцатеричным кодом. Часто это JSON, который Форматировщик JSON раскладывает и проверяет, указывая строку и столбец, где он ломается. Сжатая gzip картинка или архив — не текст, поэтому, когда выбран «Текст», каждая последовательность их байтов, не являющаяся UTF-8, показывается как U+FFFD, с замечанием, подсчитывающим эти последовательности, рядом с кнопкой, которая показывает байты шестнадцатеричным кодом; кнопка «Скачать» в любом случае сохраняет сами байты. Строка под результатом показывает, что хранит заголовок первого члена, — имя, время в UTC и комментарий, — и сохранённое имя никогда не даёт имени файлу, который сохраняет «Скачать». Вывод, превышающий то, что страница распаковывает за один раз, на этом обрывается, с замечанием, называющим этот размер, а когда трейлер gzip указывает длину больше этого предела, страница сообщает об этом, пока работа ещё идёт.
Почему маленький ввод растёт и что добавляет Base64
Сжатие выигрывает, находя повторы, а в коротком тексте их мало, тогда как каждая обёртка стоит собственных байтов: заголовок и трейлер gzip составляют 18 байт, когда заголовок больше ничего не хранит, у zlib — 6, а у сырого deflate их нет вовсе, хотя DEFLATE тратит несколько бит на разметку своих блоков. Поэтому Hello, world!, 13 байт, превращается в 33 байта в gzip, в 21 — в zlib и в 15 — в сыром deflate. Строка размеров под результатом считает байты с каждой стороны и изменение между ними, +153,8 % для этого gzip, а когда результат растёт, замечание объясняет почему. Текст с обилием повторов, например JSON или журнал, окупает эти расходы многократно: пример, с которым открывается страница, небольшой JSON, выходит меньше половины своего размера.
Записанные как текст, байты обходятся ещё дороже. Base64 берёт четыре символа на каждые три байта, на треть больше, как объясняет руководство к инструменту Base64, так что эти 33 байта gzip — это 44 символа; шестнадцатеричный код берёт по две цифры на байт, и страница пишет их парами через пробел — 98 символов для тех же 33 байт. Строка размеров считает и эти символы, а Конвертер размеров данных переводит любое из её чисел в KB или KiB.
Сжатие: без уровня, не байты gzip -c и без Brotli
Сжатие — это работа самого браузера, через CompressionStream, который определяет стандарт Compression, а этот стандарт не предлагает уровня сжатия, поэтому не предлагает его и эта страница: вы получаете то, что браузер делает по умолчанию. gzip -9 может выйти немного меньше, и редко намного.
Не совпадают и байты с теми, что пишет gzip -c, хотя и те и другие распаковываются в один и тот же текст. Сначала различаются заголовки. GNU gzip сохраняет имя и время файла, если ему не сказать -n, а при чтении из конвейера — нулевое время; он отмечает в одном байте заголовка, был ли задан -9 или -1; а в десятом байте, который RFC 1952 отводит операционной системе, он пишет 03 в Linux — номер Unix. Браузеру передаются одни байты, так что ни имя вашего файла, ни его время не могут уйти вместе с результатом: Chromium не сохраняет имени и пишет нулевое время — именно это выбирает gzip -n, — а в десятом байте пишет 03 в Linux, тогда как Chrome в Windows пишет 0a. Затем различаются и сами сжатые данные, поскольку компрессор GNU gzip — не компрессор браузера: на длинном тексте они выбирают разные байты на одном и том же уровне. Поэтому несовпадение с gzip -c само по себе ничего не значит; важно, что и те и другие распаковываются в одни и те же байты.
Brotli здесь нет — пока. Стандарт Compression его называет, но пишет его ещё не каждый браузер, а выбор, который страница могла бы предложить только там, где браузер его поддерживает, сделал бы её разными страницами в разных браузерах. zstd в этом стандарте нет вовсе. Эта страница читает и пишет DEFLATE в трёх его обёртках — и больше ничего.
Файл на входе, файл на выходе, и ничего не загружается
Файл может занять место текстового поля в обоих направлениях: выберите его или перетащите на поле. Его байты читаются как есть — ни «Сжатые байты как», ни «Байты как» к ним не применяются, — и у файла здесь нет предела, который есть у текстового поля, хотя на очень большой файл выводится предупреждение, что браузер может его не удержать. При распаковке кнопка «Скачать» называет результат так же, как gunzip: имя файла без .gz, причём .tgz становится .tar, а также без .zz или .deflate — расширений, которые эта страница пишет для двух других обёрток; любое другое имя и всё вставленное выходят как bytes.bin. При сжатии она добавляет .gz, .zz или .deflate к имени файла — .zz так называет zlib программа pigz — или к слову text для того, что вы набрали.
«Использовать как ввод» переносит результат в поле и меняет направление, так что путь туда и обратно занимает одно нажатие, а результат, пришедший из файла или слишком большой для поля, переносится как файл. В какую бы сторону ни шла работа, файл читается в этой вкладке, а работа идёт в Web Worker, который страница запускает на вашем собственном устройстве: ни полезная нагрузка, ни файл для этого никуда не загружаются.
То же самое с gunzip и Python
В командной строке gunzip и zcat читают gzip так же, как эта страница, со всеми членами, после того как base64 -d превратит полезную нагрузку обратно в байты, но ни тот ни другой не читает zlib или сырой deflate: оба отвергают их как не gzip; gzip -k сжимает файл и оставляет оригинал рядом. В Python gzip.decompress читает обёртку gzip и все члены в ней, а zlib.decompress читает любую из трёх обёрток, и какую именно, ему говорит его wbits:
# Полезная нагрузка: декодировать и затем распаковать
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 читает только один член, а остальные отбрасывает, не говоря ни слова, поэтому из двух членов выше он возвращает лишь b'Hello, '; gzip.decompress читает их все. Кроме того, Python строже этой страницы в двух отношениях: gzip.decompress отклоняет байты после конца, если это не нули, а обе функции отклоняют поток, который кончается раньше времени, тогда как страница показывает то, что вышло.
Частые вопросы
- Почему мой сжатый результат больше того, что я набрал?
- Потому что каждая обёртка стоит собственных байтов, а в коротком тексте сжатию почти нечего убрать: gzip добавляет 18 байт, zlib — 6, и DEFLATE вдобавок размечает свои блоки, поэтому несколько слов вырастают там, где длинный JSON уменьшается, и страница сообщает об этом в замечании. Сырой deflate — самый маленький из трёх. А в записи Base64 любой результат длиннее ещё на треть.
- Почему вывод не совпадает с gzip -c?
- Ничто не обещает, что он совпадёт. Заголовок gzip может хранить имя, время, отметку уровня и байт операционной системы, которые GNU gzip и браузер заполняют по-разному, а это два разных компрессора, так что на тексте подлиннее могут различаться даже данные между заголовком и трейлером. Два потока gzip одного текста оба верны, если каждый распаковывается в него, поэтому сравнивайте то, во что они распаковываются: «Использовать как ввод» сразу превращает результат этой страницы обратно.
- Что означает H4sI в начале полезной нагрузки?
- Что полезная нагрузка — это gzip, записанный в Base64: каждый поток gzip начинается с байтов 1f 8b 08, а эти три байта в Base64 — это H4sI. Вставьте её сюда как есть. Нагрузка, которая начинается с eJ, — скорее всего, zlib на уровне по умолчанию, а та, что не начинается ни с того, ни с другого, может быть сырым deflate или вовсе не сжатой, и это страница вам скажет.
- Является ли gzip шифрованием?
- Нет. Сжатие не принимает ключа, поэтому любой, у кого есть байты, может их распаковать — здесь или с помощью gunzip, — и каждый байт вернётся. Пароль внутри сжатой gzip полезной нагрузки открыт так же, как пароль, записанный целиком.
- Моя полезная нагрузка или мой файл куда-нибудь загружаются?
- Нет. Полезная нагрузка, которую вы вставляете, и файл, который вы выбираете или перетаскиваете, читаются внутри этой вкладки, где Web Worker, запущенный страницей, выполняет и сжатие, и распаковку, а «Скачать» собирает свой файл из результата прямо там. Ни то ни другое не отправляется ни на этот сайт, ни кому-либо ещё.
Похожие инструменты
- Форматировщик JSON
Проверяет и красиво форматирует JSON, с точными местами ошибок.
- Конвертер размеров данных
Перевод между КБ, МБ, ГБ и КиБ, МиБ, ГиБ.
- Base64
Кодирует и декодирует Base64 — полная поддержка UTF-8.
- Base32
Кодирует и декодирует Base32 и base32hex — текст в UTF-8 или байты в шестнадцатеричном коде.