Sanyanggae LogoSanyanggae

💻 Web & Front

Qual é a diferença entre Unicode e UTF-8? Texto coreano ilegível, bytes, comprimento e normalização

Compare pontos de código, bytes UTF-8 e comprimento de strings JavaScript com sílabas coreanas e emojis. Separe decodificação incorreta, fontes e normalização, preservando o original.

사냥개 (Sanyanggae)Publicado Atualizado 17 min de leitura

Este artigo é uma tradução do original em coreano. Original em coreano · Revisão da tradução: Revisão por IA

Diagrama explicativo em três quadros ligados: caracteres e pontos de código, bytes UTF-8, exibição e normalização.
Diagrama próprio que distingue números de caracteres Unicode, bytes UTF-8 armazenados e regras de exibição e comparação. Não representa uma falha real nem resultados de medição.

1. O número do caractere não é a sequência de bytes

Imagine exportar um documento coreano e abri-lo novamente, encontrando símbolos estranhos no título. O idioma de quem escreveu não mudou: talvez as regras para ler os números armazenados não coincidam. Unicode atribui números, chamados pontos de código, a caracteres e símbolos de diversas escritas. UTF-8 é uma codificação que representa os valores correspondentes como sequências de bytes. Por exemplo, a sílaba coreana ‘가’ tem o ponto de código U+AC00. A notação após U+ é hexadecimal; não indica uma posição no arquivo nem um endereço de memória. Esse número também não determina o desenho da fonte ou os pixels da tela. O mesmo ponto de código pode ser desenhado de maneiras diferentes conforme a fonte e a composição do texto. Suporte a Unicode, armazenamento UTF-8 e instalação de fontes coreanas são relacionados, mas precisam ser verificados separadamente. A explicação de UTF pelo Unicode ajuda a separar caracteres e formato de armazenamento.

Em UTF-8, ‘가’ corresponde aos três bytes hexadecimais EA B0 80. A letra latina A tem o ponto de código U+0041 e ocupa um único byte, 41. Um byte contém oito bits: mesmo no mesmo documento, caracteres podem ocupar quantidades diferentes de bytes. A extensão .txt ou .csv também não revela sozinha a codificação real. UTF-8 representa valores escalares Unicode válidos com um a quatro bytes. Portanto, não é possível calcular exatamente o tamanho do arquivo apenas multiplicando a quantidade de caracteres visíveis. O exemplo dessa sílaba não se aplica indiscriminadamente a todos os sinais coreanos ou idiomas. Codificação transforma informação textual em bytes; não é compressão nem criptografia. Informações adicionais do formato, quebras de linha e compressão durante a transmissão podem alterar o tamanho total da mesma frase. Distinguir a transformação entre bytes e caracteres definida pelo Encoding Standard do WHATWG do tamanho global ou da confidencialidade ajuda a delimitar o problema.

2. Bytes, unidades de código e caracteres percebidos

A propriedade length de uma string JavaScript conta unidades de código UTF-16. Não representa os bytes de um arquivo UTF-8 nem necessariamente o número de caracteres percebidos por uma pessoa. O rosto sorridente ‘😀’ é um ponto de código, U+1F600, mas usa duas unidades UTF-16: '😀'.length resulta em 2. A representação UTF-8 F0 9F 98 80 ocupa quatro bytes. O número decimal 128512 obtido por codePointAt(0) é o mesmo número que U+1F600, não uma contagem de caracteres ou um tamanho de arquivo. ‘가’ equivale, conforme essas três medidas, a um ponto de código, uma unidade UTF-16 e três bytes UTF-8. Resultados diferentes para a mesma string decorrem de unidades diferentes. A documentação MDN sobre o comprimento de strings especifica a unidade de length. Esse valor também não permite determinar sozinho quantos bytes reais um mecanismo JavaScript ocupa na memória.

Contar pontos de código individualmente também nem sempre corresponde à quantidade de caracteres visíveis. Um caractere-base pode receber marcas combinantes, e vários pontos podem compor um único emoji. As regras Unicode de segmentação descrevem limites de agrupamentos de grafemas estendidos para tratar essas combinações. Mesmo Array.from(text), que percorre uma string por pontos de código, não conta automaticamente esses grupos como unidades. No navegador, após verificar o suporte, pode-se considerar Intl.Segmenter com granularity definida como 'grapheme'. Um agrupamento de grafemas, porém, não corresponde obrigatoriamente a um único desenho da fonte, e as expectativas de edição variam entre idiomas. Um campo de apresentação precisa de uma contagem compreensível; armazenamento e comunicação podem exigir também limites em bytes. Se a interface anuncia “20 caracteres”, mas corta apenas unidades UTF-16, pode dividir um emoji ou um caractere combinado no meio.

3. Separar armazenamento, transmissão e exibição

O texto pode ficar ilegível quando a codificação usada na gravação difere da decodificação aplicada na leitura. Interpretar um arquivo UTF-8 com outra codificação pode mostrar caracteres errados mesmo sem alteração do arquivo. Em HTML, compare o formato realmente armazenado, o charset de Content-Type na resposta HTTP e a declaração meta charset no início do documento. O atributo HTML lang='ko' informa o idioma do conteúdo; seu papel é diferente do charset que orienta a interpretação dos bytes. A orientação do W3C sobre declarações de codificação explica que não basta mudar a declaração: o arquivo também deve ser realmente salvo em UTF-8, com informações coerentes no servidor e no documento. Marcas de codificação também podem afetar a prioridade da identificação; uma declaração meta não supera sempre todas as outras. Se o resultado muda entre abrir diretamente e receber pelo servidor, a etapa de transmissão fornece uma pista importante.

Quadrados exibidos porque uma fonte não contém os glifos necessários podem ter uma causa diferente da decodificação incorreta. Se os dados estão certos e falta apenas um desenho adequado, mudar a codificação de gravação não resolve. Da mesma forma, trocar a fonte não restaura o caractere original se ele já foi interpretado como outro. Preserve uma cópia do arquivo problemático e compare, em sequência, a codificação selecionada no editor, as declarações da resposta, os caracteres interpretados pelo navegador e a fonte usada. Não conclua apenas pela aparência: verifique também os pontos de código presentes ao copiar o mesmo trecho. Registre se o problema ocorre somente em um navegador ou se outros editores também leem o arquivo incorretamente. Salvar repetidas vezes até algo parecer normal pode eliminar os bytes originais. Se todas as declarações estão corretas, mas apenas certas publicações apresentam o problema, investigue também em que etapa o conteúdo mudou antes de entrar no banco de dados.

4. Conferir valores com TextEncoder e TextDecoder

Comece um experimento simples com uma string curta criada para a tarefa. TextEncoder transforma uma string JavaScript em um array de bytes UTF-8. O resultado de new TextEncoder().encode('가') é EA B0 80 em hexadecimal, e a length do array é 3. Ferramentas de desenvolvimento podem mostrar os mesmos valores decimais 234, 176, 128: não são dados diferentes, mas outra base numérica. Compare isso ao comprimento comum 1 da string para perceber as unidades. new TextDecoder('utf-8').decode(bytes) devolve ‘가’, sendo bytes o array criado anteriormente. O codificador gera UTF-8; não é um conversor universal que permite selecionar qualquer codificação antiga para a saída. Ao investigar um arquivo real, use os bytes originais preservados. Recodificar em UTF-8 um texto já lido incorretamente na tela não prova quais bytes existiam no primeiro arquivo.

Passar 'utf-8' e { fatal: true } ao construtor TextDecoder permite tratar entradas inválidas como erro em vez de substituí-las silenciosamente. Por exemplo, o byte FF isolado não é uma entrada UTF-8 válida, e sua decodificação falha nesse modo. No modo habitual de substituição, o caractere U+FFFD pode aparecer no trecho inválido. A ausência de erro, porém, não significa que a codificação original ou a correção do conteúdo foram identificadas. Há sequências de bytes aceitas em várias codificações, e um texto já alterado pode ter sido salvo novamente como UTF-8 válido. O teste verifica se o decodificador escolhido aceita a entrada. Ele não confirma automaticamente a origem do documento, sua correspondência ao original ou a inexistência de alterações no percurso.

5. Normalização alinha representações equivalentes

Mesmo duas strings lidas corretamente podem conter sequências internas diferentes de pontos de código. ‘가’ pode ser representado por U+AC00 ou pela sequência do jamo inicial combinante U+1100 e do vocálico U+1161. Os dois últimos não são simplesmente os jamos de compatibilidade usuais ‘ㄱ’ e ‘ㅏ’ colocados lado a lado: confira também os números. As formas normalmente aparecem como a mesma sílaba, mas podem não ser iguais em uma comparação bruta de strings JavaScript. Aplicar NFC aos dois lados conforme as regras Unicode de normalização permite comparar sob a mesma representação esse exemplo de equivalência canônica. NFD usa decomposição canônica. Aqui, a forma NFC ocupa três bytes UTF-8 e os dois jamos da forma NFD seis; a mesma aparência não garante o mesmo tamanho armazenado. Esse detalhe pode explicar por que uma pesquisa e um nome salvo visualmente idênticos não coincidem. É diferente de texto corrompido pela interpretação errada de bytes.

A forma de normalização depende da finalidade. NFC trata equivalência canônica; NFKC inclui decomposição de compatibilidade e pode alterar mais amplamente representações como dígitos de largura completa ou dentro de círculos. Isso pode ser útil ao gerar valores de comparação para buscas, mas diferenças assim podem ter significado em matemática ou documentos originais. Convém preservar o original e definir qual forma aplicar a uma cópia destinada à comparação. Normalização não substitui comparação de maiúsculas e minúsculas, ordenação de cada idioma ou busca por palavras de pronúncia igual. Converter em massa nomes de arquivos ou identificadores de contas pode fazer itens distintos convergirem ao mesmo valor; examine antes as regras de nomes e o tratamento de colisões do serviço. Guardar todos os sinais visualmente semelhantes com o mesmo valor não é o objetivo geral da normalização.

6. Preservar o original e registrar as transformações

Antes de sobrescrever o arquivo inicial, preserve seus bytes e registre como ele foi obtido. Experimente a decodificação esperada em uma cópia e confira se o conteúdo legível permanece após uma nova gravação. A marca inicial UTF-8 apresentada na explicação do W3C sobre BOM é EF BB BF e não é obrigatória. UTF-8 não exige escolher uma ordem de bytes, portanto essa marca não tem o mesmo papel da escolha de ordem em UTF-16. Adicionar ou remover um BOM também é diferente de converter os bytes do corpo para outra codificação. Se valores ilegíveis já foram substituídos por caracteres de substituição ou interrogações e salvos, vários valores originais podem ter se fundido; não se pode garantir recuperação automática. Só conhecendo a codificação inicial e o histórico de conversões é possível avaliar uma reversão. Normalização não recupera bytes perdidos.

Não encerre a verificação de uma função multilíngue com uma única linha em inglês. Prepare frases curtas com sílabas coreanas, letras latinas com marcas combinantes, um emoji de plano suplementar e escritas da direita para a esquerda, como árabe ou hebraico; compare entrada, gravação, recuperação e exportação. Em cada etapa, confira aparência, pontos de código, bytes UTF-8 e o critério de contagem definido pelo produto. A direção da leitura não se resolve trocando UTF-8 por outra codificação: idioma e direção da interface precisam de verificação separada. Ao alterar limites de entrada ou comparação de pesquisas, preserve os textos existentes e explique o novo critério. O contador do navegador e o limite aceito no servidor devem usar unidades coerentes; se forem necessários limites diferentes, apresente cada um. Os números e exemplos de código deste artigo ilustram conversões de strings curtas, não medições de uma falha específica de serviço. Conferir número do caractere, bytes, unidades da string, exibição e comparação ajuda a reproduzir o que mudou em cada fronteira.

Fontes e referências

Este artigo explica informações de fontes públicas. A citação de uma instituição não implica revisão nem aprovação por ela.

Editora e contato

Outros artigos sobre o tema