web-dev-qa-db-fra.com

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

La réponse évidente consiste à 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 est différent du jeu de caractères par défaut utilisé par les classes Java.io à plusieurs reprises. On dirait que Java conserve deux jeux de caractères par défaut. Quelqu'un at-il des idées à ce sujet?

Nous avons pu reproduire un cas d'échec. C'est une sorte d'erreur d'utilisateur, mais cela peut toujours exposer la cause fondamentale 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 le jeu de caractères par défaut en Latin-1 pour traiter 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 essaie de changer le runtime d'encodage en définissant le fichier.encoding dans le code. Nous savons tous que cela ne fonctionne pas. Cependant, cela supprime apparemment defaultCharset () mais cela 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 indique 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.

88
ZZ Coder

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éfinir la propriété "file.encoding" Avec System.setProperty("file.encoding", "Latin-1"); ne fait rien. Chaque fois que Charset.defaultCharset() est appelé, il retourne 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.

(mise à 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 mis en cache par défaut n'est pas défini. Je ne sais pas s'il s'agit d'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 du fichier sur file.encoding=Latin-1 La prochaine fois que vous appelez Charset.defaultCharset(), le résultat est le suivant: le jeu de caractères mis en cache par défaut n'étant pas défini, il essaiera de trouver le jeu de caractères approprié pour le fichier. nom Latin-1. Ce nom est introuvable, car il est incorrect, et renvoie la valeur par défaut UTF-8.

Pourquoi les classes IO telles que OutputStreamWriter renvoient-elles un résultat inattendu,
L’implémentation de Sun.nio.cs.StreamEncoder (qui est utilisé par ceux-ci IO classes) est également différente pour JVM 1.5 et JVM 1.6. L’implémentation de 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 de la machine virtuelle Java 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 de mise en œuvre.

62
bruno conde

Est-ce un bug ou une fonctionnalité?

On dirait un comportement indéfini. Je sais que, dans la pratique, vous pouvez modifier le codage par défaut à l'aide d'une propriété de ligne de commande, mais je ne pense pas que ce qui se passe lorsque vous faites cela est défini.

Bug ID: 4153515 Problèmes lors de la définition 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 plate-forme J2SE; c'est un détail interne des implémentations de Sun et ne doit pas être examiné ni modifié par le code de l'utilisateur. Il est également destiné à être en lecture seule; Il est techniquement impossible de prendre en charge le paramétrage de cette propriété sur des valeurs arbitraires sur la ligne de commande ou à tout autre moment de l'exécution du programme.

La méthode recommandée pour modifier le codage par défaut utilisé par VM et le système d’exécution) consiste à modifier les paramètres régionaux de la plate-forme sous-jacente avant de démarrer votre programme Java.

Je m'émerveille lorsque je vois des personnes définir l'encodage sur la ligne de commande - vous ne savez pas quel code cela va affecter.

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

24
McDowell

Premièrement, Latin-1 est identique à ISO-8859-1, la valeur par défaut vous convenait déjà. Droite?

Vous avez correctement défini le codage sur ISO-8859-1 avec votre paramètre de ligne de commande. Vous le définissez également 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, on dirait que Charset a été réinitialisé sur UTF-8 et non sur la source. Cela explique au moins la plupart des comportements.

Je ne sais pas pourquoi OutputStreamWriter affiche ISO8859_1. Il délègue aux classes Sun.misc. * À source fermée. J'imagine qu'il ne s'agit pas tout à fait de l'encodage via le même mécanisme, ce qui est étrange.

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

4
Sean Owen

Le comportement n'est pas vraiment étrange. En regardant dans l'implémentation des classes, cela est causé par:

  • Charset.defaultCharset() ne met pas en cache le jeu de caractères déterminé dans Java 5.
  • La définition de la propriété système "file.encoding" et l'appel de Charset.defaultCharset() provoquent à nouveau une deuxième évaluation de la propriété système. Aucun jeu de caractères portant le nom "latin-1" n'a été trouvé, donc Charset.defaultCharset() La valeur par défaut est "UTF-8".
  • Le OutputStreamWriter met toutefois en cache le jeu de caractères par défaut et est probablement déjà utilisé lors de l'initialisation VM, de sorte que son jeu de caractères par défaut dévie de Charset.defaultCharset() si le système La propriété "fichier.encodage" a été modifiée à l'exécution.

Comme indiqué plus haut, la manière dont le VM doit se comporter dans une telle situation) n'est pas documentée. La documentation de l'API Charset.defaultCharset() n'est pas très précise sur la détermination du jeu de caractères par défaut. , en mentionnant seulement que cela se fait généralement au démarrage 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.

4
jarnbjo

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

3
Davy Jones

vérifier

System.getProperty("Sun.jnu.encoding")

il semble que ce soit le même encodage que celui utilisé dans la ligne de commande de votre système.

0
neoedmund