Niveau 1 : bits et octets
Un ordinateur ne connaît que 0 et 1. Un bit est un interrupteur ; 8 bits forment un octet, soit 28=256 valeurs possibles. Additionner les poids des bits convertit le binaire en décimal : 01000001 = 64 + 1 = 65. Tout texte est au final une suite d'octets, et un « encodage de caractères » est une table qui dit quel nombre représente quel caractère — des tables différentes, ce sont des encodages différents.
Niveaux 2–3 : ASCII
L'ASCII de 1963 définit 128 caractères sur 7 bits : 0–31 pour les caractères de contrôle (retour à la ligne, tabulation, bip), 48–57 pour les chiffres 0–9, 65–90 pour les majuscules, 97–122 pour les minuscules. La table n'est pas disposée au hasard : majuscules et minuscules ne diffèrent que de 32 (le 6e bit), donc c | 0x20 met une lettre en minuscule ; un chiffre moins 48 donne sa valeur. L'hexadécimal est l'écriture habituelle pour lire des octets, deux chiffres par octet : 0x48 0x69 = Hi. L'ASCII n'utilise que 7 bits, le 8e restait libre — chaque pays l'a ensuite rempli à sa façon (Latin-1, GBK, Shift_JIS…), de manière incompatible : c'est la racine historique des textes illisibles.
Niveau 4 : points de code Unicode
Unicode numérote les caractères du monde entier : un point de code s'écrit U+ suivi d'hexadécimal — A = U+0041, 中 = U+4E2D, 😀 = U+1F600. L'espace va de U+0000 à U+10FFFF et se découpe en 17 « plans » ; les caractères courants tiennent presque tous dans le plan de base (U+0000–U+FFFF), emoji et sinogrammes rares vivant dans les plans supplémentaires. Le point clé : un point de code n'est qu'un numéro ; sa transformation en octets dépend de « formes d'encodage » comme UTF-8, UTF-16 ou UTF-32. « Unicode » n'est pas un encodage ; utf-8 en est un.
Niveaux 5–8 : les modèles à longueur variable d'UTF-8
UTF-8 (RFC 3629) représente un point de code sur 1 à 4 octets, et toute la règle tient dans le préfixe du premier octet :
| Plage de points de code |
Octets |
Modèle |
Bits utiles |
| U+0000–U+007F |
1 |
0xxxxxxx |
7 |
| U+0080–U+07FF |
2 |
110xxxxx 10xxxxxx |
11 |
| U+0800–U+FFFF |
3 |
1110xxxx 10xxxxxx 10xxxxxx |
16 |
| U+10000–U+10FFFF |
4 |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
21 |
On complète le binaire du point de code de zéros à gauche jusqu'au nombre de bits utiles, puis on remplit les x dans l'ordre pour obtenir les octets. é = U+00E9 = 11101001, complété à 11 bits en 00011 101001, remplit le modèle à deux octets : 11000011 10101001 = C3 A9.
Ce choix a trois avantages : un texte ASCII encodé en UTF-8 reste identique octet pour octet (compatibilité descendante totale) ; tout octet suivant commence par 10, donc on retrouve le début du caractère suivant n'importe où dans le flux (auto-synchronisation : perdre un octet ne décale pas toute la suite) ; les encodages de longueurs différentes ne se recouvrent jamais, un zéro comme 00 ne représentant que U+0000 (sûr pour les chaînes C). Le prix : un caractère chinois coûte 3 octets, 50 % de plus que les 2 octets du GBK ; mais l'anglais, la ponctuation et le code ne bougent pas, et UTF-8 est devenu l'encodage de plus de 98 % des pages web.
Les niveaux 7 et 8 font saisir un caractère de 3 octets puis un de 4. Ce dernier révèle un piège encore courant : le jeu utf8 de MySQL ne va en réalité que jusqu'à 3 octets, stocker un emoji provoque une erreur ou une troncature, il faut utf8mb4.
Niveau 9 : UTF-16 et .length
Les chaînes de JavaScript, Java, C# et de l'API Windows utilisent UTF-16 en interne : chaque unité de code fait 16 bits. Un caractère du plan de base tient sur une unité ; un point de code des plans supplémentaires se scinde en deux unités, une paire de substitution (surrogate pair), dans les plages D800–DBFF et DC00–DFFF. D'où "😀".length === 2, "😀".split("") qui coupe en deux moitiés sans signification, et str[0] qui renvoie la substitution haute. Pour compter les caractères réels, utilisez [...str].length (itération par point de code) ; pour compter les « caractères » perçus (emoji composés, diacritiques), Intl.Segmenter.
Niveaux 10–11 : Base64
Base64 (RFC 4648) n'est pas du chiffrement : c'est un moyen de transport qui transforme des octets quelconques en 64 caractères ASCII imprimables (A–Z a–z 0–9 + /), pour faire passer du binaire là où seul du texte est accepté : pièces jointes, champs JSON, URL data:, JWT. La méthode : 3 octets = 24 bits, découpés en 4 segments de 6 bits (26=64), chacun donnant un caractère par table : Man = 4D 61 6E = 01001101 01100001 01101110 → 010011 010110 000101 101110 → T W F u. Le volume gonfle donc à 4/3 de l'original.
Quand le nombre d'octets n'est pas un multiple de 3, le dernier groupe est incomplet : 1 octet manquant → un =, 2 manquants → deux =, pour que la longueur de sortie reste un multiple de 4. A, un octet de 8 bits, complété à 12 bits, se découpe en 010000 01(0000) → QQ, puis ==. La variante URL-safe remplace + / par - _ (pour éviter l'échappement dans une URL) et omet souvent le =, que le décodage restitue selon la longueur.
Niveau 12 : encodage URL en pourcentage
Dans une URL, seule une petite partie des caractères ASCII peut apparaître telle quelle (RFC 3986 §2). Les caractères réservés à signification syntaxique (&, =, ?, /), l'espace et tous les caractères non ASCII doivent s'écrire % suivi de l'hexadécimal de l'octet. L'encodage URL, c'est donc d'abord UTF-8, puis un % par octet : 中 → E4 B8 AD → %E4%B8%AD, un caractère chinois devenant 9 caractères. En JavaScript, encodeURIComponent encode le plus complètement (même / ? & =) et convient aux valeurs de paramètres ; encodeURI préserve les caractères de structure et convient à une adresse entière. La soumission de formulaire application/x-www-form-urlencoded écrit en plus l'espace en + — une convention historique des formulaires HTML, pas une partie de la RFC 3986.
Niveaux 13–14 : d'où viennent les textes illisibles, comment les sauver
Un texte illisible n'est pas une donnée corrompue : c'est de l'écrit avec un encodage A et lu avec un encodage B. Le 中 d'UTF-8 vaut E4 B8 AD :
- lu en Windows-1252 / Latin-1 (un octet, un caractère) : les trois octets deviennent
ä¸, trois lettres d'Europe de l'Ouest — une suite de lettres à diacritiques est le signe d'un « UTF-8 lu comme encodage occidental ».
- lu en GBK (deux octets par caractère chinois) :
E4 B8 forme le caractère 涓 et le AD restant est une moitié — « sinogrammes bizarres + moitié de caractère » signale un « UTF-8 lu comme GBK » ; 浣犲ソ et 涓枃 en font partie.
- à l'inverse, un texte GBK lu comme UTF-8 fait apparaître en masse le caractère de remplacement
� (U+FFFD) dès qu'une séquence d'octets est invalide.
Tant que les octets d'origine sont intacts, une relecture avec le bon encodage restitue le texte. D'où la première règle de réparation : conserver le fichier original, ne pas convertir en boucle — chaque conversion risque de remplacer un octet invalide par ? ou �, et c'est là la vraie perte d'information. Le niveau 14 fait faire le trajet inverse : reprendre 浣犲ソ en GBK pour retrouver les octets E4 BD A0 E5 A5 BD, puis lire en UTF-8 你好. La méthode moderne consiste à déclarer UTF-8 partout — en-tête HTTP, <meta charset> HTML, connexion à la base de données, boîte de dialogue d'enregistrement — pour que le désaccord n'ait pas l'occasion d'arriver.
BOM et ordre des octets
L'UTF-16 a deux ordres d'octets, gros-boutiste et petit-boutiste, et les fichiers commencent souvent par une marque d'ordre BOM (FE FF ou FF FE). L'UTF-8 n'a pas de problème d'ordre, mais le Bloc-notes Windows écrivait autrefois un BOM UTF-8 EF BB BF, ce qui fait échouer la première ligne d'un script shell ou ajoute un caractère invisible devant le premier nom de colonne d'un CSV. Si la vue « Décomposition UTF-8 » du cours montre ces trois octets au début, vous savez d'où ils viennent.