Cada caractere que você vê em uma tela — cada letra, dígito, emoji e símbolo — é armazenado na memória como uma sequência de números. As regras que mapeiam esses números para caracteres legíveis por humanos são chamadas de codificações de caracteres. Um mau entendimento ou configuração incorreta da codificação de caracteres é uma das fontes mais comuns de corrupção de dados, texto ilegível (mojibake) e vulnerabilidades de segurança sutis em aplicações web. Este guia percorre a história, a mecânica e as implicações práticas das codificações que impulsionam a web moderna.

ASCII: Onde Tudo Começou

O American Standard Code for Information Interchange (ASCII) foi publicado em 1963. Ele atribui valores numéricos a 128 caracteres: o alfabeto inglês (maiúsculas e minúsculas), os dígitos de 0 a 9, pontuação e um punhado de caracteres de controle como nova linha (0x0A) e tabulação (0x09).

Exemplos ASCII
Caractere   Decimal   Hex    Binário
A           65        0x41   01000001
Z           90        0x5A   01011010
a           97        0x61   01100001
0           48        0x30   00110000
Espaço      32        0x20   00100000

Como o ASCII usa apenas 7 bits, ele pode representar no máximo 128 caracteres. Isso era suficiente para a computação americana em meados do século XX, mas não deixava espaço para letras acentuadas (é, ñ, ü), scripts não latinos (chinês, árabe, cirílico) ou os milhares de símbolos usados em todo o mundo.

A Era das Páginas de Código & Seus Problemas

Para contornar as limitações do ASCII, os fabricantes criaram páginas de código — codificações estendidas de 8 bits que usavam os 128 valores superiores (128–255) para caracteres específicos de cada região. Por exemplo:

  • ISO 8859-1 (Latin-1) — Idiomas da Europa Ocidental
  • ISO 8859-5 — Alfabetos cirílicos
  • Windows-1252 — Superset do Latin-1 da Microsoft
  • Shift_JIS — Texto japonês

O problema fundamental era que o valor de byte 0xE9 significava é em Latin-1, mas щ em ISO 8859-5. Se um documento codificado em uma página de código fosse aberto com outra, o resultado era mojibake — texto ilegível e corrompido. Não havia um esquema universal que pudesse lidar com todos os scripts simultaneamente.

Unicode: Um Conjunto de Caracteres para Governar Todos Eles

O Unicode Consortium propôs-se a resolver isso atribuindo um code point único a cada caractere em todos os sistemas de escrita. Um code point é escrito na forma U+XXXX, onde XXXX é um número hexadecimal. A partir do Unicode 16.0, existem mais de 154.000 caracteres cobrindo 168 scripts.

Code Points Unicode
Caractere   Code Point   Descrição
A           U+0041       Latin Capital Letter A
é           U+00E9       Latin Small Letter E with Acute
中          U+4E2D       CJK Unified Ideograph (Meio)
😀          U+1F600      Grinning Face Emoji
∞           U+221E       Símbolo de Infinito

É crucial entender que Unicode não é uma codificação — é um conjunto de caracteres. Ele define qual número corresponde a qual caractere, mas não diz nada sobre como esses números são armazenados como bytes. Essa tarefa cabe a formas de codificação como UTF-8, UTF-16 e UTF-32.

UTF-8: A Codificação Dominante da Web

UTF-8 (Unicode Transformation Format — 8-bit) é uma codificação de largura variável que usa de um a quatro bytes por caractere. Foi projetada por Ken Thompson e Rob Pike em 1992 e tornou-se a codificação de fato da internet — mais de 98% das páginas web usam UTF-8.

Como Funcionam as Sequências de Bytes UTF-8

O UTF-8 codifica os code points em sequências de bytes de acordo com estas regras:

Regras de Codificação UTF-8
Intervalo de Code Point   Bytes   Padrão de Byte
U+0000  – U+007F       1       0xxxxxxx
U+0080  – U+07FF       2       110xxxxx 10xxxxxx
U+0800  – U+FFFF       3       1110xxxx 10xxxxxx 10xxxxxx
U+10000 – U+10FFFF     4       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Os bits iniciais do primeiro byte indicam quantos bytes há na sequência. Bytes de continuação sempre começam com 10, tornando o UTF-8 auto-sincronizável — você pode pular para o meio de um fluxo e encontrar o próximo limite de caractere válido.

Vantagens Chave do UTF-8

  • Compatível com ASCII — qualquer documento ASCII válido também é UTF-8 válido
  • Sem problemas de ordem de bytes — ao contrário de UTF-16/32, a ordem de bytes é inequívoca
  • Eficiente em espaço para texto predominantemente latino (1 byte por caractere ASCII)
  • Auto-sincronizável — fácil de encontrar limites de caracteres em um fluxo de bytes

UTF-16 & o BOM

O UTF-16 usa dois ou quatro bytes por caractere. Caracteres no Plano Multilíngue Básico (BMP, U+0000 a U+07FF) usam dois bytes, enquanto caracteres suplementares usam um par substituto — duas unidades de código de 16 bits.

Como o UTF-16 armazena dados em unidades de 16 bits, a ordem dos bytes é importante. Um sistema usando ordem de bytes big-endian armazena o byte mais significativo primeiro; little-endian o armazena por último. Para sinalizar qual ordem está sendo usada, os arquivos podem começar com uma Marca de Ordem de Byte (BOM):

Marcas de Ordem de Byte
Codificação         Bytes da BOM
UTF-8            EF BB BF  (opcional, muitas vezes desencorajado)
UTF-16 BE        FE FF
UTF-16 LE        FF FE
UTF-32 BE        00 00 FE FF
UTF-32 LE        FF FE 00 00

Em UTF-8, a BOM é tecnicamente válida, mas desencorajada. Ela pode causar bugs invisíveis — por exemplo, um arquivo PHP que começa com uma BOM UTF-8 emitirá três bytes de lixo antes de qualquer conteúdo pretendido, potencialmente quebrando cabeçalhos HTTP ou respostas JSON.

Mojibake: Quando as Codificações Colidem

Mojibake (文字化け, literalmente "transformação de caracteres") é a saída corrompida que aparece quando texto codificado em um esquema é decodificado com outro. Exemplos comuns incluem:

  • é (U+00E9) codificado em UTF-8 torna-se os bytes C3 A9. Se decodificado como Latin-1, você vê é em vez disso.
  • Texto japonês codificado em Shift_JIS, mas decodificado como UTF-8, produz sequências de caracteres de substituição ().
  • Um banco de dados armazenando dados UTF-8 em uma coluna Latin-1 corrompe silenciosamente caracteres multibyte.

Como Evitar Mojibake

  1. Declare a codificação explicitamente — sempre inclua <meta charset="UTF-8"> no <head> do seu HTML.
  2. Defina cabeçalhos HTTP — envie Content-Type: text/html; charset=utf-8.
  3. Configure seu banco de dados — use utf8mb4 no MySQL (não o utf8 legado, que suporta apenas sequências de 3 bytes e quebra emojis).
  4. Salve arquivos de origem como UTF-8 sem BOM — configure seu editor de acordo.
  5. Codifique caracteres não ASCII em URLs — o esquema de percent-encoding converte bytes como C3 A9 em %C3%A9.

Codificação de Caracteres no Desenvolvimento Web

Problemas de codificação podem surgir em todas as camadas de uma aplicação web. Aqui está onde prestar atenção:

HTML & a Viewport

Declaração de Charset HTML
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <title>Minha Página</title>
</head>

A tag <meta charset> deve aparecer nos primeiros 1024 bytes do documento. Os navegadores a utilizam para determinar como decodificar o restante da página. Se estiver ausente ou incorreta, o navegador recorre a heurísticas que frequentemente erram.

Manipulação de Strings em JavaScript

Strings JavaScript são internamente codificadas como UTF-16. Isso significa que caracteres fora do BMP (como emojis) são representados como pares substitutos e têm um .length de 2, não 1:

Peculiaridade do UTF-16 em JavaScript
const emoji = '😀';
console.log(emoji.length);       // 2 (par substituto)
console.log([...emoji].length);  // 1 (iterador é consciente do code-point)

// Maneira segura de contar caracteres:
const charCount = [...str].length;

URLs & Percent Encoding

URLs só podem conter um conjunto limitado de caracteres ASCII. Qualquer caractere fora desse conjunto — espaços, letras acentuadas, caracteres CJK — deve ser percent-encoded. O processo converte cada byte da representação UTF-8 para o formato %HH:

Percent Encoding de URL
Original:  café
Bytes UTF-8 de 'é': C3 A9
Codificado:   caf%C3%A9

Original:  olá mundo
Codificado:   ol%C3%A1%20mundo

Codificar URLs corretamente é essencial para evitar links quebrados, ataques de injeção e problemas de interoperabilidade. Use funções integradas como encodeURIComponent() em JavaScript ou urllib.parse.quote() em Python, em vez de criar lógica de codificação manualmente.

Bancos de Dados

Sempre certifique-se de que a codificação da sua conexão com o banco de dados, tabela e colunas correspondam. No MySQL, a configuração recomendada para suporte completo a Unicode é:

Configuração MySQL UTF-8
CREATE DATABASE meuapp
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

-- A string de conexão também deve especificar o charset:
-- mysql://usuario:senha@host/meuapp?charset=utf8mb4

Codificação & Segurança

A codificação de caracteres também é uma preocupação de segurança. Atacantes exploraram ambiguidades de codificação para contornar a validação de entrada:

  • Codificação dupla — codificar uma string já codificada (%253C decodifica para %3C, que decodifica para <) pode contornar regras de WAF (Web Application Firewall).
  • Sequências UTF-8 excessivamente longas — representar um caractere com mais bytes do que o necessário (por exemplo, codificar / como C0 AF) foi usado para contornar filtros de travessia de diretório em sistemas mais antigos.
  • Ataques de homóglifos — caracteres visualmente idênticos de scripts diferentes (por exemplo, "a" latino vs. "а" cirílico) podem falsificar nomes de domínio ou nomes de usuário.

Decodificadores UTF-8 modernos rejeitam sequências excessivamente longas, mas você ainda deve validar e normalizar a entrada usando funções como Normalizer::normalize() em PHP ou str.normalize() em Python.

Folha de Referência Rápida

Comparação de Codificações
Característica       ASCII    UTF-8         UTF-16        UTF-32
Máx. Caracteres   128      1.112.064     1.112.064     1.112.064
Bytes/Caractere   1        1–4           2 ou 4        4
Compatível com ASCII Sim      Sim           Não           Não
Problema de Ordem de Byte Não       Não           Sim (BOM)     Sim (BOM)
Uso na Web        Legado   ~98%          Raro          Muito Raro

Conclusão

A codificação de caracteres é um daqueles tópicos fundamentais que todo desenvolvedor encontra, mas poucos dedicam tempo para entender completamente. Os principais pontos a serem lembrados são simples: use UTF-8 em todos os lugares, declare explicitamente sua codificação em todas as camadas (HTML, HTTP, banco de dados) e nunca presuma que um byte equivale a um caractere. Ao trabalhar com URLs, sempre aplique percent-encoding a caracteres não ASCII corretamente para evitar links quebrados e vulnerabilidades de segurança.

Codifique & Decodifique com Confiança

Precisa aplicar percent-encoding a caracteres especiais em URLs ou depurar problemas de codificação? Use nossas ferramentas online gratuitas para codificação e decodificação instantâneas e confiáveis.