Développons en Java 2.30 | |
Copyright (C) 1999-2022 Jean-Michel DOUDOUX | (date de publication : 15/06/2022) |
|
Niveau : | Supérieur |
Un caractère est une unité minimale abstraite de texte qui n'a pas forcément toujours la même représentation graphique.
La plate-forme Java utilise Unicode pour son support des caractères mais il est fréquent de devoir traiter des données textuelles encodées différemment en entrée ou en sortie d'une application. Java propose plusieurs classes et méthodes pour permettre la conversion de nombreux encodages de caractères de et vers Unicode.
Les applications Java qui doivent traiter des données non encodées en Unicode, sont lues avec l'encodage adéquat, stockées et traitées en Unicode et exportent le résultat de Unicode vers l'encodage initial ou l'encodage cible.
La version 5.0 de Java propose un support de la version 4.0 d'Unicode.
La JSR 204 « Unicode Supplementary Character Support » définit le support des caractères étendus d'Unicode dans la plate-forme Java. Ceci permet le support des caractères au-delà des 65546 possibles sur un stockage dans 2 octets.
Ce chapitre contient plusieurs sections :
Une chaîne de caractères est stockée en interne dans la JVM en UTF-16. L'encodage des caractères est uniquement à réaliser en entrée ou en sortie de la JVM (fichiers, base de données, flux, ...).
En interne, Java utilise le jeu de caractères Unicode et stocke les caractères encodés en UTF-16. Cependant pour le stockage persistant ou l'échange de données, il peut être nécessaire d'utiliser différents jeux d'encodages de caractères. Java propose des mécanismes au travers d'API pour permettre ces conversions.
Le type de données primitif char qui stocke un caractère a une représentation sous la forme d'un entier de 16 bits non signé pour pouvoir contenir un caractère encodé en UTF-16. Toutes les classes qui encapsulent un ou plusieurs caractères encodent ceux-ci en UTF-16 en interne dans la JVM.
Pour modifier l'encodage utilisé par défaut pour la lecture et l'écriture dans des flux, il faut modifier la valeur de la propriété file.encoding de la JVM.
Il est possible de fournir la valeur désirée en paramètre de la JVM.
Exemple : |
java.exe "-Dfile.encoding=UTF-8" -jar monapp.jar |
La propriété peut aussi être modifiée par programmation en utilisant la méthode setProperty() de la classe System.
Exemple : |
System.setProperty( "file.encoding", "UTF-8" ); |
Il existe de nombreux jeux d'encodages de caractères. Une liste complète des jeux d'encodages de caractères est consultable à l'url https://www.iana.org/assignments/character-sets
Un jeu de caractères est un ensemble de caractères. Dans un jeu, chaque caractère est associé à une valeur unique.
Les jeux de caractères les plus utilisés dans les pays occidentaux sont notamment ISO-8859-1, ISO-8859-15, UTF-8, Windows CP-1252, ...
Unicode est un ensemble de caractères pouvant contenir tous les caractères utilisés dans le monde. Unicode peut contenir jusqu'à 1 million de caractères, mais tous les caractères ne sont pas utilisés.
L'ensemble des caractères est divisé en blocs.
Unicode est géré par un consortium : la version courante d'Unicode est la 5.
Unicode attribue à chaque caractère un identifiant nommé code point. Unicode utilise la notation hexadécimale prefixée par « U+ » pour représenter un code point : exemple avec le caractère A qui possède le numéro U+0041.
Les 127 premiers caractères d'Unicode correspondent exactement à l'ensemble des caractères Ascii.
Les caractères Unicode peuvent être encodés avec plusieurs encodages de la norme UTF (Unicode Transformation Format)
UTF-32 est l'encodage le plus simple d'Unicode puisqu'il utilise 32 bits (4 octets) pour stocker chaque caractère mais c'est aussi l'encodage le plus coûteux en mémoire.
UTF-16 utilise un encodage sur 16 bits (2 octets) ou 2 fois 16 bits pour stocker les caractères Unicode. Ainsi les valeurs comprises entre U+0000 et U+FFFF sont encodées uniquement sur 16 bits. Les valeurs au-delà sont stockées sur 2 fois 16 bits.
Son principal avantage est qu'il est capable de stocker la plupart des caractères courants avec un seul entier de 16 bits.
Remarque : les fichiers encodés en UTF-16 ne sont généralement pas échangeables entre différents systèmes car deux conventions d'ordonnancement des octets sont utilisées.
UTF-8 utilise un encodage sur 1 à 4 octets pour stocker les caractères Unicode selon leurs valeurs :
Avec UTF-8 chaque caractère est encodé sur un nombre variable d'octets. L'avantage d'UTF-8 est qu'il est compatible avec l'Ascii puisque les premiers caractères sont ceux de la table Ascii et qu'ils sont codés sur un seul octet en UTF-8. Ceci rend UTF-8 assez largement utilisé.
L'encodage/décodage en UTF-8 est assez coûteux car complexe puisque les caractères sont encodés sur un nombre variable d'octets.
UTF-7 encode un caractère Unicode grâce à des séquences de caractères Ascii 7 bits. Cet encodage est utilisé par certains protocoles de messagerie.
Le tableau ci-dessous montre les valeurs des octets de différents encodages de quelques caractères Unicode.
Symbole |
A |
Z |
0 |
9 |
€ |
é |
@ |
Code point |
U+0041 |
U+005A |
U+0030 |
U+0039 |
U+20AC |
U+00E9 |
U+0040 |
UTF-8 |
41 |
5A |
30 |
39 |
E2 82 AC |
C3 A9 |
40 |
UTF-16 Litte endian |
41 00 |
5A 00 |
30 00 |
39 00 |
AC 20 |
E9 00 |
40 00 |
UTF-16 Big endian |
00 41 |
00 5A |
00 30 |
00 39 |
20 AC |
00 E9 |
00 40 |
UTF-32 Little endian |
41 00 00 00 |
5A 00 00 00 |
30 00 00 00 |
39 00 00 00 |
AC 20 00 00 |
E9 00 00 00 |
40 00 00 00 |
UTF-32 Big endian |
00 00 00 41 |
00 00 00 5A |
00 00 00 30 |
00 00 00 39 |
00 00 20 AC |
00 00 00 E9 |
00 00 00 40 |
Le début d'un fichier encodé en UTF peut contenir un marqueur optionnel nommé BOM (Byte Order Marker). Ce marqueur a deux utilités :
En UTF-8, les trois premiers octets du BOM sont EF BB BF.
Lorsque l'on édite un fichier encodé en UTF-8 contenant un BOM avec un éditeur utilisant l'encodage iso-8859-1, les premiers octets affichés sont �»ï¿½.
En UTF-16, les deux premiers octets du BOM peuvent avoir deux valeurs :
En UTF-32, les quatre premiers octets du BOM peuvent avoir deux valeurs :
L'environnement d'exécution Java supporte en standard plusieurs jeux d'encodages de caractères dont :
Les implémentations de l'environnement d'exécution Java proposent généralement un ensemble beaucoup plus complet de jeux d'encodages de caractères.
Plusieurs classes qui manipulent des caractères permettent de convertir les caractères au format Unicode en utilisant le jeu d'encodages de caractères souhaité notamment :
Les jeux d'encodages de caractères supportés par la plate-forme Java dépendent de leurs implémentations. Les jeux de caractères supportés en standard sont stockés dans le fichier rt.jar
Attention : la désignation des jeux de caractères dans les API java.io et java.lang est différente de la désignation de ceux de l'API java.nio. Exemple :
Description |
Nom pour l'API java.io et java.lang |
Nom pour l'API java.nio |
MS-DOS Latin-2 |
Cp852 |
IBM852 |
ISO Latin 1 |
ISO8859_1 |
ISO-8859-1 |
ISO Latin 2 |
ISO8859_2 |
ISO-8859-2 |
ISO Latin 15 |
ISO8859_15 |
ISO-8859-15 |
ASCII |
ASCII |
US-ASCII |
UTF 8 |
UTF8 |
UTF-8 |
UTF 16 |
UTF-16 |
UTF-16 |
UTF 32 |
UTF_32 |
UTF-32 |
Windows Latin 1 |
Cp1252 |
windows-1252 |
Les jeux de caractères supportés par la version internationale sont dans le fichier charsets.jar du sous-répertoire lib du répertoire d'installation du JRE.
Le plus important lorsque l'on manipule des données de type texte en Java est de s'assurer que les caractères seront encodés ou décodés avec le bon jeu de caractères d'encodage.
Une fois que la conversion est faite en écriture et en lecture, le support des caractères Unicode se fera de façon transparente entre l'application Java et les ressources externes.
Par exemple, pour écrire des données de type texte dans un fichier, il suffit de préciser le jeu de caractères d'encodage. Lors de la lecture de ce fichier, il suffit de préciser le même jeu de caractères d'encodage pour obtenir les données.
Chaque fichier encodé est composé d'un ensemble d'un ou plusieurs octets. Java travaille en interne en stockant les données de types caractères ou chaîne de caractères en Unicode en utilisant l'encodage UTF-16.
L'encodage se fait toujours du type String vers le type byte[].
Le décodage se fait toujours du type byte[] vers le type String.
La classe String permet aussi des conversions d'Unicode vers un type d'encodage et vice versa.
Un constructeur de la classe String permet de créer une chaîne de caractères à partir d'un tableau d'octets et du nom de l'encodage utilisé.
Exemple : |
byte[] someBytes = ...;
String encodingName = "Shift_JIS";
String s = new String ( someBytes, encodingName ); |
Pour obtenir un tableau d'octets qui contient le contenu d'une chaîne de caractères encodée selon un encodage particulier, il faut utiliser la méthode getBytes() de la classe String
Exemple : |
// Using String.getBytes to encode String to bytes
String s = ...;
byte [] b = s.getBytes( "8859_1" /* encoding */ ); |
La méthode getBytes() de la classe String utilise par défaut l'encodage du système d'exploitation sur lequel la JVM est exécutée.
Une surcharge de la méthode getBytes() permet de préciser l'encodage à utiliser.
Les classes qui héritent des classes Reader et Writer proposent des fonctionnalités pour permettre des opérations de lecture et d'écriture de caractères dans un flux.
La classe InputStreamReader permet de lire des données encodées avec de nombreux types d'encodage pour obtenir des données stockées dans la JVM en Unicode.
Exemple : |
// Pour lire un fichier en UTF8 :
new InputStreamReader(new FileInputStream("monfichier;txt"), "utf8") |
La classe OutputStreamReader permet d'écrire des données en Unicode encodées vers de nombreux types d'encodage.
Les classes qui héritent de la classe Reader décodent des octets en String en fonction de l'encodage précisé.
Les classes qui héritent de la classe Writer encodent des String en octets en fonction de l'encodage précisé.
Les classes FileReader et FileWriter permettent de lire et d'écrire des caractères dans un flux.
Exemple : |
// FileWriter
Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
// FileReader
Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); |
Le package java.nio.charset propose plusieurs classes pour réaliser des conversions de caractères.
La méthode canEncode() de la classe CharsetEncoder permet de vérifier si une chaîne de caractères peut être encodée avec un jeu de caractères d'encodage.
Exemple : vérifier si une chaîne de caractères peut être encodée en latin-1 |
String str = "abcdef"
CharsetEncoder encoder = Charset.forName("iso-8859-1").newEncoder();
boolean ok = encoder.canEncode(str);
str = "1000€";
encoder = Charset.forName("iso-8859-1").newEncoder();
ok = encoder.canEncode(str); |
La méthode availableCharsets() de la classe java.nio.Charset permet de connaître la liste des encodages supportés.
La classe java.nio.Charset permet aussi de faire des conversions. Son grand avantage est de ne pas avoir à rechercher la classe correspondant à l'encodage utilisée à chaque appel comme c'est le cas avec les méthodes de la classe String.
Exemple : |
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
byte[] b = ...;
Charset def = Charset.defaultCharset() ;
Charset cs = Charset.forName("Shift_JIS");
ByteBuffer bb = ByteBuffer.wrap( b );
CharBuffer cb = cs.decode( bb );
String s = cb.toString(); |
Le package java.nio.charset.spi propose des classes pour définir ses propres jeux d'encodage de caractères.
La plupart des fichiers sources sont encodés en ASCII, ISO-8859-1 ou autres mais dans tous les cas, ils sont transformés en UTF-16 avant la compilation.
Le code source peut être écrit directement en utilisant un format UTF, par exemple UTF-8. Il suffit alors de préciser au compilateur le jeu de caractères d'encodage utilisé.
Attention : si le code est écrit en UTF-8, il faut s'assurer que l'éditeur n'inclut pas le BOM (Byte Order Mark) au début du fichier (par exemple, c'est ce que fait l'outil Notepad sous Windows), sinon le compilateur refusera de compiler le code source
Exemple : |
public class Test {
public static void main(String[] args) {
System.out.println("€");
}
} |
Avec l'outil Notepad, il faut enregistrer le fichier au format utf-8 et compiler la classe en précisant que l'encodage est UTF-8.
Exemple : |
C:\temp>"C:\Program
Files\Java\jdk1.6.0_07\bin\javac" -encoding utf-8 Test.java
Test.java:1: illegal
character: \65279
?public class Test {
^
1
error |
En fait, Notepad a ajouté les octets du BOM au début du fichier
Résultat : |
�"�public class Test {
... |
Sans ces octets, le code source se compile parfaitement sous réserve de bien préciser la valeur utf-8 au paramètre -encoding du compilateur javac.
Il est aussi possible d'utiliser l'outil native2ascii, dont le nom est relativement inadéquat, fourni avec le jdk . Cet outil lit le code source et le convertit en ascii en échappant les caractères non ascii avec leur représentation hexadécimale sous la forme \unnnn, où nnnn représente le code Unicode du caractère. Il n'est alors plus nécessaire d'utiliser le paramètre encoding. L'avantage de cette solution est que le code source est lisible sur tous les systèmes puisqu'il est encodé en Ascii.
L'encodage de caractères est généralement nécessaire et cela avec plusieurs technologies utilisées en Java.
Généralement, les fichiers texte ne contiennent aucunes indications sur l'encodage utilisé.
Certaines normes proposent cependant des fonctionnalités optionnelles pour fournir l'information.
Exemple : HTML |
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
Exemple : XML |
<?xml version="1.0" encoding="ISO8859-1" ?>
|
Pour utiliser l'encodage UTF-8 dans une application web, il faut prendre plusieurs précautions.
Dans les JSP, il faut définir l'encodage utilisé
Exemple : |
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page pageEncoding="UTF-8"%>
|
Dans une servlet, il est possible d'utiliser la méthode setCharacterEncoding() de la classe HttpRequest pour préciser l'encodage des données de la requête. Cet appel doit être fait avant l'utilisation de la méthode getParameter() pour que les données soit correctement décodées.
Avec JDBC, il est parfois nécessaire de préciser l'encodage utilisé dans les données échangées. Dans ce cas, l'attribut à utiliser dépend de la base de données concernée et il faut consulter la documentation du pilote JDBC utilisé.
Exemple : |
jdbc:mysql://localhost/mabase?useUnicode=true&characterEncoding=utf8 |
|