
Este artigo explora os conceitos básicos por trás da codificação de caracteres e, em seguida, mergulha nos detalhes técnicos dos sistemas de codificação.
Se você possui apenas um conhecimento básico de codificação de caracteres e deseja entender melhor o essencial, as diferenças entre os sistemas de codificação, por que às vezes acabamos com texto sem sentido e os princípios por trás de uma arquitetura de sistema de codificação diferente, continue lendo.
Conhecer a codificação de caracteres em detalhes requer uma leitura extensa e uma boa parte do tempo. Tentei poupar um pouco desse esforço reunindo tudo em um só lugar, fornecendo o que acredito ser um background bastante completo do tópico.
Vou examinar como as codificações de byte único (ASCII, Windows-1251 etc.) funcionam, a história de como o Unicode surgiu, as codificações baseadas em Unicode UTF-8, UTF-16 e como elas diferem, o recursos específicos, compatibilidade e falta dela entre várias codificações, princípios de codificação de caracteres e um guia prático de como os caracteres são codificados e decodificados.
Embora a codificação de caracteres possa não ser um tópico de vanguarda, é útil entender como funciona agora e como funcionou no passado sem gastar muito tempo.
História do unicode
Eu acho que é melhor começar nossa história a partir do momento em que os computadores não eram nem de longe tão avançados nem tão comuns em nossas vidas como agora. Os desenvolvedores e engenheiros que tentavam criar padrões na época não tinham idéia de que os computadores e a Internet seriam tão populares e difundidos quanto eles. Quando isso aconteceu, o mundo precisava de codificações de caracteres.
Mas como você pode ter um computador armazenando caracteres ou letras quando ele apenas entende caracteres e zeros? Fora dessa necessidade, surgiu a primeira codificação ASCII de 1 byte, que embora não necessariamente a primeira codificação, foi a mais utilizada e estabeleceu a referência. Portanto, é um bom padrão para usar.
Mas o que é ASCII? O código ASCII consiste em 8 bits. Alguma aritmética fácil mostra que esse conjunto de caracteres contém 256 símbolos (oito bits, zeros e uns 2⁸ = 256).
Os primeiros 7 bits - 128 símbolos (2⁷ = 128) do conjunto foram usados para letras latinas, caracteres de controle (como quebras de linha rígida, tabulações etc.) e símbolos gramaticais. Os outros bits eram para idiomas nacionais. Dessa forma, os primeiros 128 caracteres são sempre os mesmos e, se você quiser codificar seu idioma nativo, sirva-se dos símbolos restantes.
Isso deu origem a uma panóplia de codificações nacionais. Você acaba com uma situação como esta: digamos que você está na Rússia criando um arquivo de texto que, por padrão, usa o Windows-1251 (a codificação russa usada no Windows). E você envia seu documento para alguém fora da Rússia, digamos nos EUA. Mesmo se o destinatário souber russo, eles ficarão sem sorte quando abrirem o documento em seu computador (com software de processamento de texto usando ASCII como código padrão) porque verão caracteres bizarros e ilegíveis (mojibake) em vez de letras russas . Mais precisamente, as letras em inglês aparecerão muito bem, porque os primeiros 128 símbolos no Windows-1251 e ASCII são idênticos, mas onde houver texto em russo, o software de processamento de texto do destinatário usará a codificação errada, a menos que o usuário tenha definido manualmente o caractere correto codificação.
O problema com os padrões nacionais de códigos de caracteres é óbvio. E, finalmente, esses códigos nacionais começaram a se multiplicar, a Internet começou a explodir e todos queriam escrever em seu idioma nacional sem produzir esses mojibakes indecifráveis.
Nesse momento, havia duas opções: usar uma codificação para cada país ou criar um mapa universal de caracteres para representar todos os caracteres do planeta.
Uma breve cartilha sobre ASCII
Pode parecer excessivamente elementar, mas se formos detalhados, precisamos cobrir todas as bases.

Existem 3 grupos de colunas na tabela ASCII:
- o valor decimal do caractere
- o valor hexadecimal do caractere
- o glifo para o próprio personagem
Digamos que queremos codificar a palavra "ok" em ASCII. A letra "o" tem um valor decimal de 111 e 6F em hexadecimal. Em binário, seria - 01101111. A letra "k" é a posição 107 em decimal e 6B em hexadecimal ou - 01101011 em binário. Portanto, a palavra "OK" em ASCII se pareceria com 01101111 01101011. O processo de decodificação seria o oposto. Começamos com 8 bits, os convertemos em codificação decimal, terminamos com o número do caractere e pesquisamos na tabela o símbolo correspondente.
Unicode
Pelo exposto, deve ser bastante óbvio o motivo de um único mapa de caracteres comum ser necessário. Mas como seria? A resposta é Unicode, que na verdade não é uma codificação, mas um conjunto de caracteres. Consiste em 1.114.112 posições, ou pontos de código, a maioria ainda vazia, portanto, não é provável que o conjunto precise ser expandido.
O padrão Unicode consiste em 17 planos com 65.536 pontos de código cada. Cada plano contém um grupo de símbolos. O plano zero é o plano multilíngue básico, onde encontramos os caracteres mais usados em todos os alfabetos modernos. O segundo plano contém caracteres de idiomas mortos. Existem até dois aviões reservados para uso privado. A maioria dos aviões ainda está vazia.
Unicode possui pontos de código de 0 a 10FFFF (em hexadecimal).
Os caracteres são codificados no formato hexadecimal precedido por um "U +". Assim, por exemplo, o primeiro plano básico inclui os caracteres U + 0000 a U + FFFF (0 a 65.535), e o bloco 17 contém U + 100000 a U + 10FFFF (1.048.576 a 1.114.111).
Portanto, agora, em vez de uma variedade de inúmeras codificações, temos uma tabela abrangente que codifica todos os símbolos e caracteres dos quais podemos precisar. Mas não é sem suas falhas. Embora cada caractere tenha sido codificado anteriormente por um byte, agora ele pode ser codificado usando diferentes números de bytes. Por exemplo, você costumava precisar apenas de um byte para codificar todas as letras do alfabeto inglês. Por exemplo, a letra latina “o” em Unicode é U + 006F. Em outras palavras, o mesmo número do ASCII-6F em hexadecimal e 111 em binário. Mas para codificar o símbolo "U + 103D5" (o número persa "100"), precisamos de 103D5 em hexadecimal e 66.517 em decimal, e agora precisamos de três bytes.
Essa complexidade deve ser tratada por codificações Unicode como UTF-8 e UTF-16. E mais adiante, vamos dar uma olhada neles.
Utf-8
UTF-8 é uma codificação Unicode do sistema de codificação de largura variável que pode ser usada para exibir qualquer símbolo Unicode.
O que queremos dizer quando falamos em largura variável? Primeiro de tudo, precisamos entender que a unidade estrutural (atômica) na codificação é um byte. Codificação de largura variável significa que um caractere pode ser codificado usando diferentes números de unidades ou bytes. Por exemplo, letras latinas são codificadas com um byte e letras cirílicas com dois.
Antes de prosseguirmos, um pequeno aparte em relação à compatibilidade entre ASCII e UTF.
O fato de letras latinas e caracteres de controle de teclas, como quebras de linha, tabulação, etc. conter um byte torna a codificação UTF compatível com ASCII. Em outras palavras, caracteres de script e controle em latim são encontrados exatamente nos mesmos pontos de código em ASCII e UTF e são codificados usando um byte em ambos e, portanto, são compatíveis com versões anteriores.
Vamos usar a letra "o" do nosso exemplo ASCII anterior. Lembre-se de que sua posição na tabela ASCII é 111 ou 01101111 em binário. Na tabela Unicode, é U + 006F ou 01101111. E agora que o UTF é um sistema de codificação de largura variável, “o” seria de um byte. Em outras palavras, "o" seria representado da mesma maneira em ambos. E o mesmo para os caracteres de 0 a 128. Portanto, se o documento contiver letras em inglês, você não notará diferença se o abrisse usando UTF-8, UTF-16 ou ASCII e só notaria a diferença se começar a trabalhar com codificações nacionais.
Vejamos como a frase mista em inglês / russo "Hello World" apareceria em três sistemas de codificação diferentes: Windows-1251 (codificação em russo), ISO-8859-1 (sistema de codificação para idiomas da Europa Ocidental), UTF-8 (Unicode) . Este exemplo é revelador, porque temos uma frase em dois idiomas diferentes.

Agora, vamos considerar como esses sistemas de codificação funcionam e como podemos traduzir uma linha de texto de uma codificação para outra, e o que acontece se os caracteres são exibidos incorretamente ou se simplesmente não podemos fazer isso devido às diferenças nos sistemas.
Vamos supor que nossa frase original tenha sido escrita com a codificação Windows-1251. Quando olhamos para a tabela acima, podemos ver traduzindo de decimal ou hexadecimal para decimal que obtemos a codificação abaixo em binário usando o Windows-1251.
01001000 01100101 01101100 01101100 01101111 00100000 11101100 11101000 11110000
Então agora temos a frase "Hello World" na codificação Windows-1251.
Agora imagine que temos um arquivo de texto, mas não sabemos em que sistema de codificação o texto foi salvo. Nós assumimos que ele está codificado na ISO-8859-1 e a abrimos em nosso processador de texto usando este sistema de codificação. Como vimos anteriormente, alguns dos caracteres parecem bem, pois existem nesse sistema de codificação e estão nos mesmos pontos de código, mas os caracteres da palavra russa "mundo" não funcionam tão bem. Esses caracteres não existem no sistema de codificação e, em seus locais ou pontos de código, na ISO-8859-1, encontramos caracteres completamente diferentes. Portanto, "m" é o ponto de código 236, "e" é 232 e "p" é 240. Mas na ISO-8859-1 esses pontos de código correspondem a "ì" (236), "è" (232) e " (”(240).
Portanto, nossa frase em idioma misto “Hello World” codificada no Windows-1251 e lida na ISO-8859-1 será semelhante a “Hello ìèð”. Temos compatibilidade parcial e não podemos exibir uma frase codificada em um sistema adequadamente no outro, porque os símbolos de que precisamos simplesmente não existem na segunda codificação.
Precisamos de uma codificação Unicode - no nosso caso, usaremos o UTF-8 como exemplo. Já discutimos que os caracteres podem ter entre 1 e 4 bytes em UTF-8, mas outra vantagem é que o UTF, diferentemente dos dois sistemas de codificação anteriores, não está restrito a 256 símbolos, mas contém todos os símbolos no conjunto de caracteres Unicode .
Funciona da seguinte forma: o primeiro bit de cada caractere codificado corresponde não ao glifo ou símbolo em si, mas a um byte específico. Portanto, se o primeiro bit for zero, sabemos que o símbolo codificado usa apenas um byte - o que torna o conjunto compatível com o ASCII. Se olharmos atentamente para a tabela de símbolos ASCII, veremos que os primeiros 128 símbolos (alfabeto inglês, caracteres de controle e sinais de pontuação) são expressos em binário, todos começam com um valor de bit 0 (observe que, se você traduzir caracteres para binário usando um conversor on-line ou algo semelhante, o primeiro bit zero de alta ordem pode ser descartado, o que pode ser um pouco confuso).
01001000 - o valor do primeiro bit é 0, então 1 byte codifica 1 caractere -> "H".
01100101 - o valor do primeiro bit é 0, então 1 byte codifica 1 caractere -> “e”.
Se o valor do primeiro bit não for zero, o símbolo será codificado em vários bytes.
Uma codificação de dois bytes terá 110 para os primeiros valores de três bits.
11010000 10111100 - os bits do marcador são 110 e 10, portanto, usamos 2 bytes para codificar 1 caractere. O segundo byte, neste caso, sempre começa com "10." Portanto, omitimos os bits de controle (os bits iniciais destacados em vermelho e verde) e observamos o restante do código (10000111100) e convertemos para hex (043). -> U + 043C, que nos dá o "m" russo em Unicode.
Os bits iniciais para um caractere de três bytes são 1110.
11101000 10000111 101010101 - somamos todos os bits, exceto os de controle, e descobrimos que em hexadecimal temos 103B5, U + 103D5 - o antigo número persa cem (10000001111010101).
As codificações de caracteres de quatro bytes começam com os bits iniciais 11110.
11110100 10001111 10111111 10111111 - U + 10FFFF, que é o último caractere disponível no conjunto Unicode (1000011111111111111111111).
Agora, podemos escrever facilmente nossa frase em vários idiomas na codificação UTF-8.
Utf-16
UTF-16 é outra codificação de largura variável. A principal diferença entre UTF-16 e UTF-8 é que o UTF-16 usa 2 bytes (16 bits) por unidade de código em vez de 1 bye (8 bits). Em outras palavras, qualquer caractere Unicode codificado em UTF-16 pode ter dois ou quatro bytes. Para simplificar, vou me referir a esses dois bytes como uma unidade de código. Portanto, em UTF-16, qualquer caractere pode ser representado usando uma ou duas unidades de código.
Vamos começar com símbolos codificados usando uma unidade de código. Podemos calcular facilmente que existem 65.535 (216) caracteres com uma unidade de código, alinhada completamente com o plano multilíngue básico do Unicode. Todos os caracteres neste plano serão representados por uma unidade de código (dois bytes) em UTF-16.
Letra latina “o” - 00000000 01101111.
Letra cirílica "M" - 00000100 00011100.
Agora, vamos considerar caracteres fora do plano multilíngue básico. Eles requerem duas unidades de código (4 bytes) e são codificados de uma maneira um pouco mais complicada.
Primeiro, precisamos definir o conceito de um par substituto. Um par substituto é duas unidades de código usadas para codificar um único caractere (totalizando 4 bytes). O conjunto de caracteres Unicode reserva um intervalo especial D800 a DFFF para pares substitutos. Isso significa que, ao converter um par substituto em bytes em hexadecimal, acabamos com um ponto de código nesse intervalo que é um par substituto em vez de um caractere separado.
Para codificar um símbolo no intervalo de 10000 a 10FFFF (ou seja, caracteres que requerem mais de uma unidade de código), procedemos da seguinte maneira:
Subtraia 10000 (hex) do ponto de código (este é o ponto de código mais baixo no intervalo de 10000 a 10FFFF).
Acabamos com um número de até 20 bits não superior a FFFF.
Os 10 bits de alta ordem com os quais terminamos são adicionados ao D800 (o ponto de código mais baixo no intervalo de pares substitutos em Unicode).
Os próximos 10 bits são adicionados ao DC00 (também da faixa de pares substitutos).
Em seguida, terminamos com 2 unidades de código substitutas de 16 bits, cujos primeiros 6 bits definem a unidade como parte de um par substituto.
O décimo bit em cada substituto define a ordem do par. Se temos um "1", é o substituto principal ou alto, e se temos um "0", é o substituto final ou baixo.
Isso fará um pouco mais de sentido quando ilustrado com o exemplo abaixo.
Vamos codificar e decodificar o número persa cem (U + 103D5):
103D5 - 10000 = 3D5.
3D5 = 0000000000 1111010101 (os 10 bits mais altos são zero e, quando convertidos em hexadecimal, terminamos com "0" (os dez primeiros) e 3D5 (os segundos dez)).
0 + D800 = D800 (1101100000000000), os 6 primeiros bits nos dizem que esse ponto de código cai no intervalo dos pares substitutos, o décimo bit (à direita) tem um valor "0", então esse é o substituto alto.
3D5 + DC00 = DFD5 (1101111111010101), os primeiros 6 bits nos dizem que esse ponto de código cai no intervalo dos pares substitutos, o décimo bit (da direita) é um "1", então sabemos que esse é o substituto baixo.
O caractere resultante codificado em UTF-16 é semelhante a - 1101100000000000 1101111111010101.
Agora vamos decodificar o personagem. Digamos que temos o seguinte ponto de código - 1101100000100010 1101111010001000:
Nós convertemos para hexadecimal = D822 DE88 (os dois pontos de código caem no intervalo do par substituto, portanto, sabemos que estamos lidando com um par substituto).
1101100000100010 - o décimo bit (da direita) é um "0", então esse é o substituto alto.
1101111010001000 - o décimo bit (da direita) é um "1", portanto esse é o substituto baixo.
Ignoramos os 6 bits que identificam isso como substituto e ficamos com 0000100010 1010001000 (8A88).
Adicionamos 10000 (o ponto de código mais baixo no intervalo substituto) 8A88 + 10000 = 18A88.
Observamos a tabela Unicode para U + 18A88 = Tangut Component-649.
Parabéns a todos que leram até aqui!
Espero que isso tenha sido informativo sem deixar você muito entediado.
Você também pode achar útil:
O conjunto de caracteres unicode
Estratégias para localização de conteúdo: com base em IP ou navegador
Sobre o tradutor
A Alconost é um fornecedor global de serviços de localização para aplicativos , jogos , vídeos e sites em mais de 70 idiomas. Oferecemos traduções de lingüistas nativos, testes lingüísticos, fluxo de trabalho baseado em nuvem, localização contínua, gerenciamento de projetos 24 horas por dia, 7 dias por semana e trabalhamos com qualquer formato de recursos de string. Também criamos vídeos e imagens publicitários e educacionais, teasers, explicadores e trailers para o Google Play e a App Store.