95 votes

Comment trouver le jeu de caractères/encodage par défaut en Java ?

La réponse évidente est d'utiliser Charset.defaultCharset() mais nous avons récemment découvert que ce n'était peut-être pas la bonne réponse. On m'a dit que le résultat était différent du jeu de caractères par défaut réel utilisé par les classes java.io à plusieurs occasions. Il semble que Java conserve deux jeux de caractères par défaut. Quelqu'un a-t-il une idée de ce problème ?

Nous avons pu reproduire un cas d'échec. Il s'agit d'une erreur de l'utilisateur, mais il se peut qu'elle expose la cause profonde de tous les autres problèmes. Voici le code,

public class CharSetTest {

    public static void main(String[] args) {
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.setProperty("file.encoding", "Latin-1");
        System.out.println("file.encoding=" + System.getProperty("file.encoding"));
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.out.println("Default Charset in Use=" + getDefaultCharSet());
    }

    private static String getDefaultCharSet() {
        OutputStreamWriter writer = new OutputStreamWriter(new ByteArrayOutputStream());
        String enc = writer.getEncoding();
        return enc;
    }
}

Notre serveur requiert un jeu de caractères par défaut en Latin-1 pour gérer un encodage mixte (ANSI/Latin-1/UTF-8) dans un ancien protocole. Tous nos serveurs fonctionnent donc avec ce paramètre de la JVM,

-Dfile.encoding=ISO-8859-1

Voici le résultat sur Java 5,

Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=UTF-8
Default Charset in Use=ISO8859_1

Quelqu'un essaie de changer l'encodage au moment de l'exécution en définissant le file.encoding dans le code. Nous savons tous que cela ne fonctionne pas. Cependant, cela semble perturber defaultCharset() mais n'affecte pas le jeu de caractères par défaut utilisé par OutputStreamWriter.

Est-ce un bug ou une fonctionnalité ?

EDIT : La réponse acceptée montre la cause profonde du problème. Fondamentalement, vous ne pouvez pas faire confiance à defaultCharset() en Java 5, qui n'est pas l'encodage par défaut utilisé par les classes I/O. Il semble que Java 6 corrige ce problème.

0 votes

C'est étrange, car le defaultCharset utilise une variable statique qui n'est définie qu'une seule fois (selon la documentation - au démarrage de la VM). Quel vendeur de VM utilisez-vous ?

0 votes

J'ai pu reproduire ce problème sur Java 5, à la fois sur Sun/Linux et Apple/OS X.

0 votes

Cela explique pourquoi defaultCharset() ne met pas en cache le résultat. Je dois encore trouver quel est le vrai jeu de caractères par défaut utilisé par les classes IO. Il doit y avoir un autre jeu de caractères par défaut mis en cache quelque part ailleurs.

64voto

bruno conde Points 28120

C'est vraiment étrange... Une fois défini, le jeu de caractères par défaut est mis en cache et n'est pas modifié tant que la classe est en mémoire. Le fait de définir le "file.encoding" avec System.setProperty("file.encoding", "Latin-1"); ne fait rien. Chaque fois que Charset.defaultCharset() est appelé, il renvoie le jeu de caractères mis en cache.

Voici mes résultats :

Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=ISO-8859-1
Default Charset in Use=ISO8859_1

J'utilise pourtant la JVM 1.6.

(mise à jour)

Ok. J'ai reproduit votre bug avec la JVM 1.5.

En regardant le code source de la version 1.5, le jeu de caractères par défaut mis en cache n'est pas défini. Je ne sais pas s'il s'agit d'un bogue ou non, mais la version 1.6 modifie cette implémentation et utilise le jeu de caractères mis en cache :

JVM 1.5 :

public static Charset defaultCharset() {
    synchronized (Charset.class) {
        if (defaultCharset == null) {
            java.security.PrivilegedAction pa =
                    new GetPropertyAction("file.encoding");
            String csn = (String) AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                return cs;
            return forName("UTF-8");
        }
        return defaultCharset;
    }
}

JVM 1.6 :

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            java.security.PrivilegedAction pa =
                    new GetPropertyAction("file.encoding");
            String csn = (String) AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

Lorsque vous définissez l'encodage du fichier sur file.encoding=Latin-1 la prochaine fois que vous appelez Charset.defaultCharset() Comme le jeu de caractères par défaut mis en cache n'est pas défini, il essaiera de trouver le jeu de caractères approprié pour le nom. Latin-1 . Ce nom n'est pas trouvé, car il est incorrect, et renvoie le nom par défaut. UTF-8 .

Quant à savoir pourquoi les classes IO telles que OutputStreamWriter renvoie un résultat inattendu,
la mise en œuvre de sun.nio.cs.StreamEncoder (qui est utilisé par ces classes IO) est également différent pour la JVM 1.5 et la JVM 1.6. L'implémentation de la JVM 1.6 est basée sur l'élément Charset.defaultCharset() pour obtenir l'encodage par défaut, s'il n'est pas fourni aux classes IO. L'implémentation de la JVM 1.5 utilise une méthode différente Converters.getDefaultEncodingName(); pour obtenir le jeu de caractères par défaut. Cette méthode utilise son propre cache du jeu de caractères par défaut qui est défini lors de l'initialisation de la JVM :

JVM 1.6 :

public static StreamEncoder forOutputStreamWriter(OutputStream out,
        Object lock,
        String charsetName)
        throws UnsupportedEncodingException
{
    String csn = charsetName;
    if (csn == null)
        csn = Charset.defaultCharset().name();
    try {
        if (Charset.isSupported(csn))
            return new StreamEncoder(out, lock, Charset.forName(csn));
    } catch (IllegalCharsetNameException x) { }
    throw new UnsupportedEncodingException (csn);
}

JVM 1.5 :

public static StreamEncoder forOutputStreamWriter(OutputStream out,
        Object lock,
        String charsetName)
        throws UnsupportedEncodingException
{
    String csn = charsetName;
    if (csn == null)
        csn = Converters.getDefaultEncodingName();
    if (!Converters.isCached(Converters.CHAR_TO_BYTE, csn)) {
        try {
            if (Charset.isSupported(csn))
                return new CharsetSE(out, lock, Charset.forName(csn));
        } catch (IllegalCharsetNameException x) { }
    }
    return new ConverterSE(out, lock, csn);
}

Mais je suis d'accord avec les commentaires. Vous ne devrait pas s'appuyer sur cette propriété . C'est un détail de mise en œuvre.

0 votes

Pour reproduire cette erreur, vous devez être sous Java 5 et votre encodage par défaut JRE doit être UTF-8.

2 votes

Il s'agit d'écrire sur l'implémentation, pas sur l'abstraction. Si vous vous fiez à des éléments non documentés, ne soyez pas surpris si votre code se casse lorsque vous passez à une version plus récente de la plate-forme.

25voto

McDowell Points 62645

Est-ce un bug ou une fonctionnalité ?

Cela ressemble à un comportement non défini. Je sais que, dans la pratique, vous pouvez modifier l'encodage par défaut en utilisant une propriété de ligne de commande, mais je ne pense pas que ce qui se passe lorsque vous faites cela soit défini.

Numéro de bogue : 4153515 sur les problèmes de mise en place de cette propriété :

Il ne s'agit pas d'un bogue. La propriété "file.encoding" n'est pas requise par la spécification de la plateforme J2SE il s'agit d'un détail interne des implémentations de Sun qui ne doit pas être examiné ou modifié par le code utilisateur. ne doit pas être examiné ou modifié par le code utilisateur. Il est également prévu qu'elle soit en lecture seule ; il est techniquement impossible de prendre en charge la définition de cette propriété de cette propriété à des valeurs arbitraires sur la ligne de commande ou à tout autre moment du programme.

La meilleure façon de modifier l'encodage par défaut utilisé par la VM et le système d'exécution est de changer la locale de la plate-forme sous-jacente avant de démarrer votre projet. d'exécution est de modifier la locale de la plate-forme sous-jacente avant de lancer votre programme Java.

Je frémis quand je vois des gens qui définissent l'encodage en ligne de commande - on ne sait pas quel code cela va affecter.

Si vous ne voulez pas utiliser l'encodage par défaut, définissez l'encodage que vous voulez explicitement via la méthode appropriée/ Constructeur .

6voto

jarnbjo Points 18238

Le comportement n'est pas vraiment si étrange. En examinant l'implémentation des classes, on constate qu'il est causé par :

  • Charset.defaultCharset() ne met pas en cache le jeu de caractères déterminé dans Java 5.
  • Définir la propriété système "file.encoding" et invoquer Charset.defaultCharset() provoque à nouveau une seconde évaluation de la propriété système, aucun jeu de caractères portant le nom "Latin-1" n'est trouvé, donc Charset.defaultCharset() La valeur par défaut est "UTF-8".
  • Le site OutputStreamWriter met cependant en cache le jeu de caractères par défaut et est probablement déjà utilisé lors de l'initialisation de la VM, de sorte que son jeu de caractères par défaut s'écarte de Charset.defaultCharset() si la propriété système "file.encoding" a été modifiée au moment de l'exécution.

Comme nous l'avons déjà souligné, il n'est pas documenté comment la VM doit se comporter dans une telle situation. Le site Charset.defaultCharset() La documentation de l'API n'est pas très précise quant à la manière dont le jeu de caractères par défaut est déterminé. Elle mentionne seulement que cela se fait généralement au démarrage de la VM, en fonction de facteurs tels que le jeu de caractères par défaut du système d'exploitation ou les paramètres régionaux par défaut.

4voto

Sean Owen Points 36577

Tout d'abord, Latin-1 est identique à ISO-8859-1, donc, la valeur par défaut était déjà correcte pour vous. N'est-ce pas ?

Vous avez réussi à définir l'encodage sur ISO-8859-1 avec votre paramètre de ligne de commande. Vous l'avez également défini par programme sur "Latin-1", mais ce n'est pas une valeur reconnue de l'encodage de fichier pour Java. Voir http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Lorsque vous faites cela, il semble que Charset se réinitialise à UTF-8, en regardant la source. Cela explique au moins la plupart des comportements.

Je ne sais pas pourquoi OutputStreamWriter affiche ISO8859_1. Il délègue à des classes sun.misc.* à code fermé. Je suppose qu'elle ne traite pas l'encodage par le même mécanisme, ce qui est bizarre.

Mais bien sûr, vous devriez toujours spécifier quel encodage vous voulez utiliser dans ce code. Je ne me fierais jamais à la valeur par défaut de la plate-forme.

3voto

Davy Jones Points 11

J'ai défini l'argument vm dans le serveur WAS comme -Dfile.encoding=UTF-8 pour modifier le jeu de caractères par défaut des serveurs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X