📡
Serialização, endianness, UTF-8: os bytes que viajam
⏱ 11 min de leitura·+55 XP
Dados precisam ser convertidos para bytes para viajar pela rede ou ser armazenados. Serialização parece trivial até você debugar uma corrupção de dados entre sistemas com endianness diferente, ou descobrir que um encoding errado transformou "olá" em "ol\xc3\xa1" no banco.
Unicode e encodings: separar conceito de representação
# Unicode: code points abstratos
s = "olá mundo"
print(len(s)) # 9 caracteres (code points)
print(s[2]) # "á" — código U+00E1
# UTF-8: encoding de comprimento variável (1-4 bytes por code point)
b_utf8 = s.encode("utf-8")
print(b_utf8) # b'olá mundo' — "á" = 2 bytes (0xC3 0xA1)
print(len(b_utf8)) # 10 bytes (9 chars + 1 extra byte para "á")
# Latin-1 (ISO-8859-1): 1 byte por char, apenas 256 caracteres
b_latin1 = s.encode("latin-1")
print(b_latin1) # b'olá mundo' — "á" = 1 byte (0xE1)
print(len(b_latin1)) # 9 bytes
# UTF-16: 2 bytes por char (BOM indica endianness)
b_utf16 = s.encode("utf-16")
print(b_utf16[:4]) # b'ÿþ' = BOM little-endian + primeiros bytes
# Detectar encoding:
import chardet
resultado = chardet.detect(b_utf8)
print(resultado) # {'encoding': 'utf-8', 'confidence': 0.99}
# Unicode normalization: mesma string, bytes diferentes!
# "á" pode ser: U+00E1 (precomposta) ou U+0061 + U+0301 (a + combining accent)
import unicodedata
s1 = "á" # "á" precomposta
s2 = "á" # "a" + acento combinando
print(s1 == s2) # False! mesma aparência, bytes diferentes
print(s1 == unicodedata.normalize("NFC", s2)) # True (normaliza para NFC)Endianness: a ordem dos bytes
import struct
# Big-endian (network byte order) vs little-endian
numero = 0x12345678
# Big-endian: byte mais significativo primeiro
be = struct.pack(">I", numero) # > = big-endian, I = unsigned int 4 bytes
print(be.hex()) # 12345678
# Little-endian: byte menos significativo primeiro
le = struct.pack("<I", numero) # < = little-endian
print(le.hex()) # 78563412 ← invertido!
# x86/ARM são little-endian:
import sys
print(sys.byteorder) # 'little'
# Rede TCP/IP usa big-endian (network byte order):
import socket
# Converter int para network byte order:
print(socket.htons(0x1234).to_bytes(2, 'big').hex()) # 1234 (já big-endian)
# struct.pack: pacotes binários para protocolo de rede
# ">HHI" = big-endian, unsigned short, unsigned short, unsigned int
header = struct.pack(">HHI", 0x0001, 0x0002, 0x12345678)
print(header.hex()) # 000100021234567
# Desempacotar:
version, msg_type, length = struct.unpack(">HHI", header)
print(version, msg_type, length) # 1 2 305419896Formatos de serialização: comparação real
| Formato | Tipo | Tamanho relativo | Parse speed | Schema? | Legível? |
|---|---|---|---|---|---|
| JSON | Texto | 100% (baseline) | Lento | Não | Sim |
| XML | Texto | 150-300% | Muito lento | XSD opcional | Sim |
| MessagePack | Binário | ~50% | Rápido | Não | Não |
| CBOR | Binário | ~50% | Rápido | Não | Não |
| Protobuf | Binário | ~30% | Muito rápido | Sim (.proto) | Não |
| Avro | Binário | ~30% | Muito rápido | Sim (JSON schema) | Não |
| Parquet | Binário colunar | ~10-20% | Muito rápido | Sim | Não |
import json, struct, time
dados = {"id": 12345, "nome": "Fernando", "ativo": True, "score": 98.7}
# JSON: texto legível mas parsing lento
json_bytes = json.dumps(dados).encode()
print(f"JSON: {len(json_bytes)} bytes") # ~50 bytes
# struct: binário fixo, sem nomes de campo
# ">IH?f" = big-endian: int(4), short(2), bool(1), float(4) = 11 bytes
binario = struct.pack(">IH?f", 12345, len("Fernando"), True, 98.7)
print(f"struct: {len(binario)} bytes") # 11 bytes — 5x menor
# msgpack: binário flexível com nomes de campo
# pip install msgpack
import msgpack
mp_bytes = msgpack.packb(dados)
print(f"msgpack: {len(mp_bytes)} bytes") # ~30 bytes — sem overhead de texto
# Protobuf (pip install protobuf + arquivo .proto):
# message Dados { int32 id = 1; string nome = 2; bool ativo = 3; float score = 4; }
# pb_bytes = Dados(id=12345, nome="Fernando", ativo=True, score=98.7).SerializeToString()
# ~20 bytes — mais compacto ainda✅
Decisão prática: JSON para APIs públicas e debugging (legibilidade > performance). MessagePack/CBOR para cache e APIs internas (binário sem schema). Protobuf/gRPC para microserviços de alta frequência (schema + performance). Parquet/Arrow para analytics e data pipelines (colunar = compressão máxima). Sempre use UTF-8 para texto — é o padrão universal.
💡
Próximo: Tempo distribuído — NTP, clock skew, e por que nunca usar wall clock para medir duração.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo