Gzip

Pega gzip, zlib o deflate crudo en Base64 para leer lo que contiene (los bytes dicen cuál), o comprime un texto en uno de los tres, en tu navegador.

Entrada
o suelta uno aquí: se lee en local y nunca se sube
Salida
[
  {
    "name": "Lucía",
    "city": "Madrid"
  },
  {
    "name": "Mateo",
    "city": "Ciudad de México"
  },
  {
    "name": "Sofía",
    "city": "Buenos Aires"
  },
  {
    "name": "Hugo",
    "city": "Barcelona"
  },
  {
    "name": "Valentina",
    "city": "Bogotá"
  },
  {
    "name": "Martín",
    "city": "Lima"
  },
  {
    "name": "Camila",
    "city": "Santiago"
  },
  {
    "name": "Diego",
    "city": "Sevilla"
  }
]

Leído como gzip.

Bytes sin comprimir
440
Bytes comprimidos
184
Variación
-58,2%
Caracteres de Base64
248

Una compresión en tres envoltorios, y la palabra deflate

gzip, zlib y deflate crudo son una sola compresión, DEFLATE, envuelta de tres maneras. El RFC 1951 define DEFLATE en sí: los bytes se cortan en bloques, y cada uno se escribe como códigos que representan un byte o una secuencia copiada de más atrás. El envoltorio de gzip, el RFC 1952, pone una cabecera de al menos diez bytes delante y ocho bytes detrás, un CRC-32 de los bytes originales y su longitud; el de zlib, el RFC 1950, pone dos bytes delante y un Adler-32 detrás; el deflate crudo es la compresión sin ningún envoltorio. Los datos comprimidos de dentro pueden ser los mismos en los tres, así que el envoltorio es toda la diferencia.

La palabra deflate es donde los nombres se enredan. Para HTTP, Content-Encoding: deflate significa el envoltorio de zlib, y el RFC 9110 señala que algunos servidores envían deflate crudo con ese nombre de todos modos. El compresor del propio navegador sigue a HTTP: al envoltorio de zlib lo llama deflate, y al deflate crudo lo llama deflate-raw. El DeflateStream de .NET y el gzdeflate de PHP escriben deflate crudo, mientras que el gzcompress de PHP y el zlib.compress de Python escriben el envoltorio de zlib. Así que unos bytes etiquetados como deflate pueden ser cualquiera de los dos, y solo los bytes pueden decir cuál.

Por eso la página lee el envoltorio a partir de los bytes cuando descomprime, y dice cuál leyó debajo de lo que salió: «Leído como gzip», «Leído como zlib» o «Leído como deflate crudo». Los bytes lo deciden: gzip siempre empieza por 1f 8b, los dos primeros bytes de zlib, leídos juntos como un solo número, son un múltiplo de 31, y ningún codificador empieza un deflate crudo con ninguno de los dos. Un archivo con la extensión .gz que contiene zlib se lee como zlib, con un aviso de que el nombre y los bytes no coinciden, y los bytes que no están en ninguno de los tres se rechazan, porque no hay nada que descomprimir. Al comprimir, eliges tú el envoltorio, y es gzip hasta que elijas otro.

De dónde vienen las cargas, y qué son H4sI y eJ

Los bytes comprimidos le llegan a un desarrollador de unas pocas formas: como el cuerpo de una respuesta HTTP cuyo Content-Encoding nombra gzip o deflate, como un archivo, o como texto, escrito en Base64 o en hexadecimal. Todo flujo gzip empieza por los mismos tres bytes, 1f 8b 08 — sus dos bytes de firma y el número de su método de compresión, que es DEFLATE —, y tres bytes son exactamente cuatro caracteres de Base64, así que el gzip escrito en Base64 empieza siempre por H4sI. Eso es lo que una suscripción de CloudWatch Logs le entrega a una función de Lambda o a un flujo de Kinesis en los datos de cada registro, y así guarda Helm un release: su JSON comprimido con gzip y luego escrito en Base64. Cuando Helm guarda un release en un Secret de Kubernetes, leerlo directamente del Secret da Base64 dentro de Base64, porque un Secret guarda sus datos en un Base64 propio; la herramienta Base64 decodifica esa capa exterior y la convierte en el H4sI… que lee esta página.

Los dos bytes de cabecera de zlib nombran su método, su ventana y el nivel con que se escribió, así que, con la ventana por defecto de zlib, su Base64 empieza de una de cuatro maneras: eJ con el nivel por defecto, que es lo que escribe el zlib.compress de Python salvo que se le pida otra cosa, eA o eF con los niveles por debajo, y eN con los de por encima. El deflate crudo no tiene ninguna firma, y su Base64 empieza como dé la casualidad que empiece su primer bloque.

«Comprimido como» dice cómo lee y escribe la página esos bytes como texto: en Base64, como viene la página por defecto, o en hexadecimal. Vale para las dos direcciones y la página nunca lo cambia por ti, porque cada dígito hexadecimal es también un carácter de Base64, así que 1f8b0800 es texto en los dos y bytes distintos en cada uno. El Base64 se lee en el alfabeto estándar o en el seguro para URL, con o sin su relleno, a través de espacios y saltos de línea, y se escribe con relleno, o seguro para URL y sin relleno cuando el interruptor «Seguro para URL» está activado. El hexadecimal se escribe en pares separados por espacios y se lee separado o todo seguido, con 0x delante, o como volcado hexadecimal — y 0x es como T-SQL escribe un valor binario, como el gzip que devuelve el COMPRESS() de SQL Server. Cuando el hexadecimal leído como Base64 falla, o el Base64 leído como hexadecimal se rechaza en un carácter que el hexadecimal no tiene, y en los dos casos todo el texto se lee de la otra manera, un aviso lo dice junto a un botón que cambia; nada cambia hasta que lo pulsas.

Leer un flujo: miembros, los bytes que siguen, un final prematuro

El RFC 1952 hace de un archivo gzip una serie de miembros: flujos gzip completos, uno tras otro, cada uno con su propia cabecera y su propia cola. cat a.gz b.gz crea un archivo así, y también añadirle algo con gzip -c file >> archive.gz, el ejemplo del propio manual de GNU gzip; gunzip lee todos los miembros y une lo que contienen. Esta página los lee del mismo modo: cada miembro se lee y se comprueba, lo que contienen se muestra unido, y un aviso dice cuántos se leyeron. No todos los programas hacen esto. El estándar que sigue el descompresor del propio navegador permite un solo miembro en un flujo gzip y trata un segundo como un error, y el zlib.decompress de Python se detiene tras el primero sin decir nada.

Los bytes que siguen al final y no abren ningún miembro se pasan por alto en lugar de rechazarse, porque todo lo anterior está completo y comprobado, y un aviso dice cuántos son, el desplazamiento en que empiezan y si todos son cero. Los ceros ahí son relleno — el manual de GNU gzip se los encuentra en cinta, escritos hasta el final de un bloque — y gunzip los pasa por alto sin decir nada; los demás bytes los pasa por alto con una advertencia de que se ignoró basura al final, mientras que el gzip.decompress de Python rechaza el archivo. Lo mismo vale tras el Adler-32 de un flujo zlib, y tras el último bloque de un flujo crudo, aunque el deflate crudo no lleva ninguna suma de comprobación que verificar.

Un flujo que termina antes de su final, como un texto pegado incompleto o una descarga detenida a medias, se muestra hasta donde llega, con un aviso de que la suma de comprobación no se verificó. Un flujo dañado se rechaza, y no se muestra nada de él. Eso es más estricto que gunzip, que escribe lo que ha descomprimido antes de llegar a la suma de comprobación que lo encuentra incorrecto; pero un bit cambiado puede decodificarse sin que nada lo note durante un trecho, así que lo que salió antes de que se notara el daño puede estar ya mal. Tampoco se da ninguna posición, porque donde un decodificador nota el daño no es donde está el daño. Y un flujo zlib que pide un diccionario predefinido se rechaza con una frase propia: el flujo identifica los bytes con los que se preparó su compresor y no los trae, así que no hay nada con lo que leerlo.

Lo que sale se escribe según diga «Bytes como»: como texto en UTF-8 o en hexadecimal. A menudo es JSON, que el Formateador JSON embellece y valida, señalando la línea y la columna donde se rompe. Una imagen o un paquete de archivos comprimidos con gzip no son texto, así que con «Texto» cada secuencia de sus bytes que no es UTF-8 se muestra como U+FFFD, con un aviso que cuenta esas secuencias junto a un botón que muestra los bytes en hexadecimal; «Descargar» guarda los bytes mismos en cualquier caso. Una línea debajo del resultado muestra lo que guarda la cabecera del primer miembro, un nombre, una hora en UTC y un comentario, y el nombre guardado nunca es el nombre del archivo que guarda «Descargar». Una salida que pasa de lo máximo que la página descomprime de una vez se detiene ahí, con un aviso que nombra ese tamaño, y cuando la cola de un gzip indica una longitud mayor, la página lo dice mientras el trabajo está en marcha.

Por qué una entrada pequeña crece, y qué añade Base64

La compresión gana encontrando repeticiones, y un texto corto tiene pocas, mientras que cada envoltorio cuesta bytes propios: la cabecera y la cola de gzip suman 18 bytes cuando la cabecera no guarda nada más, las de zlib 6, y el deflate crudo no tiene ninguna, aunque DEFLATE gasta unos pocos bits en marcar sus bloques. Así, Hello, world!, 13 bytes, se convierte en 33 bytes como gzip, 21 como zlib y 15 como deflate crudo. La línea de tamaños debajo de un resultado cuenta los bytes de cada lado y la variación entre ellos, +153,8% para ese gzip, y un aviso dice por qué cuando un resultado crece. Un texto con muchas repeticiones, como un JSON o un archivo de registro, recupera el coste muchas veces: el ejemplo con el que se abre la página, un JSON pequeño, sale con menos de la mitad de su tamaño.

Escritos como texto, los bytes cuestan más todavía. Base64 usa cuatro caracteres por cada tres bytes, un tercio más, como explica la guía de la herramienta Base64, así que esos 33 bytes de gzip son 44 caracteres; el hexadecimal usa dos dígitos por byte, y la página los escribe en pares separados por espacios, 98 caracteres para los mismos 33 bytes. La línea de tamaños cuenta también esos caracteres, y el Conversor de tamaños de datos convierte cualquiera de sus recuentos a KB o KiB.

Comprimir: sin nivel de compresión, no los bytes de gzip -c, y sin Brotli

Comprimir es trabajo del propio navegador, mediante el CompressionStream que define el estándar Compression, y ese estándar no ofrece ningún nivel de compresión, así que esta página tampoco: lo que obtienes es el nivel por defecto del navegador. gzip -9 puede salir un poco más pequeño, y rara vez por mucho.

Tampoco son los bytes los que escribe gzip -c, aunque los dos se descompriman en el mismo texto. Primero difieren las cabeceras. GNU gzip guarda el nombre y la hora de un archivo salvo que se le indique -n, y desde una tubería deja la hora a cero; anota en un byte de la cabecera si se le indicó -9 o -1; y en el décimo byte, que el RFC 1952 asigna al sistema operativo, escribe 03 en Linux, el número de Unix. Al navegador se le dan solo los bytes, así que ni el nombre de tu archivo ni su hora pueden salir con el resultado: Chromium no guarda ningún nombre y deja la hora a cero, que es lo que elige gzip -n, y en el décimo byte escribe 03 en Linux, donde Chrome en Windows escribe 0a. Después difieren los datos comprimidos, porque el compresor de GNU gzip no es el del navegador: en un texto largo, los dos eligen bytes distintos con el mismo nivel. Una discrepancia con gzip -c, por tanto, no significa nada por sí sola; lo que cuenta es que los dos se descompriman en los mismos bytes.

Brotli no está aquí, por ahora. El estándar Compression lo nombra, pero todavía no todos los navegadores lo escriben, y una opción que la página solo pudiera ofrecer donde el navegador la admite sería una página distinta en navegadores distintos. zstd ni siquiera está en ese estándar. Lo que esta página lee y escribe es DEFLATE en sus tres envoltorios, y nada más.

Un archivo entra, un archivo sale, y nada se sube

Un archivo puede ocupar el lugar del cuadro de texto en las dos direcciones: elige uno, o suéltalo sobre el cuadro. Sus bytes se leen tal como son, sin que intervengan «Comprimido como» ni «Bytes como», y un archivo no tiene aquí ningún límite donde el cuadro de texto sí lo tiene, aunque un archivo muy grande provoca una advertencia de que puede que el navegador no lo soporte. Al descomprimir, «Descargar» le pone al resultado el nombre que le pondría gunzip, el del archivo sin .gz, con .tgz convertido en .tar, y también sin .zz o .deflate, las extensiones que esta página escribe para los otros dos envoltorios; cualquier otro nombre, y todo lo pegado, sale como bytes.bin. Al comprimir, añade .gz, .zz o .deflate al nombre del archivo — .zz es el nombre que pigz da a zlib — o a la palabra text para lo que escribiste.

«Usar como entrada» lleva un resultado al cuadro y cambia la dirección, así que un viaje de ida y vuelta cuesta un clic, y un resultado que vino de un archivo, o que es demasiado grande para el cuadro, pasa como archivo. Vaya en la dirección que vaya, el archivo se lee en esta pestaña y el trabajo se hace en un worker que la página inicia en tu propio dispositivo: ni una carga ni un archivo se suben para hacerlo.

Lo mismo con gunzip y Python

En una línea de comandos, gunzip y zcat leen gzip como lo hace esta página, todos los miembros incluidos, una vez que base64 -d ha devuelto una carga a bytes, aunque ninguno de los dos lee zlib ni deflate crudo, que los dos rechazan por no ser gzip; gzip -k comprime un archivo y conserva el original a su lado. En Python, gzip.decompress lee el envoltorio de gzip y todos los miembros que contiene, y zlib.decompress lee cualquiera de los tres envoltorios, según le indique su wbits:

# Una carga: decodificarla y luego descomprimirla
printf 'H4sIAAAAAAAAA/NIzcnJ11Eozy/KSVEEAObG5usNAAAA' | base64 -d | gunzip   # Hello, world!

# Un archivo: comprimirlo conservando el original y luego volver a mostrarlo
printf 'Hello, world!' > hello.txt
gzip -k hello.txt
zcat hello.txt.gz                                  # Hello, world!

# Dos miembros, el segundo añadido tras el primero, leídos de nuevo como uno
printf 'Hello, ' | gzip > two.gz
printf 'world!' | gzip >> two.gz
zcat two.gz                                        # Hello, world!

# Lo mismo en un script: todos los miembros y luego un envoltorio 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!'

Con wbits, 31 pide el envoltorio de gzip, 15, el valor por defecto, el de zlib, y -15 el deflate crudo — el 15 de cada uno es la ventana más grande —, mientras que 47 acepta el de gzip o el de zlib, según con cuál empiecen los bytes. Eso sí, zlib.decompress lee un solo miembro y descarta el resto sin decir nada, y por eso, de los dos miembros de arriba, solo devuelve b'Hello, '; gzip.decompress los lee todos. Python es además más estricto que esta página por partida doble: gzip.decompress rechaza los bytes tras el final que no son ceros, y las dos funciones rechazan un flujo que termina antes de tiempo, donde la página muestra lo que salió.

Preguntas frecuentes

¿Por qué mi resultado comprimido es más grande que lo que escribí?
Porque cada envoltorio cuesta bytes propios y un texto corto tiene poco que la compresión pueda quitar: gzip añade 18 bytes, zlib 6, y DEFLATE marca además sus bloques, así que unas pocas palabras crecen donde un JSON largo se encoge, y la página lo dice en un aviso. El deflate crudo es el más pequeño de los tres. Escrito en Base64, cualquier resultado es un tercio más largo todavía.
¿Por qué la salida no coincide con la de gzip -c?
Nada promete que vaya a coincidir. Una cabecera gzip puede llevar un nombre, una hora, una marca del nivel y un byte para el sistema operativo, que GNU gzip y el navegador rellenan de forma distinta, y los dos son compresores distintos, así que en un texto más largo hasta los datos entre la cabecera y la cola pueden diferir. Dos flujos gzip de un mismo texto son correctos los dos cuando cada uno se descomprime en él, así que compara aquello en lo que se descomprimen: «Usar como entrada» manda el resultado de esta página directamente en la dirección contraria.
¿Qué significa H4sI al principio de una carga?
Que la carga es gzip escrito en Base64: todo flujo gzip empieza por los bytes 1f 8b 08, y esos tres bytes son H4sI en Base64. Pégala aquí tal como está. Una carga que empieza por eJ es muy probablemente zlib con su nivel por defecto, y una que no empieza por ninguno de los dos puede ser deflate crudo, o no estar comprimida en absoluto, y la página te lo dice.
¿Es gzip un cifrado?
No. La compresión no usa ninguna clave, así que cualquiera que tenga los bytes puede descomprimirlos, aquí o con gunzip, y recupera cada byte. Una contraseña dentro de una carga comprimida con gzip está tan expuesta como una escrita tal cual.
¿Se sube mi carga o mi archivo a algún sitio?
No. La carga que pegas y el archivo que eliges o sueltas se leen los dos dentro de esta pestaña, donde un worker que inicia la página hace la compresión y la descompresión, y «Descargar» construye su archivo a partir del resultado ahí mismo. Ninguno de los dos se envía a este sitio ni a nadie más.

Herramientas relacionadas

  • Formateador JSON

    Valida y embellece JSON, con ubicaciones de error claras.

  • Conversor de tamaños de datos

    Convierte entre KB, MB, GB y KiB, MiB, GiB.

  • Base64

    Codifica y decodifica Base64, con soporte UTF-8 completo.

  • Base32

    Codifica y decodifica Base32 y base32hex — texto en UTF-8 o bytes en hexadecimal.