Encoder du texte : ASCII et UTF-8
Représenter des lettres en octets et distinguer octets, caractères et émojis en JavaScript.
Du message aux octets
Reprenons notre message « Salut ». Pour l'envoyer ou l'enregistrer, il faut répondre à une première question : quels octets représentent les lettres ? L'encodage UTF-8 y répond. (La seconde question — vers quel réseau envoyer ces octets ? — sera traitée au chapitre Réseau : l'acheminement suppose les adresses IP, vues plus tard.)
Vous rencontrerez cette question en développement quand un texte accentué dépasse une limite en octets, ou quand un fichier paraît corrompu après un mauvais décodage.
Objectifs
À la fin de cette leçon, vous saurez :
- expliquer pourquoi "A" vaut 65 et comment UTF-8 encode "é" et "€" ;
- distinguer octet, caractère et émoji en JavaScript (
lengthvs octets) ; - manipuler tout cela en JS et en shell.
🔗 Pour vous rafraîchir la mémoire : le binaire et les bits
Du caractère aux bits : ASCII puis UTF-8
Un fichier texte contient des octets, pas des lettres directement. La table ASCII associe notamment A au nombre 65, donc à l'octet 01000001 (41 en hexadécimal). Un programme qui lit cet octet comme du texte peut afficher A. Mais ASCII seul ne suffit pas pour é, € ou 😀 : il faut un autre encodage.
"A" → 65 → 01000001 (1 octet)
"é" → 233 en Latin-1, mais en UTF-8 → 2 octets C3 A9
"€" → 3 octets E2 82 AC
"😀" → 4 octets F0 9F 98 80
🔑 Numéro du caractère ≠ encodage. Unicode attribue à chaque caractère un numéro :
éporte le numéro 233 (on l'écrit U+00E9). Ensuite, chaque encodage écrit ce numéro à sa façon : Latin-1 le posait tel quel sur un octet (E9= 233), UTF-8 le réécrit sur deux octets (C3 A9). C'est pourquoi"é".charCodeAt(0)affiche 233 (le numéro) tandis queBuffer.from("é")affichec3 a9(l'écriture UTF-8 de ce numéro). Deux questions différentes, deux réponses différentes.
UTF-8, l'encodage le plus courant sur le web, conserve les octets ASCII pour les caractères correspondants. Les autres caractères utilisent généralement 2 à 4 octets, avec des bits qui indiquent comment lire la suite (110xxxxx 10xxxxxx pour deux octets). Résultat : un fichier ASCII est déjà un fichier UTF-8 valide. Si l'émetteur écrit é en UTF-8 (C3 A9) mais que le destinataire lit ces octets avec une autre convention, le texte peut devenir illisible : c'est un problème d'encodage, pas de transport.
Pour notre message, Buffer.from("Salut", "utf8") donne les cinq octets 53 61 6c 75 74 en hexadécimal. Si vous remplacez Salut par Salüt, ü occupe deux octets en UTF-8 : six octets sont envoyés pour cinq lettres affichées. Essayez Buffer.from("Salüt", "utf8").length dans Node pour le constater.
Vérifiez (recopiez tel quel — od est un afficheur d'octets : -An masque les adresses, -tx1 affiche en hexadécimal octet par octet, -t u1 en décimal ; -n évite de compter le saut de ligne final) :
echo -n "A" | od -An -tx1 # 41
echo -n "é" | od -An -tx1 # c3 a9
echo -n "€" | od -An -tx1 # e2 82 ac
printf "A" | od -An -t u1 # 65
En JavaScript (même consigne qu'aux leçons précédentes : recopiez, comparez, le langage vient au chapitre 4) :
"A".charCodeAt(0) // 65 : le numéro du caractère
"é".charCodeAt(0) // 233 : le numéro (U+00E9), pas les octets !
Buffer.from("é", "utf8") // <Buffer c3 a9> : l'écriture UTF-8 de ce numéro
Buffer.from("€", "utf8") // <Buffer e2 82 ac>
Buffer.from("€", "utf8").length // 3 (octets)
"€".length // 1 : une seule tranche de 16 bits (voir ci-dessous)
"😀".length // 2 : deux tranches de 16 bits, pas deux caractères !
[... "😀"].length // 1 : découpé en points de code, un seul caractère
Pourquoi length vaut 2 pour 😀 ? JavaScript range le texte en tranches de 16 bits (encodage UTF-16) : la plupart des lettres tiennent dans une tranche, les émojis comme 😀 en demandent deux. C'est une troisième découpe, différente des octets UTF-8 du fichier : length ne compte donc ni des caractères visibles, ni des octets.
Leçon : savoir ce que l'on compte — octets, unités UTF-16, points de code ou caractères visibles — évite les bugs de taille et de troncature. Même un point de code ne correspond pas toujours à un caractère visible complet : certains symboles combinent plusieurs points de code.
Exercice
- En shell :
echo -n "Hi!" | od -An -tx1puis-t u1. Combien d'octets ? Même chose pourecho -n "Hé!". - En JS :
Buffer.from("Hi!", "utf8").lengthetBuffer.from("Hé!", "utf8").length. Pourquoi une différence d'un octet ? Buffer.from("Salüt", "utf8")contient six octets pour cinq lettres : lesquels correspondent àü, et pourquoi deux octets ?- Pour
€, donnez : son écriture UTF-8 (d'après les affichages ci-dessus), sonlength, et ce qu'affichera"€".charCodeAt(0)— numéro ou octets ? Vérifiez ensuite en machine.
Résumé
- ASCII associe 128 caractères à des valeurs de 0 à 127 ; UTF-8 conserve ces valeurs et encode les autres caractères sur plusieurs octets.
- JS :
charCodeAt/Buffer/odrévèlent les octets réels ;string.lengthcompte en UTF-16, pas en octets.
➡️ Plus tard dans le cursus : au chapitre Réseau, la leçon Calculer un masque réseau réutilisera les bits et l'hexadécimal pour décider où envoyer ces paquets.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Exercice 1.
echo -n "Hi!" | od -An -tx1 # 48 69 21 (3 octets)
echo -n "Hi!" | od -An -t u1 # 72 105 33
echo -n "Hé!" | od -An -tx1 # 48 c3 a9 21 (4 octets : H=48, é=c3 a9, !=21)
Exercice 2. "Hi!" = 3 octets (ASCII pur). "Hé!" = 4 octets car é prend 2 octets en UTF-8 (c3 a9). Buffer.from(...).length compte les octets, string.length compterait 3 vs 3 (JS compte en unités UTF-16 : "é" = 1 unité) — d'où l'importance de distinguer les deux métriques.
Exercice 3. S a l = 53 61 6c (1 octet chacun, ASCII), ü = c3 bc (2 octets en UTF-8), t = 74. Total six octets : les caractères ASCII restent sur un octet, les accentués en prennent deux.
Exercice 4. Écriture UTF-8 : e2 82 ac (3 octets). length : 1 (une seule tranche UTF-16). charCodeAt(0) affiche le numéro : 8364 (U+20AC) — vérifiez avec node. Trois réponses, trois découpes : octets du fichier, tranches JS, numéro Unicode.