Gzip
Cole gzip, zlib ou deflate bruto em Base64 para ler o que ele contém (os bytes dizem qual), ou digite um texto para comprimi-lo em um dos três, no navegador.
[
{
"name": "Ana",
"city": "São Paulo"
},
{
"name": "Miguel",
"city": "Rio de Janeiro"
},
{
"name": "Helena",
"city": "Salvador"
},
{
"name": "Arthur",
"city": "Belo Horizonte"
},
{
"name": "Laura",
"city": "Recife"
},
{
"name": "Davi",
"city": "Curitiba"
},
{
"name": "Alice",
"city": "Porto Alegre"
},
{
"name": "Heitor",
"city": "Fortaleza"
}
]Lido como gzip.
- Bytes não comprimidos
- 444
- Bytes comprimidos
- 183
- Variação
- -58,8%
- Caracteres de Base64
- 244
Uma compressão em três invólucros, e a palavra deflate
gzip, zlib e deflate bruto são uma única compressão, o DEFLATE, envolvida de três maneiras. A RFC 1951 define o próprio DEFLATE: os bytes divididos em blocos, cada um escrito como códigos que representam um byte ou um trecho copiado de mais atrás. O invólucro do gzip, a RFC 1952, põe na frente um cabeçalho de pelo menos dez bytes e atrás oito bytes, um CRC-32 dos bytes originais e o comprimento deles; o do zlib, a RFC 1950, põe dois bytes na frente e um Adler-32 atrás; o deflate bruto é a compressão sem invólucro nenhum. Os dados comprimidos lá dentro podem ser os mesmos nos três, então o invólucro é toda a diferença entre eles.
É na palavra deflate que os nomes se confundem. Para o HTTP, Content-Encoding: deflate significa o invólucro do zlib, e a RFC 9110 observa que alguns servidores enviam deflate bruto com esse nome mesmo assim. O compressor do próprio navegador segue o HTTP: chama o invólucro do zlib pelo nome deflate, e o deflate bruto pelo nome deflate-raw. O DeflateStream do .NET e o gzdeflate do PHP escrevem deflate bruto, enquanto o gzcompress do PHP e o zlib.compress do Python escrevem o invólucro do zlib. Então bytes rotulados como deflate podem ser qualquer um dos dois, e só os bytes podem dizer qual.
Por isso a página lê o invólucro a partir dos bytes quando descomprime, e diz qual leu abaixo do que saiu: “Lido como gzip”, “Lido como zlib” ou “Lido como deflate bruto”. Os bytes decidem: o gzip sempre começa com 1f 8b, os dois primeiros bytes do zlib, lidos juntos como um único número, são um múltiplo de 31, e nenhum codificador começa um deflate bruto com qualquer um dos dois. Um arquivo cujo nome termina em .gz mas que contém zlib é lido como zlib, com um aviso de que o nome e os bytes discordam, e bytes que não estão em nenhum dos três são recusados, sem nada para descomprimir. Ao comprimir, é você quem escolhe o invólucro, e ele é gzip até você escolher outro.
De onde vêm as cargas, e o que são H4sI e eJ
Bytes comprimidos chegam a um desenvolvedor de algumas formas: como o corpo de uma resposta HTTP cujo Content-Encoding indica gzip ou deflate, como um arquivo, ou como texto, escrito em Base64 ou em hexadecimal. Todo fluxo gzip começa com os mesmos três bytes, 1f 8b 08 — os dois bytes que o identificam e o número do seu método de compressão, que é o DEFLATE —, e três bytes são exatamente quatro caracteres de Base64, então gzip escrito em Base64 sempre começa com H4sI. É isso que uma assinatura do CloudWatch Logs entrega a uma função Lambda ou a um fluxo do Kinesis nos dados de cada registro, e é assim que o Helm armazena uma release: o JSON dela comprimido com gzip e depois escrito em Base64. Quando o Helm guarda uma release em um Secret do Kubernetes, lê-la direto do Secret dá Base64 dentro de Base64, já que um Secret guarda os seus dados em um Base64 próprio; a ferramenta Base64 decodifica essa camada externa e devolve o H4sI… que esta página lê.
Os dois bytes de cabeçalho do zlib indicam o seu método, a sua janela e o nível com que ele foi escrito, então, com a janela padrão do zlib, o Base64 dele começa de uma de quatro maneiras: eJ no nível padrão, que é o que o zlib.compress do Python escreve a menos que se peça outro, eA ou eF nos níveis abaixo dele, e eN nos níveis acima. O deflate bruto não tem nenhum byte que o identifique, e o Base64 dele começa do jeito que o primeiro bloco dele por acaso começar.
O seletor “Comprimido como” diz de que forma a página lê e escreve esses bytes em texto: em “Base64”, como ela abre, ou em “Hexadecimal”. A escolha vale nas duas direções, e a página nunca a troca por você, porque todo dígito hexadecimal também é um caractere de Base64, então 1f8b0800 é texto nos dois e bytes diferentes em cada um. O Base64 é lido no alfabeto padrão ou no seguro para URL, com ou sem o preenchimento, passando por cima de espaços e quebras de linha, e é escrito com preenchimento, ou seguro para URL e sem preenchimento quando o interruptor “Seguro para URL” está ligado. O hexadecimal é escrito em pares separados por espaços e lido com espaços ou tudo junto, com 0x na frente, ou como um despejo hexadecimal — e 0x é como o T-SQL escreve um valor binário, como o gzip que o COMPRESS() do SQL Server devolve. Quando um hexadecimal lido como Base64 falha, ou um Base64 lido como hexadecimal é recusado em um caractere que o hexadecimal não tem, e, nos dois casos, o texto inteiro é lido do outro jeito, um aviso diz isso ao lado de um botão que troca; nada troca até você clicar nele.
Lendo um fluxo: membros, os bytes depois dele, um fim prematuro
A RFC 1952 faz de um arquivo gzip uma série de membros, fluxos gzip completos um após o outro, cada um com o seu próprio cabeçalho e o seu trailer. O comando cat a.gz b.gz cria um arquivo assim, e o mesmo faz acrescentar dados a um com gzip -c file >> archive.gz, o exemplo do próprio manual do GNU gzip; o gunzip lê todos os membros e junta o que eles contêm. Esta página os lê do mesmo jeito: cada membro é lido e verificado, o que eles contêm aparece unido, e um aviso diz quantos foram lidos. Nem todo programa que lê gzip faz isso. A especificação que o descompressor do próprio navegador segue permite um único membro em um fluxo gzip e trata um segundo como erro, e o zlib.decompress do Python para depois do primeiro sem dizer nada.
Bytes depois do fim que não abrem nenhum membro são pulados em vez de recusados, já que tudo antes deles está completo e verificado, e um aviso diz quantos são, o deslocamento em que começam e se todos são zero. Zeros ali são preenchimento — o manual do GNU gzip os encontra em fita, escritos até o fim de um bloco — e o gunzip passa por cima deles em silêncio; por cima de outros bytes ele passa com um alerta de que o lixo no final foi ignorado, enquanto o gzip.decompress do Python recusa o arquivo. O mesmo vale depois do Adler-32 de um fluxo zlib e depois do último bloco de um fluxo bruto, embora o deflate bruto não traga nenhuma soma de verificação para conferir.
Um fluxo que termina antes do fim, como um texto colado pela metade ou um download interrompido no meio do caminho, aparece até onde vai, com um aviso de que a soma de verificação não foi conferida. Um fluxo danificado é recusado, e nada dele é mostrado. Isso é mais rigoroso que o gunzip, que escreve o que já descomprimiu antes de chegar à soma de verificação que mostra que está errado; mas um bit alterado pode ser decodificado em silêncio por um bom trecho antes que algo o revele, então o que saiu antes de o dano aparecer pode já estar errado. Também não se informa nenhuma posição, já que o lugar onde um decodificador percebe o dano não é o lugar onde o dano está. E um fluxo zlib que pede um dicionário preestabelecido é recusado em uma frase própria: o fluxo identifica os bytes que o compressor dele recebeu de antemão, mas não os traz, então não há com o que lê-lo.
O que sai é escrito como diz “Bytes como”: em “Texto”, como UTF-8, ou em “Hexadecimal”. Muitas vezes é JSON, que o Formatador JSON organiza e verifica, apontando a linha e a coluna onde ele quebra. Uma imagem ou um pacote de arquivos que passou pelo gzip não é texto, então, com “Texto”, cada sequência dos bytes dele que não é UTF-8 aparece como U+FFFD, com um aviso que conta essas sequências ao lado de um botão que mostra os bytes em hexadecimal; “Baixar” salva os próprios bytes em qualquer um dos casos. Uma linha abaixo do resultado mostra o que o cabeçalho do primeiro membro armazena, um nome, uma hora em UTC e um comentário, e o nome armazenado nunca é o nome do arquivo que “Baixar” salva. Uma saída que passa do máximo que a página descomprime de uma vez para ali, com um aviso que informa esse tamanho, e, quando o trailer de um gzip declara um comprimento acima disso, a página diz isso enquanto o trabalho ainda está rodando.
Por que uma entrada pequena cresce, e o que o Base64 acrescenta
A compressão ganha encontrando repetição, e um texto curto tem pouca, enquanto cada invólucro custa bytes próprios: o cabeçalho e o trailer do gzip somam 18 bytes quando o cabeçalho não armazena nada além disso, os do zlib somam 6, e o deflate bruto não tem nenhum, embora o DEFLATE gaste alguns bits para delimitar os seus blocos. Assim, Hello, world!, 13 bytes, vira 33 bytes como gzip, 21 como zlib e 15 como deflate bruto. A linha de tamanhos abaixo de um resultado conta os bytes de cada lado e a variação entre eles, +153,8% para esse gzip, e um aviso explica o motivo quando um resultado cresce. Um texto com bastante repetição, como um JSON ou um log, recupera esse custo muitas vezes: o exemplo com que a página abre, um JSON pequeno, sai com menos da metade do seu tamanho.
Escritos como texto, os bytes custam ainda mais. O Base64 usa quatro caracteres para cada três bytes, um terço a mais, como explica o guia da ferramenta Base64, então esses 33 bytes de gzip viram 44 caracteres; o hexadecimal usa dois dígitos por byte, e a página os escreve em pares separados por espaços, 98 caracteres para os mesmos 33 bytes. A linha de tamanhos também conta esses caracteres, e o Conversor de tamanhos de dados converte qualquer uma das contagens dela em KB ou KiB.
Ao comprimir: sem nível, bytes que não são os do gzip -c, e sem Brotli
Comprimir é trabalho do próprio navegador, por meio do CompressionStream que a especificação Compression define, e essa especificação não oferece nível de compressão, então esta página também não oferece: o que você recebe é o padrão do navegador. O gzip -9 pode sair um pouco menor, e raramente por muito.
Os bytes também não são os que o gzip -c escreve, embora os dois descomprimam para o mesmo texto. Primeiro, os cabeçalhos diferem. O GNU gzip armazena o nome e a hora de um arquivo, a menos que receba -n, e, quando lê de um pipe, uma hora zero; ele marca em um byte do cabeçalho quando o nível foi -9 ou -1; e no décimo byte, que a RFC 1952 reserva ao sistema operacional, ele escreve 03 no Linux, o número do Unix. O navegador recebe apenas os bytes, então nem o nome nem a hora do seu arquivo podem sair junto com o resultado: o Chromium não armazena nome nenhum e grava uma hora zero, que é o que o gzip -n escolhe, e no décimo byte escreve 03 no Linux, enquanto o Chrome no Windows escreve 0a. Depois, os dados comprimidos diferem, já que o compressor do GNU gzip não é o do navegador: em um texto longo, os dois escolhem bytes diferentes no mesmo nível. Uma diferença em relação ao gzip -c, portanto, não significa nada por si só; o que importa é que os dois descomprimam para os mesmos bytes.
O Brotli não está aqui, por enquanto. A especificação Compression o inclui, mas nem todo navegador o escreve ainda, e uma opção que a página só pudesse oferecer onde o navegador a produz seria uma página diferente em cada navegador. O zstd nem está nessa especificação. O que esta página lê e escreve é o DEFLATE nos seus três invólucros, e nada além disso.
Um arquivo entra, um arquivo sai, e nada é enviado
Um arquivo pode ocupar o lugar da caixa de texto em qualquer uma das direções: escolha um, ou largue um sobre a caixa. Os bytes dele são lidos como estão, sem passar por “Comprimido como” nem por “Bytes como”, e aqui um arquivo não tem limite, enquanto a caixa de texto tem um, embora um arquivo muito grande gere um alerta de que o navegador pode não conseguir mantê-lo na memória. Ao descomprimir, “Baixar” dá nome ao resultado como o gunzip faz, o nome do arquivo sem o .gz, com .tgz virando .tar, e também sem .zz ou .deflate, as extensões que esta página escreve para os outros dois invólucros; qualquer outro nome, e tudo o que for colado, sai como bytes.bin. Ao comprimir, ele acrescenta .gz, .zz ou .deflate ao nome do arquivo, sendo .zz a extensão que o pigz usa para o zlib, ou à palavra text, para o que você digitou.
“Usar como entrada” leva um resultado para a caixa e inverte a direção, então uma ida e volta custa um clique, e um resultado que veio de um arquivo, ou que é grande demais para a caixa, vai como arquivo. Seja qual for o caminho, o arquivo é lido nesta aba e o trabalho roda em um worker que a página inicia no seu próprio dispositivo: nem uma carga nem um arquivo são enviados para isso.
O mesmo com o gunzip e o Python
Na linha de comando, o gunzip e o zcat leem gzip como esta página lê, todos os membros incluídos, depois que o base64 -d transforma uma carga de volta em bytes, embora nenhum dos dois leia zlib ou deflate bruto, que ambos rejeitam como não sendo gzip; o gzip -k comprime um arquivo e mantém o original ao lado dele. No Python, o gzip.decompress lê o invólucro do gzip e todos os membros dentro dele, e o zlib.decompress lê qualquer um dos três invólucros, e o wbits diz a ele qual:
# Uma carga: decodificá-la, depois descomprimi-la
printf 'H4sIAAAAAAAAA/NIzcnJ11Eozy/KSVEEAObG5usNAAAA' | base64 -d | gunzip # Hello, world!
# Um arquivo: comprimi-lo mantendo o original, depois exibi-lo de volta
printf 'Hello, world!' > hello.txt
gzip -k hello.txt
zcat hello.txt.gz # Hello, world!
# Dois membros, o segundo acrescentado ao primeiro, lidos como um só
printf 'Hello, ' | gzip > two.gz
printf 'world!' | gzip >> two.gz
zcat two.gz # Hello, world!
# O mesmo em um script: todos os membros, depois um invólucro de cada vez
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!'Com wbits, o valor 31 pede o invólucro do gzip, o valor 15, que é o padrão, pede o do zlib, e o valor -15 pede o deflate bruto, sendo o 15 em cada um a maior janela, enquanto o valor 47 aceita o do gzip ou o do zlib, conforme o começo dos bytes. Mas o zlib.decompress lê um único membro e descarta o resto sem dizer nada, e é por isso que ele devolve só b'Hello, ' dos dois membros acima; o gzip.decompress lê todos. O Python também é mais rigoroso que esta página em dois pontos: o gzip.decompress recusa bytes depois do fim que não sejam zeros, e as duas funções recusam um fluxo que termina antes da hora, enquanto a página mostra o que saiu.
Perguntas frequentes
- Por que o meu resultado comprimido é maior que o texto que eu digitei?
- Porque cada invólucro custa bytes próprios e um texto curto tem pouco para a compressão remover: o gzip acrescenta 18 bytes, o zlib 6, e o DEFLATE ainda marca os seus blocos, então poucas palavras crescem onde um JSON longo encolhe, e a página diz isso em um aviso. O deflate bruto é o menor dos três. Escrito em Base64, qualquer resultado fica ainda um terço mais longo.
- Por que a saída não é igual à do gzip -c?
- Nada garante que vá ser. Um cabeçalho gzip pode conter um nome, uma hora, uma marca do nível e um byte para o sistema operacional, que o GNU gzip e o navegador preenchem de formas diferentes, e os dois são compressores diferentes, então, em um texto mais longo, até os dados entre o cabeçalho e o trailer podem diferir. Dois fluxos gzip do mesmo texto estão ambos certos quando cada um descomprime para ele, então compare o que eles dão ao serem descomprimidos: “Usar como entrada” leva o resultado desta página direto de volta.
- O que significa H4sI no começo de uma carga?
- Que a carga é gzip escrito em Base64: todo fluxo gzip começa com os bytes 1f 8b 08, e esses três bytes são H4sI em Base64. Cole aqui do jeito que está. Uma carga que começa com eJ é muito provavelmente zlib no nível padrão, e uma que não começa com nenhum dos dois pode ser deflate bruto, ou nem estar comprimida, o que a página diz a você.
- O gzip é criptografia?
- Não. A compressão não usa chave nenhuma, então qualquer pessoa com os bytes nas mãos pode descomprimi-los, aqui ou com o gunzip, e cada byte volta. Uma senha dentro de uma carga comprimida com gzip fica tão exposta quanto uma escrita por extenso.
- Minha carga ou meu arquivo são enviados para algum lugar?
- Não. A carga que você cola e o arquivo que você escolhe ou larga sobre a caixa são lidos dentro desta aba, onde um worker que a página inicia faz a compressão e a descompressão, e “Baixar” monta o seu arquivo a partir do resultado ali mesmo. Nenhum dos dois é enviado para este site nem para mais ninguém.
Ferramentas relacionadas
- Formatador JSON
Valida e embeleza JSON, com localizações de erro claras.
- Conversor de tamanhos de dados
Converta entre KB, MB, GB e KiB, MiB, GiB.
- Base64
Codifica e decodifica Base64 — suporte completo a UTF-8.
- Base32
Codifica e decodifica Base32 e base32hex — texto em UTF-8 ou bytes em hexadecimal.