Este artigo tem como objetivo reunir e desmontar os princípios e o mecanismo de trabalho das codificações de texto, em detalhes esse mecanismo para desmontar e explicar. Será útil para aqueles que apenas imaginam o que são codificações de texto e como elas funcionam, como diferem entre si, por que caracteres ilegíveis às vezes aparecem e qual princípio de codificação existem diferentes codificações.
Para obter uma compreensão detalhada desse problema, você precisará ler e reunir mais de um artigo e gastar bastante tempo com isso. Neste material, tudo está reunido e, em teoria, deve economizar tempo e a análise, na minha opinião, se mostrou bastante detalhada.
O que acontecerá com o corte: o princípio de operação de codificações de byte único (ASCII, Windows-1251, etc.), os pré-requisitos para a aparência do Unicode, o que é Unicode, as codificações Unicode UTF-8, UTF-16, suas diferenças, características fundamentais, compatibilidade e incompatibilidade de diferentes codificações, princípios de codificação de caracteres, análise prática de codificação e decodificação.
O problema com codificações agora certamente perdeu relevância, mas ainda acho que não será supérfluo saber como elas funcionam agora e como antes.
Pré-requisitos Unicode
Acho que vale a pena começar a partir do momento em que a informatização ainda não estava tão desenvolvida e ganhava impulso. Então, os desenvolvedores e padronizadores não pensaram que os computadores e a Internet ganhariam popularidade e prevalência tão grandes. Na verdade, surgiu a necessidade de codificar o texto. De que forma era necessário armazenar as letras no computador, e ele (o computador) entende apenas uns e zeros. Portanto, uma codificação ASCII de um byte foi desenvolvida (provavelmente não é a primeira codificação, mas é a mais comum e indicativa; portanto, a consideraremos como referência). Como ela é? Cada caractere nesta codificação é codificado com 8 bits. É fácil calcular que, com base nisso, a codificação pode conter 256 caracteres (oito bits, zeros ou uns 2
8 = 256).
Os primeiros 7 bits (128 caracteres 2
7 = 128) nessa codificação foram dados a caracteres latinos, caracteres de controle (como quebras de linha, tabulações etc.) e caracteres gramaticais. O restante foi reservado para as línguas nacionais. Ou seja, os primeiros 128 caracteres são sempre os mesmos e, se você quiser codificar seu idioma nativo, use a capacidade restante. Na verdade, um enorme zoológico de códigos nacionais apareceu. E agora você pode imaginar, por exemplo, quando estou na Rússia, pego e crio um documento de texto; por padrão, ele é criado na codificação Windows-1251 (codificação russa usada no Windows) e enviado a alguém, por exemplo, nos EUA. Mesmo o fato de meu interlocutor saber russo não o ajudará, porque quando ele abre meu documento em seu computador (no editor com a codificação padrão do mesmo ASCII), ele não vê letras russas, mas krakozyabry. Para ser mais preciso, os lugares no documento que escrevo em inglês serão exibidos sem problemas, porque os primeiros 128 caracteres das codificações Windows-1251 e ASCII são os mesmos, mas onde escrevi o texto em russo, se não indicar a codificação correta em seu editor, sob a forma de um crocodilo.
Eu acho que o problema com codificações nacionais é compreensível. Na verdade, existem muitas dessas codificações nacionais, e a Internet se tornou muito ampla, e todos nela queriam escrever em seu próprio idioma e não queriam que seu idioma parecesse cabelos tortos. Havia duas maneiras de indicar, para cada página da codificação, ou criar uma tabela de caracteres comuns a todos os caracteres do mundo. A segunda opção ganhou, então criou uma tabela de caracteres Unicode.
Pequena oficina ASCII
Pode parecer elementar, mas desde que decidi explicar tudo em detalhes, isso é necessário.
Aqui está a tabela de caracteres ASCII:

Aqui temos 3 colunas:
- número de caractere decimal
- número de caractere no formato hexadecimal
- representação do próprio símbolo.
Então, codifique a string "ok" (ASCII). O caractere "o" (eng.) Possui uma posição de 111 em decimal e
6F em hexadecimal.
01101111
traduzir isso em um sistema binário -
01101111
. O símbolo "k" (port.) - posição 107 em decimal e
6B em hexadecimal, se traduz no binário -
01101011
. A cadeia total "ok" codificada em ASCII terá a seguinte aparência -
01101111 01101011
. O processo de decodificação será inverso. Pegamos 8 bits, os convertemos em uma codificação de 10 decimais, obtemos o número do caractere, olhamos para a tabela que tipo de caractere é.
Unicode
Com os pré-requisitos para criar uma tabela comum para todos no mundo dos personagens, resolvidos. Agora, na verdade, para a própria mesa. Unicode - esta é a tabela que é (não é uma codificação, mas uma tabela de símbolos). É composto por 1.114.112 posições. A maioria dessas posições ainda não foi preenchida com símbolos; portanto, é improvável que esse espaço precise ser expandido.
Esse espaço total é dividido em 17 blocos, 65.536 caracteres cada. Cada bloco contém seu próprio grupo de caracteres. O bloco zero é o básico, contém os caracteres mais usados de todos os alfabetos modernos. No segundo bloco, estão os caracteres de idiomas extintos. Existem dois blocos reservados para uso privado. A maioria dos blocos ainda não está preenchida.
A capacidade total de caracteres Unicode é de
0 a
10FFFF (em hexadecimal).
Caracteres hexadecimais são gravados com o prefixo "U +". Por exemplo, o primeiro bloco base inclui caracteres de U + 0000 a U + FFFF (de 0 a 65.535) e o último décimo sétimo bloco de U + 100.000 a U + 10FFFF (de 1.048.576 a 1.114.111).
Bem, agora, em vez do zoológico de codificações nacionais, temos uma tabela abrangente na qual todos os caracteres que podem ser úteis para nós são criptografados. Mas também existem desvantagens. Se antes de cada caractere ser codificado com um byte, agora ele pode ser codificado com um número diferente de bytes. Por exemplo, para codificar todos os caracteres do alfabeto inglês, um byte ainda é suficiente, por exemplo, o mesmo caractere “o” (inglês) possui U + 006F em Unicode, ou seja, o mesmo número que em ASCII é
6F em hexadecimal e 111 em decimal. Mas para codificar o caractere "
U + 103D5 " (este é o antigo número persa cem) - 103D5 em hexadecimal e 66.517 em decimal, aqui precisamos de três bytes.
Codificações Unicode como UTF-8 e UTF-16 já devem resolver esse problema. Além disso, falaremos sobre eles.
Utf-8
UTF-8 é uma codificação Unicode de comprimento variável que pode ser usada para representar qualquer caractere Unicode.
Vamos falar mais sobre comprimento variável, o que isso significa? A primeira coisa a dizer é que a unidade estrutural (atômica) dessa codificação é um byte. O fato de a codificação de uma variável ser longa significa que um caractere pode ser codificado com um número diferente de unidades estruturais da codificação, ou seja, com um número diferente de bytes. Por exemplo, o latim é codificado em um byte e o cirílico em dois bytes.
Um pouco divergente do tópico, é necessário escrever sobre a compatibilidade de ASCII e UTF
O fato de caracteres latinos e estruturas básicas de controle, como quebras de linha, guias, etc. codificado com um byte torna codificações utf compatíveis com codificações ASCII. Na verdade, as estruturas em latim e de controle estão localizadas nos mesmos locais, tanto em ASCII quanto em UTF, e o fato de serem codificadas ali e ali por um byte garante essa compatibilidade.
Vamos pegar o caractere "o" do exemplo ASCII acima. Lembre-se de que na tabela de caracteres ASCII está em 111 posições, em forma de bit será
01101111
. Na tabela Unicode, esse caractere é U + 006F, que também será
01101111
no formato de bit. E agora, como UTF é uma codificação de tamanho variável, esse caractere será codificado em um byte. Ou seja, a representação deste símbolo nas duas codificações será a mesma. E assim, para todo o intervalo de caracteres de 0 a 128. Ou seja, se o documento consistir em texto em inglês, você não notará a diferença se o abrir na codificação UTF-8 e UTF-16 e ASCII (por exemplo, em UTF-16, esses caracteres são todos Igualmente será codificado em dois bytes; portanto, você não verá a diferença se o seu editor ignorar zero bytes) e assim por diante até começar a trabalhar com o alfabeto nacional.
Vamos comparar na prática como será a frase "Hello World" em três codificações diferentes: Windows-1251 (codificação russa), ISO-8859-1 (codificação de idiomas da Europa Ocidental), UTF-8 (codificação unicode). A essência deste exemplo é que a frase está escrita em dois idiomas. Vamos ver como ficará em diferentes codificações.
Na codificação ISO-8859-1, não existem esses caracteres "m", "e" e "p".Agora vamos trabalhar com as codificações e ver como converter uma string de uma codificação para outra e o que acontecerá se a conversão estiver incorreta ou se não puder ser feita devido à diferença nas codificações.
Assumimos que a frase foi originalmente codificada no Windows-1251. Com base na tabela acima, escrevemos esta frase em formato binário, codificado no Windows-1251. Para fazer isso, precisamos apenas converter os símbolos de binário para decimal ou hexadecimal (da tabela acima).
01001000 01100101 01101100 01101100 01101111 00100000 11101100 11101000 11110000
Bem, esta é a frase "Hello World" codificada no Windows-1251.Agora imagine que você tem um arquivo com texto, mas não sabe em qual codificação está esse texto. Você assume que ele está codificado na ISO-8859-1 e a abre no seu editor nessa codificação. Como dito acima, com uma parte dos símbolos, tudo está em ordem, eles estão nessa codificação e até estão nos mesmos lugares, mas com os símbolos da palavra "mundo" tudo fica mais complicado. Esses caracteres não estão nessa codificação e em seu lugar na codificação ISO-8859-1 são caracteres completamente diferentes. Especificamente, "m" é a posição 236, "e" é 232. "p" é 240. E nessas posições da codificação ISO-8859-1, estão os seguintes caracteres: posição 236 - caractere "ì", 232 - "è", 240 - "ð"
Portanto, a frase “Hello World”, codificada no Windows-1251 e aberta na codificação ISO-8859-1, terá a seguinte aparência: “Hello ìèð”. Portanto, essas duas codificações são apenas parcialmente compatíveis e não funcionará corretamente para codificar uma sequência de uma codificação para outra, porque simplesmente não existem caracteres desse tipo.
Aqui serão necessárias codificações Unicode e, especificamente, neste caso, considere UTF-8. O fato de que os caracteres nele podem ser codificados com um número diferente de bytes de 1 a 4, já descobrimos. Agora vale a pena dizer que o uso de UTF pode ser codificado não apenas 256 caracteres, como nos dois anteriores, mas apenas todos os caracteres Unicode
Funciona da seguinte maneira. O primeiro bit de cada byte do caractere de codificação não é responsável pelo próprio caractere, mas pela determinação do byte. Ou seja, por exemplo, se o (primeiro) bit inicial for zero, isso significa que apenas um byte é usado para codificar um caractere. O que fornece compatibilidade com ASCII. Se você observar atentamente a tabela de caracteres ASCII, verá que os primeiros 128 caracteres (alfabeto inglês, caracteres de controle e sinais de pontuação) se forem convertidos em binários, tudo começa com um bit zero (tenha cuidado se você converter caracteres em um sistema binário usando, por exemplo, online conversor, o primeiro bit zero à esquerda pode ser descartado, o que pode ser confuso).
01001000
- o primeiro bit é zero e 1 byte codifica 1 caractere -> "H"
01100101
- o primeiro bit é zero, o que significa que 1 byte codifica 1 caractere -> "e"
Se o primeiro bit não for zero, o caractere será codificado em vários bytes.
Para caracteres de byte duplo, os três primeiros bits devem ser - 110
110 10000 10 111100
- no início de 110, 2 bytes codificam 1 caractere. O segundo byte, neste caso, sempre começa com 10. No total, descarte os bits de controle (os iniciais, destacados em vermelho e verde) e pegue todos os
10000111100
restantes (
10000111100
), traduza-os em hexadecimal (043C) -> U + 043C em Unicode, o símbolo "m "
para caracteres de três bytes no primeiro byte, os bits iniciais são 1110
1110 1000 10 000111 10 1010101
-
1110 1000 10 000111 10 1010101
tudo, exceto os bits de controle, e obtemos que em hexadecimal é 103V5, U + 103D5 é o dígito persa antigo cem (
10000001111010101
)
para caracteres de quatro bytes no primeiro byte, os bits iniciais são 11110
11110 100 10 001111 10 111111 10 111111
- U + 10FFFF é o último caractere válido na tabela unicode (
100001111111111111111
)
Agora, se desejar, podemos gravar nossa frase na codificação UTF-8.
Utf-16
UTF-16 também é uma codificação de comprimento variável. Sua principal diferença em relação ao UTF-8 é que a unidade estrutural não é de um, mas de dois bytes. Ou seja, na codificação UTF-16, qualquer caractere Unicode pode ser codificado com dois ou quatro bytes. Por uma questão de clareza, permita-me chamar um par de bytes de código. Com base nisso, qualquer caractere Unicode codificado em UTF-16 pode ser codificado com um par de códigos ou dois.
Vamos começar com os caracteres que são codificados por um par de códigos. É fácil calcular que podem existir 65.535 caracteres (2v16), o que coincide completamente com o bloco Unicode base. Todos os caracteres que estão neste bloco Unicode na codificação UTF-16 serão codificados com um par de códigos (dois bytes), tudo aqui é simples.
o símbolo "o" (latino) -
00000000 01101111
o símbolo "M" (cirílico) -
00000100 00011100
Agora considere caracteres fora do intervalo base de Unicode. Para sua codificação, são necessários dois pares de códigos (4 bytes). E o mecanismo para codificá-los é um pouco mais complicado, vamos em ordem.
Para começar, apresentamos os conceitos de um par substituto. Um par substituto são dois pares de códigos usados para codificar um caractere (total de 4 bytes). Para esses pares substitutos, um intervalo especial de
D800 a
DFFF é atribuído na tabela Unicode. Isso significa que, ao converter um par de códigos de um formato de byte em hexadecimal, você obtém um número desse intervalo; esse não é um caractere independente, mas um par substituto.
Para codificar um caractere do intervalo de
10000 a 10FFFF (ou seja, um caractere para o qual você precisa usar mais de um par de códigos), é necessário:
- Subtraia 10000 (hexadecimal) do código de caractere (este é o menor número do intervalo de 10000 a 10FFFF )
- como resultado do primeiro ponto, um número não maior que FFFFF será obtido, ocupando até 20 bits
- os 10 bits iniciais do número recebido são somados com D800 (o início do intervalo de pares substitutos em Unicode)
- os próximos 10 bits são somados com DC00 (também um número do intervalo de pares substitutos)
- depois disso, obtemos 2 pares substitutos de 16 bits cada, os 6 primeiros bits de cada par são responsáveis por determinar que é um substituto,
- o décimo bit em cada substituto é responsável por sua ordem; se for 1, então este é o primeiro substituto, se 0, então o segundo
Vamos analisar isso na prática, acho que ficará mais claro.
Por exemplo, criptografamos o símbolo e, em seguida, descriptografamos. Tome o número persa antigo cem (U + 103D5):
- 103D5 - 10000 = 3D5
- 3D5 =
0000000000 1111010101
(os 10 bits 0000000000 1111010101
acabaram sendo zero, trazemos isso para um número hexadecimal, obtemos 0 (dez primeiros), 3D5 (segundos dez)) - 0 + D800 = D800 (
110110 0 000000000
), os primeiros 6 bits determinam que o número do intervalo de substitutos emparelha o décimo bit (à direita) é zero, então este é o primeiro substituto - 3D5 + DC00 = DFD5 (
110111 1 111010101
) os primeiros 6 bits determinam que o número no intervalo de pares substitutos é o décimo bit (à direita) é um, então este é o segundo substituto - total esse caractere em UTF-16 é
1101100000000000 1101111111010101
Agora decodifique o oposto. Digamos que tenhamos esse código - 1101100000100010 1101111010001000:
- converter em forma hexadecimal = D822 DE88 (ambos os valores são do intervalo de pares substitutos, portanto, temos diante de nós um par substituto)
110110 0 000100010
- o décimo bit (à direita) é zero e o primeiro substituto110111 1 010001000
- o décimo bit (à direita) é um, depois o segundo substituto- descartamos 6 bits dos responsáveis pela determinação do substituto, obtemos
0000100010 1010001000
( 8A88 ) - adicione 10.000 (menos intervalos substitutos) 8A88 + 10000 = 18A88
- procure na tabela unicode o caractere U + 18A88 = Tangut Component-649. Componentes do script Tangut.
Graças a quem foi capaz de ler até o fim, espero que tenha sido útil e não muito chato.
Aqui estão alguns links interessantes sobre este tópico:
habr.com/en/post/158895 - informações gerais úteis sobre codificações
habr.com/en/post/312642 - sobre Unicode
unicode-table.com/ru - a própria tabela de caracteres Unicode
Bem, na verdade, onde você estaria sem ela
en.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4 - Unicode
en.wikipedia.org/wiki/ASCII - ASCII
pt.wikipedia.org/wiki/UTF-8 - UTF-8
pt.wikipedia.org/wiki/UTF-16 - UTF-16