Unicode, UTF-8 e UTF-16
Este artigo é a tradução e adaptação de uma resposta dada no Stackoverflow, discorrendo sobre os sistemas de codificação Unicode, UTF-8 e UTF-16, cobrindo essa questão de uma forma bastante abrangente e esclarecedora. Sua publicação é feita nos termos da licença CC BY-SA 3.0.
Por que precisamos do Unicode?
Nos (nem tão) velhos tempos, tudo que existia era o ASCII. Estaria tudo bem, se todo o necessário para a comunicação fossem apenas alguns caracteres de controle, pontuação, números e letras como os desta sentença. Infelizmente, o mundo estranho de hoje, da intercomunicação global e da midia social, não foi previsto, e não é incomum ver português, العربية, 汉语, עִבְרִית, ελληνικά e ភាសាខ្មែ no mesmo documento (espero não ter quebrado nenhum browser antigo).
Mas apenas para argumentar, digamos que o Zé Arruela seja um desenvolvedor de software. Ele insiste que não precisará nada além do português e, portanto, quer usar apenas ASCII. Isso pode estar bem para Zé, o usuário, mas não é bom para Zé, o desenvolvedor de software. Aproximadamente metade do mundo usa caracteres não-latinos e o uso do ASCII iria sem dúvida nenhuma excluir essas pessoas e, acima de tudo, ele estaria fechando as portas de uma poderosa economia em crescimento para o seu software.
Dessa forma, tornou-se necessária a criação de um conjunto mais abrangente de caracteres. Foi assim que surgiu o Unicode. Ele atribui a cada caracter um número único, denominado ponto de código (code point). Uma vantagem do Unicode sobre outros conjuntos possíveis é que os primeiros 256 pontos de código são idênticos ao padrão ISO-8859-1 e, portanto, iguais ao ASCII. Além disso, a vasta maioria dos caracteres usados comumente são representáveis usando apenas dois bytes, numa região chamada Plano Multilingual Básico (BMP - Basic Multilingual Plane). Precisamos então de uma codificação de caracteres para acessar esse conjunto. Neste artigo, vamos nos concentrar nas codificações UTF-8 e UTF-16.
Considerações quanto à memória
Pois bem, quantos bytes dão acesso a quais caracteres nessas codificações?
-
UTF-8:
-
1 byte: ASCII padrão
-
2 bytes: árabe, hebraico, maioria da escrita europeia (excluindo principalmente o georgiano)
-
3 bytes: BMP
-
4 bytes: todos os caracteres Unicode
-
-
UTF-16:
-
2 bytes: BMP
-
4 bytes: todos os caracteres Unicode
-
É bom observar desde já que os caracteres que não estão no BMP incluem escritas antigas, símbolos matemáticos, símbolos musicais, e caracteres raros do chinês/japonês/coreano (CJK - Chinese/Japanese/Korean).
Se você trabalhar a maior parte do tempo com caracteres ASCII, então o UTF-8 será com certeza mais eficiente em termos de ocupação de memória. Todavia, caso trabalhe majoritariamente com escritas não-europeias, o uso do UTF-8 poderá ser cerca de 1,5 vezes menos eficiente que o UTF-16 no uso de memória. Ao lidar com grandes quantidades de texto como, por exemplo, grandes páginas da Web ou documentos extensos, isto poderá impactar o desempenho.
Fundamentos de Codificação
-
UTF-8: Para os caracteres do ASCII padrão (0-127), os códigos UTF-8 são idênticos. Isso o torna ideal se for preciso compatibilidade retroativa com texto ASCII já existente. Outros caracteres vão requerer sempre de 2 a 4 bytes. Isso é feito reservando-se alguns bits em cada um desses bytes para indicar que ele faz parte de um caracter multi-byte. O primeiro bit de cada byte, em particular, é 1 para evitar conflito com os caracteres ASCII.
-
UTF-16: Para caracteres BMP válidos, a representação UTF-16 é simplesmente seu ponto de código. Entretando, para os caracteres não-BMP o UTF-16 introduz os pares substitutos (surrogate pairs). Neste caso, a combinação de duas partes dos bytes-duplos mapeiam para um caracter não-BMP. Estas duas partes estão dentro do limite numérico do BMP, mas o padrão Unicode garante que sejam inválidos como caracteres BMP. Ademais, uma vez que o UTF-16 possui dois bytes como unidade básica, ele é afetado por extremidade. Para compensar, um byte para marca de ordenação pode ser colocado no começo do fluxo de dados indicando extremidade (endianness). Assim, se estiver sendo lido UTF-16 na entrada, e nenhuma extremidade for especificada, isso deverá ser verificado.
Como pode ser observado, o UTF-8 e o UTF-16 não são de forma alguma compatíveis um com o outro. Dessa forma, se estiver sendo feita I/O, deve-se verificar qual codificação está sendo usada! Para maiores detalhes sobre essas codificações, consulte a FAQ UTF.
Considerações práticas para programação
Tipos de dados Character
e String
Como eles são codificados na linguagem de programação? Se não forem bytes simples, no instante em que se tentar enviar para a saída caracteres não-ASCII, pode-se encontrar alguns problemas. Além disso, mesmo que o tipo do caracter seja baseado num UTF, isso não significa que as strings estejam codificadas no UTF apropriado. Elas podem permitir sequências de bytes ilegais. Geralmente, é preciso usar uma biblioteca que suporte UTF, como a ICU para C, C++ e Java. Em todo o caso, quando se pretender efetuar entrada/saída de qualquer coisa além da codificação padrão, é preciso fazer uma conversão prévia.
Codificações recomendada/padrão/dominante
Quando se tiver oportunidade de escolher qual UTF usar, é melhor normalmente seguir os padrões recomendados para o ambiente no qual se está trabalhando. Por exemplo, o UTF-8 é dominante na Web, e desde o HTML5, tem sido a codificação recomendada.Por outro lado, tanto os ambientes .NET quanto Java são baseados no tipo de caracter UTF-16. De uma forma confusa (e incorretamente), são feitas frequentes referências à "codificação Unicode", referindo-se normalmente à codificação UTF dominante num dado ambiente.
Suporte das bibliotecas
Quais codificações são suportadas pelas bibliotecas que estão sendo usadas? Elas suportam os casos limítrofes? Uma vez que a necessidade é a mãe da inventividade, as bibliotecas UTF-8 geralmente suportam adequadamente caracteres de 4 bytes, uma vez que caracteres de 1, 2 e 3 bytes podem ocorrer com frequência. Entretando, nem todas as bibliotecas do UTF-16 suportam pares substitutos (surrogate pairs), uma vez que eles ocorrem muito raramente.
Caracteres de contagem
Existem caracteres de combinação no Unicode. Por exemplo, o ponto de código U+006E (n), e o U+0303 (um til de combinação), formam n˜, mas o ponto de código U+00F1 forma ñ. Eles deveriam parecer idênticos, mas um simples algorítimo de contagem retornará 2 no primeiro exemplo e 1 no último. Isso não está necesssariamente incorreto, mas também pode não ser o resultado esperado.
Comparação de igualdade
A, А, e Α parecem a mesma coisa, mas são respectivamente Latino, Cirílico e Grego. Podemos ter também casos como C e Ⅽ, um é uma letra, o outro, um numeral romano. Além disso, temos que levar em consideração os caracteres de combinação. Para maiores informações, consulte caracteres Duplicados no Unicode.
Pares substitutos (Surrogate Pairs)
Essa é uma questão que também aparece com frequência, assim fornecerei apenas alguns links de exemplos: