Wie finde ich den Standardzeichensatz / die Standardcodierung in Java?

92

Die offensichtliche Antwort ist zu verwenden, Charset.defaultCharset()aber wir haben kürzlich herausgefunden, dass dies möglicherweise nicht die richtige Antwort ist. Mir wurde gesagt, dass sich das Ergebnis von dem tatsächlichen Standardzeichensatz unterscheidet, der von java.io-Klassen bei mehreren Gelegenheiten verwendet wird. Java behält anscheinend zwei Sätze von Standardzeichensätzen bei. Hat jemand irgendwelche Einsichten zu diesem Thema?

Wir konnten einen Fehlerfall reproduzieren. Es ist eine Art Benutzerfehler, aber es kann immer noch die Hauptursache für alle anderen Probleme aufdecken. Hier ist der 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;
    }
}

Unser Server benötigt einen Standardzeichensatz in Latin-1, um mit einer gemischten Codierung (ANSI / Latin-1 / UTF-8) in einem Legacy-Protokoll umgehen zu können. Alle unsere Server werden also mit diesem JVM-Parameter ausgeführt.

-Dfile.encoding=ISO-8859-1

Hier ist das Ergebnis auf Java 5,

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

Jemand versucht, die Codierungslaufzeit zu ändern, indem er die Datei.encoding im Code festlegt. Wir alle wissen, dass das nicht funktioniert. Dies löst jedoch anscheinend defaultCharset () aus, hat jedoch keinen Einfluss auf den von OutputStreamWriter verwendeten tatsächlichen Standardzeichensatz.

Ist das ein Fehler oder eine Funktion?

BEARBEITEN: Die akzeptierte Antwort zeigt die Grundursache des Problems. Grundsätzlich können Sie defaultCharset () in Java 5 nicht vertrauen. Dies ist nicht die Standardcodierung, die von E / A-Klassen verwendet wird. Java 6 behebt dieses Problem anscheinend.

ZZ Coder
quelle
Das ist seltsam, da das defaultCharset eine statische Variable verwendet, die nur einmal festgelegt wird (gemäß den Dokumenten - beim Start der VM). Welchen VM-Anbieter verwenden Sie?
Bozho
Ich konnte dies auf Java 5 reproduzieren, sowohl unter Sun / Linux als auch unter Apple / OS X.
ZZ Coder
Dies erklärt, warum defaultCharset () das Ergebnis nicht zwischenspeichert. Ich muss noch herausfinden, welcher Standard-Zeichensatz von E / A-Klassen verwendet wird. An anderer Stelle muss ein anderer Standardzeichensatz zwischengespeichert sein.
ZZ Coder
@ZZ Coder, ich recherchiere noch darüber. Ich denke nur, dass Charset.defaulyCharset () in JVM 1.5 nicht von sun.nio.cs.StreamEncoder aufgerufen wird. In JVM 1.6 wird die Methode Charset.defaulyCharset () aufgerufen, die die erwarteten Ergebnisse liefert. Die JVM 1.5-Implementierung von StreamEncoder speichert die vorherige Codierung irgendwie zwischen.
Bruno Conde

Antworten:

62

Das ist wirklich seltsam ... Einmal festgelegt, wird der Standardzeichensatz zwischengespeichert und nicht geändert, während sich die Klasse im Speicher befindet. Das Festlegen der "file.encoding"Eigenschaft mit System.setProperty("file.encoding", "Latin-1");bewirkt nichts. Bei jedem Charset.defaultCharset()Aufruf wird der zwischengespeicherte Zeichensatz zurückgegeben.

Hier sind meine Ergebnisse:

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

Ich benutze allerdings JVM 1.6.

(aktualisieren)

OK. Ich habe Ihren Fehler mit JVM 1.5 reproduziert.

Wenn Sie sich den Quellcode 1.5 ansehen, wird der zwischengespeicherte Standardzeichensatz nicht festgelegt. Ich weiß nicht, ob dies ein Fehler ist oder nicht, aber 1.6 ändert diese Implementierung und verwendet den zwischengespeicherten Zeichensatz:

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;
}

Wenn Sie die Dateicodierung auf file.encoding=Latin-1den nächsten Aufruf Charset.defaultCharset()einstellen, geschieht Folgendes: Da der zwischengespeicherte Standardzeichensatz nicht festgelegt ist, wird versucht, den entsprechenden Zeichensatz für den Namen zu finden Latin-1. Dieser Name wird nicht gefunden, da er falsch ist, und gibt den Standard zurück UTF-8.

Was die Gründe angeht, warum die E / A-Klassen beispielsweise OutputStreamWriterein unerwartetes Ergebnis zurückgeben, unterscheidet sich
die Implementierung von sun.nio.cs.StreamEncoder(die von diesen E / A-Klassen verwendet wird) auch für JVM 1.5 und JVM 1.6. Die JVM 1.6-Implementierung basiert auf der Charset.defaultCharset()Methode zum Abrufen der Standardcodierung, wenn keine für E / A-Klassen bereitgestellt wird. Die JVM 1.5-Implementierung verwendet eine andere Methode Converters.getDefaultEncodingName();, um den Standardzeichensatz abzurufen. Diese Methode verwendet einen eigenen Cache des Standardzeichensatzes, der bei der JVM-Initialisierung festgelegt wird:

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);
}

Aber ich stimme den Kommentaren zu. Sie sollten sich nicht auf diese Eigenschaft verlassen . Es ist ein Implementierungsdetail.

bruno conde
quelle
Um diesen Fehler zu reproduzieren, müssen Sie sich in Java 5 befinden und Ihre JRE-Standardcodierung muss UTF-8 sein.
ZZ Coder
2
Dies schreibt an die Implementierung, nicht an die Abstraktion. Wenn Sie sich auf undokumentierte Inhalte verlassen, wundern Sie sich nicht, wenn Ihr Code beim Upgrade auf eine neuere Version der Plattform beschädigt wird.
McDowell
24

Ist das ein Fehler oder eine Funktion?

Sieht nach undefiniertem Verhalten aus. Ich weiß, dass Sie in der Praxis die Standardcodierung mithilfe einer Befehlszeileneigenschaft ändern können, aber ich denke nicht, dass definiert ist, was passiert, wenn Sie dies tun.

Fehler-ID: 4153515 bei Problemen beim Festlegen dieser Eigenschaft:

Dies ist kein Fehler. Die Eigenschaft "file.encoding" wird von der J2SE-Plattformspezifikation nicht benötigt. Es ist ein internes Detail der Implementierungen von Sun und sollte nicht durch Benutzercode überprüft oder geändert werden. Es soll auch schreibgeschützt sein. Es ist technisch unmöglich, die Einstellung dieser Eigenschaft auf beliebige Werte in der Befehlszeile oder zu einem anderen Zeitpunkt während der Programmausführung zu unterstützen.

Die bevorzugte Methode zum Ändern der von der VM und dem Laufzeitsystem verwendeten Standardcodierung besteht darin, das Gebietsschema der zugrunde liegenden Plattform zu ändern, bevor Sie Ihr Java-Programm starten.

Ich erschrecke, wenn ich sehe, dass Leute die Codierung in der Befehlszeile einstellen - Sie wissen nicht, welchen Code dies beeinflussen wird.

Wenn Sie die Standardcodierung nicht verwenden möchten, legen Sie die gewünschte Codierung explizit über die entsprechende Methode / den entsprechenden Konstruktor fest .

McDowell
quelle
4

Erstens ist Latin-1 dasselbe wie ISO-8859-1, daher war die Standardeinstellung für Sie bereits in Ordnung. Richtig?

Sie haben die Codierung mit Ihrem Befehlszeilenparameter erfolgreich auf ISO-8859-1 eingestellt. Sie setzen es auch programmgesteuert auf "Latin-1", aber das ist kein anerkannter Wert einer Dateicodierung für Java. Siehe http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Wenn Sie dies tun, sieht es so aus, als würde Charset auf UTF-8 zurückgesetzt, wenn Sie sich die Quelle ansehen. Das erklärt zumindest den größten Teil des Verhaltens.

Ich weiß nicht, warum OutputStreamWriter ISO8859_1 anzeigt. Es delegiert an geschlossene sun.misc. * -Klassen. Ich vermute, es geht nicht ganz darum, über denselben Mechanismus zu codieren, was seltsam ist.

Aber natürlich sollten Sie immer angeben, welche Codierung Sie in diesem Code meinen. Ich würde mich nie auf die Plattform-Standardeinstellung verlassen.

Sean Owen
quelle
4

Das Verhalten ist nicht wirklich so seltsam. Ein Blick auf die Implementierung der Klassen wird verursacht durch:

  • Charset.defaultCharset() speichert den ermittelten Zeichensatz in Java 5 nicht zwischen.
  • Das Setzen der Systemeigenschaft "file.encoding" und das Charset.defaultCharset()erneute Aufrufen führt zu einer zweiten Auswertung der Systemeigenschaft. Es wurde kein Zeichensatz mit dem Namen "Latin-1" gefunden, daher wird Charset.defaultCharset()standardmäßig "UTF-8" verwendet.
  • Das OutputStreamWriterspeichert jedoch den Standardzeichensatz zwischen und wird wahrscheinlich bereits während der VM-Initialisierung verwendet, sodass der Standardzeichensatz davon Charset.defaultCharset()abweicht, wenn die Systemeigenschaft "file.encoding" zur Laufzeit geändert wurde.

Wie bereits erwähnt, ist nicht dokumentiert, wie sich die VM in einer solchen Situation verhalten muss. In der Charset.defaultCharset()API-Dokumentation wird nicht sehr genau festgelegt, wie der Standardzeichensatz ermittelt wird. Es wird lediglich erwähnt, dass dies normalerweise beim Start der VM erfolgt, basierend auf Faktoren wie dem Standardzeichensatz des Betriebssystems oder dem Standardgebietsschema.

jarnbjo
quelle
3

Ich habe das vm-Argument im WAS-Server als -Dfile.encoding = UTF-8 festgelegt, um den Standardzeichensatz des Servers zu ändern.

Davy Jones
quelle
1

prüfen

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

Es scheint dieselbe Codierung zu sein wie die in der Befehlszeile Ihres Systems verwendete.

neoedmund
quelle