Aproveite o mês das
carreiras na Alura

Até 44% OFF

Falta pouco!

00

DIAS

00

HORAS

00

MIN

00

SEG

Entendendo Unicode e os Character Encodings

Alura
peas
peas

Compartilhe

Todo mundo já passou por problemas com character encodings. Quem nunca abriu uma conexão JDBC com o MySQL e puxou do banco um monte de caracteres onde em vez de acentos só se viam pontos de interrogação e caracteres estranhos?

O blog do Joel Spolsky já publicou um post sobre esse assunto, que é bem simples e direto. O fato importante é mostar que Unicode não é um encoding. Unicode define codepoints (um número) para cada letra (ou símbolo). Por exemplo, a letra ´A´ é o codepoint 65. A partir do Unicode 3.1, o codepoint pode até mesmo ser maior que 2^16: o fatídico 65536 (atualmente vai até 16*65536, ou seja 0x10FFFF). Sim! Unicode nada mais é que um tabelão! Nas palavras do wikipedia, "... (Unicode) assign a unique number to each character used in the written languages of the world", traduzindo, Unicode associa um número único para cada caractere usado nas línguas escritas de todo o mundo.

Pois é, unicode não é uma maneira de se representar caracteres com 2 bytes. Aliás, Unicode não é um encoding. A pergunta "Você está usando unicode ou latin1?" está completamente errada. Quem é responsável por codificar um codepoint em bytes é o encoding. Aqui estamos falando de UTF-8, o ISO-8859-1 (vulgo latin1), entre outros. Alguns encodings podem não suportar todos os codepoints possíveis, outros podem tentar economizar alguns bytes quando codificar alguns caracteres (caso do UTF-8).

Banner da Imersão de IA da Alura com Google Gemini. Participe de aulas gratuitas online com certificado. Domine as inovações mais recentes da IA.

Mas não quero ficar na teoria, quero passar para o código. Vamos então codificar o 'ç' em diferentes encodings: ISO-8859-1, UTF-8 e UTF-16. Basta colocar o seguinte código no main:

 String\[\] codes = { "ISO-8859-1", "UTF-8", "UTF-16" }; String palavra = "ç";
for (String encoding : codes) { byte\[\] b = palavra.getBytes(encoding); System.out.printf("%10s\\t%d\\t", encoding, b.length); for (int k = 0; k < b.length; k++) { String hex = Integer.toHexString((b\[k\] + 256) % 256); if (hex.length() == 1) hex = "0" + hex; System.out.print(hex); } System.out.println(); } 

E ao rodar, teremos em cada linha o encoding, a quantidade de bytes utilizada para codificar o 'ç' e sua representação em hexadecimal codificada.

ISO-8859-1 1 e7 UTF-8 2 c3a7 UTF-16 4 feff00e7

Vamos tentar o mesmo para a letra 'a':

ISO-8859-1 1 61 UTF-8 1 61 UTF-16 4 feff0061

É interessante reparar aqui que o UTF-8 gastou 2 bytes em um caso, e 1 byte no outro. Outro fato importante é que UTF-8 codifica diversos caracteres da mesma forma que o ISO-8859-1 (e este por sua vez tem uma estrita relação com o US-ASCII). Você pode incrementar esse código e testar com outros encodings, tais como US-ASCII, Cp1252 e UTF-16. Você pode ver quais encodings a sua JVM suporta com Charset.availableCharsets().

Como falei anteriormente, existem caracteres que extrapolam o índice do 65535. Então como ficam esses caracteres no java, já que o char tem apenas 2 bytes? É aí que entram os surrogate pairs: alguns caracteres agora são utilizados para indicar que o restante do caractere ainda está por vir! Alguns problemas surgem com isso: o método length() da String não funciona mais tão bem: ele apenas diz quantos chars aquela String possui.

Para resolver esses problemas novos métodos e classes foram adicionados ao java 5 (através da JSR-204), como o codePointCount, na String. Esse artigo da Sun discute bem esse assunto.

Mas quando ocorrem os problemas de encoding que citamos no começo do post? Um caso em potencial é quando tentamos ler uma sequência de bytes usando um encoding que não foi o que utilizamos para codificar aquela String. Vamos simular isso escrevendo o 'ç' em UTF-8 e lendo como ISO-8859-1, e vice-versa:

 // ç escrito em UTF-8 mas lido em ISO-8859-1 System.out.println(new String("ç".getBytes("UTF-8"), "ISO-8859-1")); // ç escrito em ISO-8859-1 mas lido em UTF-8 System.out.println(new String("ç".getBytes("ISO-8859-1"), "UTF-8")); 
``` E o resultado:
`ç
?` 
Cadê o c cedilha? Você pode não ver, mas ele está por aí! Esses caracteres lhe trazem algumas lembranças?

Veja outros artigos sobre Programação