Fonctionnement des encodages de texte. D'où viennent les "crocodiles"? Les principes de codage. Généralisation et analyse détaillée

Cet article vise à rapprocher et à démonter les principes et le mécanisme de travail des encodages de texte, en détail ce mécanisme à démonter et à expliquer. Il sera utile à ceux qui n'imaginent qu'en gros ce que sont les codages de texte et comment ils fonctionnent, comment ils diffèrent les uns des autres, pourquoi des caractères parfois illisibles apparaissent, quel principe de codage ont différents codages.

Pour avoir une compréhension détaillée de ce problème, vous devrez lire et rassembler plus d'un article et y consacrer beaucoup de temps. Dans ce document, tout est rassemblé et, en théorie, cela devrait gagner du temps et l'analyse s'est révélée assez détaillée à mon avis.

Que se passera-t-il sous la coupe: le principe de fonctionnement des codages mono-octets (ASCII, Windows-1251, etc.), les prérequis pour l'apparition d'Unicode, qu'est-ce qu'Unicode, les codages Unicode UTF-8, UTF-16, leurs différences, principales caractéristiques, compatibilité et incompatibilité des différents codages, principes du codage des caractères, analyse pratique du codage et du décodage.

Le problème des encodages maintenant a certainement perdu de sa pertinence, mais je pense qu'il ne sera pas superflu de savoir comment ils fonctionnent maintenant et comment ils fonctionnaient auparavant.

Prérequis Unicode


Je pense que cela vaut la peine de commencer à partir du moment où l'informatisation n'était pas encore aussi développée et ne faisait que gagner du terrain. Ensuite, les développeurs et les normalisateurs ne pensaient pas que les ordinateurs et Internet gagneraient en popularité et en prévalence. En fait, le besoin s'est fait sentir de coder le texte. Sous quelle forme il était nécessaire de stocker les lettres dans l'ordinateur, et lui (l'ordinateur) ne comprend que les uns et les zéros. Un codage ASCII à un octet a donc été développé (ce n'est probablement pas le premier codage, mais c'est le plus courant et indicatif, nous le considérerons donc comme une référence). Comment est-elle? Chaque caractère de ce codage est codé sur 8 bits. Il est facile de calculer que, sur cette base, le codage peut contenir 256 caractères (huit bits, zéros ou uns 2 8 = 256).

Les 7 premiers bits (128 caractères 2 7 = 128) de cet encodage ont été attribués aux caractères latins, aux caractères de contrôle (tels que les sauts de ligne, les tabulations, etc.) et aux caractères de grammaire. Le reste était réservé aux langues nationales. Autrement dit, il s'est avéré que les 128 premiers caractères sont toujours les mêmes, et si vous souhaitez encoder votre langue maternelle, veuillez utiliser la capacité restante. En fait, un immense zoo de codages nationaux est apparu. Et maintenant, vous pouvez vous-même imaginer, par exemple, quand je suis en Russie que je prends et crée un document texte, par défaut, il est créé en encodage Windows-1251 (encodage russe utilisé dans Windows) et envoyé à quelqu'un, par exemple, aux États-Unis. Même le fait que mon interlocuteur connaisse la langue russe ne l'aidera pas, car après avoir ouvert mon document sur son ordinateur (dans l'éditeur avec l'encodage par défaut du même ASCII), il ne verra pas des lettres russes, mais du krakozyabra. Pour être plus précis, ces endroits du document que j'écris en anglais seront affichés sans problème, car les 128 premiers caractères des encodages Windows-1251 et ASCII sont les mêmes, mais où j'ai écrit le texte russe, s'il n'indique pas l'encodage correct dans son éditeur, sous la forme d'un crocodile.

Je pense que le problème avec les encodages nationaux est compréhensible. En fait, il y a beaucoup de ces encodages nationaux, et Internet est devenu très large, et tout le monde voulait écrire dans sa propre langue et ne voulait pas que sa langue ressemble à des cheveux tordus. Il y avait deux façons de sortir, pour indiquer pour chaque page de l'encodage, ou pour créer un commun pour tous les caractères de la table des symboles du monde. La deuxième option a gagné, nous avons donc créé une table de caractères Unicode.

Petit atelier ASCII


Cela peut sembler élémentaire, mais comme j'ai décidé de tout expliquer en détail, cela est nécessaire.

Voici la table des caractères ASCII:



Ici, nous avons 3 colonnes:

  • numéro de caractère décimal
  • numéro de caractère au format hexadécimal
  • représentation du symbole lui-même.

Donc, encodez la chaîne «ok» (ASCII). Le caractère "o" (eng.) A une position de 111 en décimal et 6F en hexadécimal. 01101111 cela en un système binaire - 01101111 . Le symbole "k" (eng.) - position 107 en décimal et 6B en hexadécimal, traduit en binaire - 01101011 . La chaîne totale "ok" encodée en ASCII ressemblera à ceci - 01101111 01101011 . Le processus de décodage sera inversé. Nous prenons 8 bits, les traduisons en un codage à 10 décimales, obtenons le numéro de caractère, regardons le tableau de quel type de caractère il s'agit.

Unicode


Avec les conditions préalables à la création d'une table commune pour tous dans le monde des personnages, triés. Maintenant, en fait, à la table elle-même. Unicode - c'est la table qui est (ce n'est pas un encodage, mais une table de symboles). Il se compose de 1 114 112 postes. La plupart de ces postes ne sont pas encore pourvus de symboles, il est donc peu probable que cet espace doive être agrandi.

Cet espace total est divisé en 17 blocs de 65 536 caractères chacun. Chaque bloc contient son propre groupe de caractères. Le bloc zéro est le bloc de base, il contient les caractères les plus utilisés de tous les alphabets modernes. Dans le deuxième bloc, les caractères des langues éteintes. Il y a deux blocs réservés pour un usage privé. La plupart des blocs ne sont pas encore remplis.

La capacité totale des caractères Unicode est de 0 à 10FFFF (en hexadécimal).

Les caractères hexadécimaux sont écrits avec le préfixe "U +". Par exemple, le premier bloc de base comprend des caractères de U + 0000 à U + FFFF (de 0 à 65 535), et le dernier dix-septième bloc de U + 100 000 à U + 10FFFF (de 1 048 576 à 1 114 111).

Eh bien maintenant, au lieu du zoo des encodages nationaux, nous avons un tableau complet dans lequel tous les caractères qui peuvent nous être utiles sont cryptés. Mais il y a aussi des inconvénients. Si avant chaque caractère était codé avec un octet, il peut maintenant être codé avec un nombre différent d'octets. Par exemple, pour coder tous les caractères de l'alphabet anglais, un octet est encore suffisant, par exemple, le même caractère «o» en anglais est unicode U + 006F, c'est-à-dire que le même nombre qu'en ASCII est 6F en hexadécimal et 111 en décimal. Mais pour coder le caractère " U + 103D5 " (il s'agit de l'ancien nombre persan cent) - 103D5 en hexadécimal et 66 517 en décimal, ici nous avons besoin de trois octets.

Les codages Unicode tels que UTF-8 et UTF-16 devraient déjà résoudre ce problème. Nous en parlerons plus loin.

Utf-8


UTF-8 est un codage Unicode de longueur variable qui peut être utilisé pour représenter n'importe quel caractère Unicode.

Parlons plus de longueur variable, qu'est-ce que cela signifie? La première chose à dire est que l'unité structurelle (atomique) de ce codage est un octet. Le fait que le codage d'une variable soit long signifie qu'un caractère peut être codé avec un nombre différent d'unités structurelles du codage, c'est-à-dire avec un nombre différent d'octets. Par exemple, le latin est codé sur un octet et le cyrillique sur deux octets.

Une petite parenthèse du sujet, il faut écrire sur la compatibilité ASCII et UTF


Le fait que les caractères latins et les structures de contrôle de base, tels que les sauts de ligne, les tabulations, etc. encodé avec un octet rend les encodages utf compatibles avec les encodages ASCII. Autrement dit, les structures latines et de contrôle sont situées aux mêmes endroits à la fois en ASCII et en UTF, et le fait qu'elles soient codées ici et là d'un octet garantit cette compatibilité.

Prenons le caractère «o» de l'exemple ASCII ci-dessus. N'oubliez pas que dans le tableau des caractères ASCII, il est à 111 positions, sous forme de bits, il sera 01101111 . Dans la table Unicode, ce caractère est U + 006F, qui sera également 01101111 sous forme de bits. Et maintenant, comme UTF est un codage de longueur variable, ce caractère sera codé dans un octet. Autrement dit, la représentation de ce symbole dans les deux codages sera la même. Et donc pour toute la plage de caractères de 0 à 128. Autrement dit, si votre document est composé de texte anglais, vous ne remarquerez pas la différence si vous l'ouvrez en encodage UTF-8 et UTF-16 et ASCII (par exemple, en UTF-16, ces caractères sont tous sera également codé sur deux octets, vous ne verrez donc pas la différence si votre éditeur ignore zéro octet), et ainsi de suite jusqu'à ce que vous commenciez à travailler avec l'alphabet national.

Comparons en pratique à quoi ressemblera l'expression "Hello World" dans trois encodages différents: Windows-1251 (encodage russe), ISO-8859-1 (encodage des langues d'Europe occidentale), UTF-8 (encodage unicode). L'essence de cet exemple est que la phrase est écrite en deux langues. Voyons à quoi cela ressemblera dans différents encodages.


Dans le codage ISO-8859-1, il n'y a pas de tels caractères "m", "et" et "p".

Maintenant, travaillons avec les encodages et voyons comment convertir une chaîne d'un encodage à un autre et ce qui se passera si la conversion est incorrecte, ou si cela ne peut pas être fait en raison de la différence dans les encodages.

Nous supposons que la phrase a été initialement encodée dans Windows-1251. Sur la base du tableau ci-dessus, nous écrivons cette phrase sous forme binaire, codée dans Windows-1251. Pour ce faire, il suffit de traduire les symboles du binaire en décimal ou hexadécimal (du tableau ci-dessus).

01001000 01100101 01101100 01101100 01101111 00100000 11101100 11101000 11110000
Eh bien, c'est l'expression "Hello World" encodée dans Windows-1251.

Imaginez maintenant que vous avez un fichier contenant du texte, mais vous ne savez pas dans quel encodage ce texte se trouve. Vous supposez qu'il est encodé en ISO-8859-1 et l'ouvrez dans votre éditeur dans cet encodage. Comme dit ci-dessus, avec une partie des symboles, tout est en ordre, ils sont dans cet encodage, et sont même aux mêmes endroits, mais avec les symboles du mot «monde», tout est plus compliqué. Ces caractères ne sont pas dans cet encodage et à leur place dans l'encodage ISO-8859-1 sont des caractères complètement différents. Plus précisément, "m" est la position 236, "et" est 232. "p" est 240. Et à ces positions dans le codage ISO-8859-1 se trouvent les caractères suivants position 236 - caractère "ì", 232 - "è", 240 - "ð"

Ainsi, l'expression «Hello World» encodée dans Windows-1251 et ouverte dans l'encodage ISO-8859-1 ressemblera à ceci: «Hello ìèð». Il s'avère donc que ces deux encodages ne sont que partiellement compatibles, et cela ne fonctionnera pas correctement pour encoder une chaîne d'un encodage à un autre, car il n'y a tout simplement pas de tels caractères.

Ici, des codages Unicode seront nécessaires, et spécifiquement dans ce cas, pensez à UTF-8. Nous avons déjà découvert le fait que les caractères qu'il contient peuvent être codés avec un nombre d'octets différent de 1 à 4. Maintenant, il vaut la peine de dire que l'utilisation de l'UTF peut être encodée non seulement 256 caractères, comme dans les deux précédents, mais juste tous les caractères Unicode

Cela fonctionne comme suit. Le premier bit de chaque octet du caractère de codage n'est pas responsable du caractère lui-même, mais de la détermination de l'octet. C'est-à-dire, par exemple, si le premier bit (premier) est nul, cela signifie qu'un seul octet est utilisé pour coder un caractère. Ce qui assure la compatibilité avec ASCII. Si vous regardez attentivement la table de caractères ASCII, vous verrez que les 128 premiers caractères (alphabet anglais, caractères de contrôle et signes de ponctuation) s'ils sont convertis en binaire, tout commence par un bit zéro (soyez prudent si vous traduisez des caractères dans un système binaire en utilisant par exemple en ligne convertisseur, le premier bit de tête zéro peut être ignoré, ce qui peut prêter à confusion).

01001000 - le premier bit est zéro, puis 1 octet code 1 caractère -> "H"

01100101 - le premier bit est zéro, ce qui signifie que 1 octet code 1 caractère -> "e"

Si le premier bit n'est pas nul, le caractère est codé sur plusieurs octets.

Pour les caractères codés sur deux octets, les trois premiers bits doivent être - 110

110 10000 10 111100 - au début de 110, puis 2 octets codent 1 caractère. Dans ce cas, le deuxième octet commence toujours par 10. Au total, jetez les bits de contrôle (les initiaux, qui sont surlignés en rouge et vert) et prenez tous les 10000111100 restants ( 10000111100 ), traduisez-les en hexadécimal (043) -> U + 043C en Unicode, le symbole «m ".

pour les caractères à trois octets du premier octet, les bits de tête sont 1110

1110 1000 10 000111 10 1010101 - nous 1110 1000 10 000111 10 1010101 tout sauf les bits de contrôle et nous obtenons qu'en hexadécimal c'est 103V5, U + 103D5 est l'ancien chiffre persan cent ( 10000001111010101 )

pour les caractères de quatre octets dans le premier octet, les bits de tête sont 11110

11110 100 10 001111 10 111111 10 111111 - U + 10FFFF est le dernier caractère valide dans la table unicode ( 100001111111111111111 )

Maintenant, si vous le souhaitez, nous pouvons enregistrer notre phrase dans le codage UTF-8.

Utf-16


UTF-16 est également un codage de longueur variable. Sa principale différence avec UTF-8 est que l'unité structurelle n'est pas un mais deux octets. Autrement dit, dans le codage UTF-16, tout caractère Unicode peut être codé avec deux ou quatre octets. Par souci de clarté, permettez-moi d'appeler une paire de ces octets une paire de codes. Sur cette base, tout caractère Unicode encodé en UTF-16 peut être encodé avec une paire de codes ou deux.

Commençons par les caractères codés par une paire de codes. Il est facile de calculer qu'il peut y avoir 65 535 de ces caractères (2v16), ce qui coïncide complètement avec le bloc Unicode de base. Tous les caractères qui se trouvent dans ce bloc Unicode dans le codage UTF-16 seront codés avec une paire de codes (deux octets), tout est simple ici.

le symbole "o" (latin) - 00000000 01101111
le symbole "M" (cyrillique) - 00000100 00011100

Considérez maintenant les caractères en dehors de la plage Unicode de base. Pour leur encodage, deux paires de codes (4 octets) sont nécessaires. Et le mécanisme pour les coder est un peu plus compliqué, allons-y dans l'ordre.

Pour commencer, nous introduisons les concepts d'une paire de substitution. Une paire de substitution est constituée de deux paires de codes utilisées pour coder un caractère (4 octets au total). Pour ces paires de substitution, une plage spéciale de D800 à DFFF est affectée dans la table Unicode. Cela signifie que lors de la conversion d'une paire de codes d'une forme d'octets en hexadécimal, vous obtenez un nombre de cette plage, alors ce n'est pas un caractère indépendant, mais une paire de substitution.

Pour coder un caractère compris entre 10000 et 10FFFF (c'est-à-dire un caractère pour lequel vous devez utiliser plusieurs paires de codes), vous avez besoin de:

  1. Soustrayez 10000 (hexadécimal) du code de caractère (c'est le plus petit nombre de la plage 10000 - 10FFFF )
  2. à la suite du premier point, un nombre non supérieur à FFFFF sera obtenu, occupant jusqu'à 20 bits
  3. les 10 premiers bits du nombre reçu sont additionnés avec D800 (le début de la plage de paires de substitution en Unicode)
  4. les 10 bits suivants sont additionnés avec DC00 (également un nombre de la plage de paires de substitution)
  5. après cela, nous obtenons 2 paires de substitution de 16 bits chacune, les 6 premiers bits de chaque paire sont responsables de déterminer qu'il s'agit d'une substitution,
  6. le dixième bit de chaque substitut est responsable de son ordre; s'il vaut 1, alors c'est le premier substitut, si 0, alors le second

Nous analyserons cela dans la pratique, je pense que cela deviendra plus clair.

Par exemple, nous chiffrons le symbole, puis le déchiffrons. Prenez l'ancien nombre persan cent (U + 103D5):

  1. 103D5 - 10000 = 3D5
  2. 3D5 = 0000000000 1111010101 (les 10 premiers bits se sont révélés être des zéros, nous apportons cela à un nombre hexadécimal, nous obtenons 0 (dix premiers), 3D5 (deuxièmes dix))
  3. 0 + D800 = D800 ( 110110 0 000000000 ) les 6 premiers bits déterminent que le nombre de la plage de paires de substitution, le dixième bit (à droite) est zéro, alors c'est le premier substitut
  4. 3D5 + DC00 = DFD5 ( 110111 1 111010101 ) les 6 premiers bits déterminent que le nombre dans la plage de paires de substitution est le dixième bit (à droite) est un, alors c'est le deuxième substitut
  5. le total de ce caractère en UTF-16 est 1101100000000000 1101111111010101

Maintenant décodez le contraire. Disons que nous avons un tel code - 1101100000100010 1101111010001000:

  1. traduire en forme hexadécimale = D822 DE88 (les deux valeurs proviennent de la plage de paires de substitution, nous avons donc devant nous une paire de substitution)
  2. 110110 0 000100010 - le dixième bit (à droite) est zéro, puis le premier substitut
  3. 110111 1 010001000 - le dixième bit (à droite) est un, puis le deuxième substitut
  4. nous jetons 6 bits de ceux qui sont responsables de la détermination du substitut, nous obtenons 0000100010 1010001000 ( 8A88 )
  5. ajouter 10 000 (moins de plages de substitution) 8A88 + 10000 = 18A88
  6. regardez dans la table unicode le caractère U + 18A88 = Tangut Component-649. Composants du script Tangut.

Merci à ceux qui ont pu lire jusqu'au bout, j'espère que cela a été utile et pas très ennuyeux.

Voici quelques liens intéressants sur ce sujet:
habr.com/en/post/158895 - informations générales utiles sur les encodages
habr.com/en/post/312642 - à propos d'Unicode
unicode-table.com/ru - la table de caractères Unicode elle-même

Eh bien, où serais-tu sans elle
en.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4 - Unicode
en.wikipedia.org/wiki/ASCII - ASCII
en.wikipedia.org/wiki/UTF-8 - UTF-8
en.wikipedia.org/wiki/UTF-16 - UTF-16

Source: https://habr.com/ru/post/fr478636/


All Articles