java - spéciaux - system.setproperty( "utf-8");




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

La réponse évidente est d'utiliser Charset.defaultCharset() mais nous avons récemment découvert que cela pourrait ne pas être la bonne réponse. On m'a dit que le résultat est différent du vrai jeu de caractères par défaut utilisé par les classes java.io à plusieurs occasions. On dirait que Java conserve 2 jeux de charset par défaut. Quelqu'un at-il des idées sur ce problème?

Nous avons été en mesure de reproduire un cas d'échec. C'est une sorte d'erreur de l'utilisateur, mais il peut encore exposer la cause première 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 dans Latin-1 pour gérer un codage mixte (ANSI / Latin-1 / UTF-8) dans un protocole hérité. Donc, tous nos serveurs fonctionnent avec ce paramètre 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 tente de modifier l'exécution de l'encodage en définissant le fichier file.encoding dans le code. Nous savons tous que cela ne fonctionne pas. Cependant, cela semble supprimer defaultCharset () mais n'affecte pas le jeu de caractères par défaut réel utilisé par OutputStreamWriter.

Est-ce un bug ou une fonctionnalité?

EDIT: La réponse acceptée montre la cause première du problème. Fondamentalement, vous ne pouvez pas faire confiance à defaultCharset () dans Java 5, qui n'est pas le codage par défaut utilisé par les classes d'E / S. On dirait que Java 6 corrige ce problème.


Est-ce un bug ou une fonctionnalité?

Ressemble à un comportement indéfini. Je sais que, en pratique, vous pouvez changer le codage 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 ceci est défini.

Bug ID: 4153515 sur les problèmes de réglage de cette propriété:

Ce n'est pas un bug. La propriété "file.encoding" n'est pas requise par la spécification de la plateforme J2SE; c'est un détail interne des implémentations de Sun et ne doit pas être examiné ou modifié par le code utilisateur. Il est également destiné à être en lecture seule; il est techniquement impossible de prendre en charge le paramétrage de cette propriété à des valeurs arbitraires sur la ligne de commande ou à tout autre moment pendant l'exécution du programme.

La meilleure façon de modifier le codage par défaut utilisé par la machine virtuelle et le système d'exécution consiste à modifier les paramètres régionaux de la plateforme sous-jacente avant de démarrer votre programme Java.

Je grince des dents quand je vois des gens qui définissent l'encodage sur la ligne de commande - vous ne savez pas quel code va affecter.

Si vous ne voulez pas utiliser le codage par défaut, définissez le codage que vous voulez explicitement via la méthode / constructor approprié.


C'est vraiment étrange ... Une fois défini, le jeu de caractères par défaut est mis en cache et il n'est pas modifié tant que la classe est en mémoire. Définition de la propriété "file.encoding" avec System.setProperty("file.encoding", "Latin-1"); ne fait rien. Chaque fois que Charset.defaultCharset() est appelée, elle 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 JVM 1.6 cependant.

(mettre à jour)

D'accord. J'ai reproduit votre bogue avec JVM 1.5.

En regardant le code source de 1.5, le jeu de caractères par défaut mis en cache n'est pas défini. Je ne sais pas si c'est un bogue ou non mais 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 le codage de fichier sur file.encoding=Latin-1 la prochaine fois que vous appelez Charset.defaultCharset() , ce qui se passe, car le jeu de caractères par défaut en cache n'est pas défini, il tente de trouver le jeu de caractères approprié pour le nom Latin-1 . Ce nom est introuvable car il est incorrect et renvoie l' UTF-8 par défaut.

Quant à savoir pourquoi les classes d'E / S telles que OutputStreamWriter renvoient un résultat inattendu,
l'implémentation de sun.nio.cs.StreamEncoder (utilisée par ces classes d'E / S) est également différente pour JVM 1.5 et JVM 1.6. L'implémentation JVM 1.6 est basée sur la méthode Charset.defaultCharset() pour obtenir le codage par défaut, si aucun n'est fourni aux classes IO. L'implémentation 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 défini lors de l'initialisation de la machine virtuelle Java:

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 devriez pas compter sur cette propriété . C'est un détail d'implémentation.


Le comportement n'est pas vraiment étrange. En regardant dans la mise en œuvre des classes, 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 appeler à nouveau Charset.defaultCharset () provoque 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 utilise par défaut "UTF-8 ".
  • Le 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 détourne Charset.defaultCharset () si la propriété système "file.encoding" a été modifiée à l'exécution.

Comme déjà indiqué, il n'est pas documenté comment la VM doit se comporter dans une telle situation. La documentation de l'API Charset.defaultCharset () n'est pas très précise sur la façon dont le jeu de caractères par défaut est déterminé, mentionnant seulement que cela est généralement fait au démarrage de la VM, en fonction de facteurs tels que le jeu de caractères par défaut ou les paramètres régionaux par défaut.


Premièrement, Latin-1 est le même que ISO-8859-1, donc, la valeur par défaut était déjà OK pour vous. Droite?

Vous avez correctement défini le codage 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 d'un codage de fichier pour Java. Voir http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Lorsque vous faites cela, ressemble à réinitialiser Charset à UTF-8, à partir de la source. Cela explique au moins la plupart du comportement.

Je ne sais pas pourquoi OutputStreamWriter montre ISO8859_1. Il délègue aux classes sun.misc. * Fermées. Je suppose qu'il ne s'agit pas vraiment d'encodage via le même mécanisme, ce qui est bizarre.

Mais bien sûr, vous devriez toujours spécifier quel encodage vous voulez dire dans ce code. Je ne compterais jamais sur la plate-forme par défaut.







character-encoding