Entendendo Unicode e os Character Encodings
Postado em 22. out, 2006 por Paulo Silveira em Java
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).
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?
15 Respostas para “Entendendo Unicode e os Character Encodings”
Trackbacks/Pingbacks
-
-
fevereiro 12, 2011
[...] Ótima leitura sobre o tema aqui: http://blog.caelum.com.br/entendendo-unicode-e-os-character-encodings/ [...]
ASSINE NOSSO RSS




Fernando Boaglio
24. out, 2006
Outro artigo interessante sobre esse assunto:
USING CHARSETS AND ENCODINGS
http://java.sun.com/developer/JDCTechTips/2003/tt0110.html#1
Rangel Viotti
02. dez, 2006
Gostei muito de sua explicação. Mas agora eu te faço a pergunta que não quer calar:
Qual deles eu, como brasileiro e desejando que as pessoas leiam as palavras por inteiro, devo usar???
Abraços.
Eliezer Reis
15. fev, 2007
O que eu entendi é que não adianta você pegar uma String escrita em UTF-8 e tentar ler ela como ISO ou vice-versa. O certo é definir um charset e usá-lo do início ao fim sem excessões. Desde seus arquivos .html, .jsp, .css, .js, .java, solicitações ajax, no banco de dados e o que mais você utilizar.
Exemplificando:
A situação do ç só daria correto se você tentar algo assim:
System.out.println(new String(“ç”.getBytes(“UTF-8″), “UTF-8″));
ou
System.out.println(new String(“ç”.getBytes(“ISO-8859-1″), “ISO-8859-1″));
Eu já tive muita dor de cabeça com uma situação onde acontecia o seguinte. O usuário entrava com os dados em um textarea no meu formulário web que estava configurado com o charset UTF-8. Quando ele enviava era o ajax que “submetia” logo o XmlHttpRequest utiliza o padrao UTF-8. Infelizmente quando pegava o requisição o meu servlet fazia o decode automático (penso eu) e convertia para ISO-8859-1, isso porque o servlet estava configurado para pegar o padrão do sistema operacional entao a situação era a seguinte.
System.out.println(new String(“ç”.getBytes(“UTF-8″), “ISO-8859-1″));
Resultado = ç;
Não consegui perceber onde estava errado porque quando dava um request.getParameter(“”) o decode rolava automático e me descabelei todo.
Portanto, Nunca misture as coisas trabalhe com um charset do ínicio ao fim. Prefira o UTF-8 porque é uma tentativa de padronizar tudo e como é feita pelo W3C você tem a garantia de que empresas como Microsoft, Sun, e demais o utilizam (ou pelo menos é o que se espera).
Eliezer Reis
15. fev, 2007
Outro motivo para utilizar UTF-8 é que ele aceita muitos mais caracters que o ISO-8859-1.
Exemplo:
O caracter &rdquo que é parecido com o " (aspas duplas) aparece como ? no ISO-8859-1.
Gabriel Corrêa de Oliveira
16. mai, 2007
Acabo de descobrir que quando executamos aplicações de linha de comando no Windows o encode usado pelo prompt do DOS é o Cp850.
Assim, a única maneira de apresentar corretamente caracteres com acento é usar:
OutputStreamWriter o = new OutputStreamWriter(System.out, “Cp850″); PrintWriter pw = new PrintWriter(o, true);
pw.println(“àáçãé”);
Alberto Matias
22. abr, 2008
so estudante universitario quero estar atualizado em codigo
Alberto Matias
22. abr, 2008
O Unicode tornou-se o esquema predominante para o processamento interno de texto, e por vezes também para armazenamento
Jomello
03. nov, 2008
Muito bom esse artigo, mas tenho uma duvida.
Estou utilizando utf-8 em tudo .html,xml,servlet,css e banco Postgresql.
Só que quando mando algo via ajax pelo Firefox tudo funciona maravilhosamente, mas quando mando pelo nosso querido IE eis que ele manda sei lá o que.
A minha pergunta é, como faço para saber qual charset foi enviado?
Abraços
Jean Landim
12. dez, 2009
De fato o UTF-8 é melhor que o latin-1.
Paulo Silveira
19. dez, 2009
pra quem quiser algo mais prático para resolver os problemas, tem o famoso post do luca:
http://www.guj.com.br/posts/list/12456.java
Paulo Silveira
08. mar, 2010
Legal ver o método DataInputStream.readUTF. Como ler um unicode, se acabamos de discutir que não há arquivos em “formato Unicode” . Na verdade ele usa algo parecido, mas nao igual, ao UTF-8: http://java.sun.com/j2se/1.4.2/docs/api/java/io/DataInputStream.html
Cleyson Lago
26. mai, 2011
Já tive muitos problemas com Character Encodings no php, já que ele trabalha nativamente com ISO-8859-1 , mas conseguindo intender como funciona essas codificações facilita um pouco.
Ótimo artigo
Adriano
29. mai, 2011
recomendo quem for testar o codigo do exemplo testar tamben com essa lista de Charset
String[] codes = Charset.availableCharsets().keySet().toArray(new String[0]);
existe encodings de até 72bits.
Anonimo
19. dez, 2011
Como eliminar este problema???
Como verificar qual/quais encodes estou a usar na minha aplicação???
Java + mysql + ZK + Spring