Pular para o conteúdo principal

Bases e bytes

· 7 min para ler
Leandro Andrade
Leandro Andrade
Software Developer

base16, base62, base64, url-safe, UTF-8, 1 byte, 4 bytes. São conceitos que aparecem em encurtadores de URL, geração de identificadores, cabeçalhos HTTP e no dimensionamento de colunas do banco de dados — e que costumam ser usados sem que a diferença entre caractere e byte esteja clara.

O que significa baseX​

A regra é direta: em uma baseX, apenas X caracteres distintos são permitidos para representar um valor. O conjunto desses caracteres é chamado de alfabeto da base.

BaseCaracteresAlfabeto
base220, 1
base880 – 7
base16160 – 9, A – F
base62620 – 9, a – z, A – Z
base64640 – 9, a – z, A – Z, +, /
base64 url-safe640 – 9, a – z, A – Z, -, _

Quanto maior a base, menos caracteres são necessários para representar o mesmo valor:

decimal 255 → base2: 11111111 (8 caracteres)
→ base8: 377 (3 caracteres)
→ base16: FF (2 caracteres)

base16: o formato de depuração​

Cada byte vira exatamente dois caracteres hexadecimais, o que torna a representação previsível e fácil de inspecionar:

Buffer.from('usuario').toString('hex');
// '7573756172696f' → 7 bytes viram 14 caracteres

É o formato de hashes (sha256), UUIDs e dumps binários. O custo é dobrar o tamanho da representação.

base64: transportar binário como texto​

O base64 agrupa os bits de 3 bytes (24 bits) e os reparte em 4 blocos de 6 bits. Como 2⁶ = 64, cada bloco vira um caractere do alfabeto:

texto: S o l
bytes: 01010011 01101111 01101100
grupos: 010100 110110 111101 101100
base64: U 2 9 s
Buffer.from('usuario').toString('base64');
// 'dXN1YXJpbw==' → 7 bytes viram 12 caracteres

Duas consequências práticas:

  • O tamanho cresce cerca de 33%: a cada 3 bytes de entrada, saem 4 caracteres;
  • O = é preenchimento (padding): entra quando a entrada não é múltiplo de 3 bytes, apenas para completar o último bloco de 4.
base64 não é segurança

base64 é codificação, não criptografia nem compressão. Qualquer pessoa decodifica o valor com um comando. É por isso que o payload de um JWT, mesmo parecendo ilegível, pode ser lido por qualquer um — o que o protege é a assinatura, não a codificação.

base64 url-safe: por que existe​

No base64 tradicional, dois caracteres do alfabeto têm significado especial em uma URL:

  • + é interpretado como espaço em query strings;
  • / é o separador de caminho.

A variante url-safe (RFC 4648, seção 5) resolve o problema trocando + por - e / por _, e normalmente omitindo o padding =:

Buffer.from('usuario').toString('base64url');
// 'dXN1YXJpbw' → sem '=' e sem caracteres problemáticos

É a codificação usada em JWT, tokens de redefinição de senha e qualquer valor que trafegue em URL ou nome de arquivo.

base62: identificadores curtos e legíveis​

O base62 é o base64 sem +, / e sem padding — ou seja, apenas letras e números. Ele não precisa de escape em URL, não quebra ao ser copiado de um e-mail e não gera ambiguidade em um double-click.

Por isso é a escolha clássica de encurtadores de URL:

7 caracteres em base62 = 62⁷ = 3.521.614.606.208 combinações

Mais de três trilhões de identificadores em apenas 7 caracteres — motivo pelo qual https://bit.ly/aiksAi7 cabe em 22 bytes.

Escolhendo a base
  • Precisa inspecionar ou comparar visualmente? base16.
  • Precisa transportar binário em um campo de texto? base64.
  • O valor vai para uma URL, cabeçalho ou nome de arquivo? base64url ou base62.

Bytes: nem todo caractere ocupa o mesmo espaço​

Aqui está a confusão mais comum: caractere não é byte. Em UTF-8 — a codificação padrão da web e da maioria dos bancos de dados modernos — o espaço ocupado é variável.

BytesO que cabeExemplos
1Caracteres ASCII: 0 – 9, a – z, A – Z e pontuação básica (/, -, _, :, .)a, 7, -
2Letras acentuadas, cedilha, alfabetos grego, cirílico, árabe e hebraicoç, á, ñ, ж
3Ideogramas CJK (chinês, japonês, coreano) e a maioria dos símbolos日, 本, €
4Emojis e caracteres suplementares😀, 𝄞
Correção comum

É frequente ouvir que japonês e chinês ocupam 2 bytes — isso vale para UTF-16, não para UTF-8. Em UTF-8, ideogramas CJK ocupam 3 bytes. Quem ocupa 2 bytes são os acentuados latinos e alfabetos como o cirílico.

Contando na prática​

7 bytes: u s u a r i o
| | | | | | |
1 1 1 1 1 1 1

9 bytes: e n d e r e ç o
| | | | | | | |
1 1 1 1 1 1 2 1

O ç sozinho explica a diferença entre os dois valores abaixo:

ValorCaracteresBytes
usuario77
catalogo de produtos web2424
catálogo de produtos web2425
https://bit.ly/aiksAi72222
https://www.lojaexemplo.com.br/departamento-de-vendas/produtos/...100100

Em JavaScript, o length da string não responde essa pergunta:

const campo = 'endereço';

campo.length; // 8 → unidades de código UTF-16
Buffer.byteLength(campo, 'utf8'); // 9 → bytes reais em UTF-8

const emoji = '😀';
emoji.length; // 2 → um par substituto (surrogate pair)
Buffer.byteLength(emoji, 'utf8'); // 4 → bytes reais em UTF-8
[...emoji].length; // 1 → caracteres percebidos
Três contagens diferentes

String.prototype.length conta unidades UTF-16, o spread conta pontos de código e Buffer.byteLength conta bytes. Validar um limite de tamanho com length deixa passar valores que estouram o limite real de armazenamento ou de transmissão.

Por que isso importa​

Dimensionamento de colunas no banco​

VARCHAR(255) significa coisas diferentes conforme o banco e o charset:

  • PostgreSQL: VARCHAR(255) são 255 caracteres, independentemente de quantos bytes cada um ocupa;
  • MySQL com utf8mb4: VARCHAR(255) também são 255 caracteres, mas o armazenamento pode chegar a 1.020 bytes — e é o limite em bytes que conta para o índice.
Limite de índice

No MySQL/InnoDB, o limite de uma chave de índice é de 3.072 bytes. Com utf8mb4 (4 bytes por caractere no pior caso), uma coluna VARCHAR(768) já ocupa todo o espaço disponível. Criar um índice em uma coluna maior falha — ou é silenciosamente truncado, dependendo da configuração.

Limites de protocolo são medidos em bytes​

Praticamente todo limite de infraestrutura é expresso em bytes, nunca em caracteres:

LimiteValor típico
Cookie (por cookie)4.096 bytes
Cabeçalhos HTTP (nginx, por padrão)8 KB
URL (limite prático em navegadores)~2.000 caracteres
Documento no MongoDB16 MB
Mensagem no Kafka (padrão)1 MB

Um JWT com claims em português acentuado ocupa mais espaço do que a contagem de caracteres sugere — e o base64url ainda acrescenta os ~33% de overhead sobre o total em bytes.

Truncar texto é mais delicado do que parece​

Cortar uma string por bytes pode partir um caractere ao meio, produzindo o caractere de substituição �:

const texto = 'preço';

// errado: corta no meio do caractere multibyte
Buffer.from(texto).subarray(0, 4).toString('utf8'); // 'pre�'

// correto: opera sobre caracteres, não bytes
[...texto].slice(0, 4).join(''); // 'preç'

O mesmo cuidado vale para emojis, cujo corte incorreto quebra o par substituto e gera um caractere inválido.

Conclusão​

Base e byte respondem perguntas diferentes: a base define quais caracteres podem representar um valor e, portanto, quantos caracteres serão necessários; o byte define quanto espaço cada caractere realmente ocupa.

Na prática, três pontos evitam a maior parte dos problemas:

  • Escolha a base pelo destino do valor — base16 para inspeção, base64 para transporte, base64url e base62 para URLs;
  • Lembre que base64 cresce cerca de 33% e não oferece nenhuma proteção — é codificação, não criptografia;
  • Meça limites em bytes, não em caracteres: um único ç já quebra a conta, e um emoji vale por quatro.