Jak znaleźć domyślny zestaw znaków / kodowanie w Javie?


92

Oczywistą odpowiedzią jest użycie, Charset.defaultCharset()ale niedawno odkryliśmy, że może to nie być właściwa odpowiedź. Powiedziano mi, że wynik różni się od rzeczywistego domyślnego zestawu znaków używanego przez klasy java.io w kilku przypadkach. Wygląda na to, że Java zachowuje 2 zestawy domyślnego zestawu znaków. Czy ktoś ma jakieś spostrzeżenia w tej sprawie?

Udało nam się odtworzyć jeden przypadek niepowodzenia. To rodzaj błędu użytkownika, ale nadal może ujawnić główną przyczynę wszystkich innych problemów. Oto kod,

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

Nasz serwer wymaga domyślnego zestawu znaków Latin-1, aby poradzić sobie z pewnym kodowaniem mieszanym (ANSI / Latin-1 / UTF-8) w starszym protokole. Wszystkie nasze serwery działają z tym parametrem maszyny JVM,

-Dfile.encoding=ISO-8859-1

Oto wynik na Javie 5,

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

Ktoś próbuje zmienić środowisko wykonawcze kodowania, ustawiając plik file.encoding w kodzie. Wszyscy wiemy, że to nie działa. Jednak to najwyraźniej wyrzuca defaultCharset (), ale nie wpływa na rzeczywisty domyślny zestaw znaków używany przez OutputStreamWriter.

Czy to błąd lub funkcja?

EDYCJA: Zaakceptowana odpowiedź przedstawia główną przyczynę problemu. Zasadniczo nie można ufać defaultCharset () w Javie 5, co nie jest domyślnym kodowaniem używanym przez klasy I / O. Wygląda na to, że Java 6 rozwiązuje ten problem.


To dziwne, ponieważ defaultCharset używa zmiennej statycznej, która jest ustawiana tylko raz (zgodnie z dokumentacją - podczas uruchamiania maszyny wirtualnej). Jakiego dostawcy maszyn wirtualnych używasz?
— Bozho

Udało mi się to odtworzyć na Javie 5, zarówno w Sun / Linux, jak i Apple / OS X.
— ZZ Coder

To wyjaśnia, dlaczego defaultCharset () nie zapisuje wyniku w pamięci podręcznej. Nadal muszę się dowiedzieć, jaki jest prawdziwy domyślny zestaw znaków używany przez klasy IO. Musi istnieć inny domyślny zestaw znaków w innym miejscu.
— ZZ Coder

@ZZ Coder, wciąż tego badam. Jedyne, co wiem, to to, że Charset.defaulyCharset () nie jest wywoływana z sun.nio.cs.StreamEncoder w JVM 1.5. W JVM 1.6 wywoływana jest metoda Charset.defaulyCharset (), która daje oczekiwane wyniki. Implementacja StreamEncodera JVM 1.5 w jakiś sposób buforuje poprzednie kodowanie.
— bruno conde

Odpowiedzi:


62

To naprawdę dziwne ... Po ustawieniu domyślny zestaw znaków jest przechowywany w pamięci podręcznej i nie jest zmieniany, gdy klasa jest w pamięci. Ustawienie "file.encoding"właściwości na System.setProperty("file.encoding", "Latin-1");nic nie robi. Za każdym razem, gdy Charset.defaultCharset()jest wywoływana, zwraca buforowany zestaw znaków.

Oto moje wyniki:

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

Jednak używam JVM 1.6.

(aktualizacja)

Ok. Odtworzyłem twój błąd w JVM 1.5.

Patrząc na kod źródłowy wersji 1.5, domyślny zestaw znaków w pamięci podręcznej nie jest ustawiany. Nie wiem, czy to błąd, czy nie, ale 1.6 zmienia tę implementację i używa buforowanego zestawu znaków:

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

Kiedy ustawisz kodowanie pliku file.encoding=Latin-1na następne wywołanie Charset.defaultCharset(), co się stanie, ponieważ buforowany domyślny zestaw znaków nie jest ustawiony, spróbuje znaleźć odpowiedni zestaw znaków dla nazwy Latin-1. Ta nazwa nie została znaleziona, ponieważ jest nieprawidłowa i zwraca wartość domyślną UTF-8.

Jeśli chodzi o przyczyny, dla których klasy IO, takie jak OutputStreamWriterzwracają nieoczekiwany wynik,
implementacja sun.nio.cs.StreamEncoder(która jest używana przez te klasy IO) jest inna dla JVM 1.5 i JVM 1.6. Implementacja JVM 1.6 jest oparta na Charset.defaultCharset()metodzie uzyskiwania domyślnego kodowania, jeśli nie zostało ono dostarczone do klas IO. Implementacja JVM 1.5 korzysta z innej metody Converters.getDefaultEncodingName();pobierania domyślnego zestawu znaków. Ta metoda używa własnej pamięci podręcznej domyślnego zestawu znaków, który jest ustawiany podczas inicjalizacji maszyny 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);
}

Ale zgadzam się z komentarzami. Nie powinieneś polegać na tej właściwości . To szczegół implementacji.


Aby odtworzyć ten błąd, musisz korzystać z języka Java 5, a domyślne kodowanie środowiska JRE to UTF-8.
— ZZ Coder

2
To jest pisanie do implementacji, a nie abstrakcja. Jeśli polegasz na nieudokumentowanych materiałach, nie zdziw się, jeśli Twój kod zepsuje się po uaktualnieniu do nowszej wersji platformy.
— McDowell

24

Czy to błąd lub funkcja?

Wygląda na niezdefiniowane zachowanie. Wiem, że w praktyce możesz zmienić domyślne kodowanie za pomocą właściwości wiersza poleceń, ale nie sądzę, co się stanie, gdy to zrobisz, jest zdefiniowane.

Identyfikator błędu: 4153515 dotyczący problemów z ustawieniem tej właściwości:

To nie jest błąd. Właściwość „file.encoding” nie jest wymagana przez specyfikację platformy J2SE; jest to wewnętrzny szczegół implementacji firmy Sun i nie powinien być sprawdzany ani modyfikowany przez kod użytkownika. Ma być również przeznaczony tylko do odczytu; jest technicznie niemożliwe, aby wspierać ustawienie tej właściwości na dowolne wartości w linii poleceń lub w jakimkolwiek innym momencie podczas wykonywania programu.

Preferowanym sposobem zmiany domyślnego kodowania używanego przez maszynę wirtualną i system wykonawczy jest zmiana ustawień regionalnych platformy bazowej przed uruchomieniem programu Java.

Wzdrygam się, gdy widzę, jak ludzie ustawiają kodowanie w wierszu poleceń - nie wiesz, jaki kod to wpłynie.

Jeśli nie chcesz używać domyślnego kodowania, ustaw żądane kodowanie jawnie za pomocą odpowiedniej metody / konstruktora .


4

Po pierwsze, Latin-1 jest tym samym, co ISO-8859-1, więc ustawienie domyślne było już dla Ciebie OK. Dobrze?

Pomyślnie ustawiłeś kodowanie na ISO-8859-1 za pomocą parametru wiersza poleceń. Ustawiasz również programowo na „Latin-1”, ale nie jest to rozpoznawana wartość kodowania pliku dla języka Java. Zobacz http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Kiedy to zrobisz, wygląda na to, że Charset resetuje się do UTF-8, patrząc na źródło. To przynajmniej wyjaśnia większość tego zachowania.

Nie wiem, dlaczego OutputStreamWriter wyświetla ISO8859_1. Deleguje do klas sun.misc. * O zamkniętym źródle. Domyślam się, że nie do końca radzi sobie z kodowaniem za pomocą tego samego mechanizmu, co jest dziwne.

Ale oczywiście zawsze powinieneś określać, jakie kodowanie masz na myśli w tym kodzie. Nigdy nie polegałem na domyślnej platformie.


4

To zachowanie nie jest tak dziwne. Patrząc na realizację zajęć, jest to spowodowane:

  • Charset.defaultCharset() nie buforuje określonego zestawu znaków w Javie 5.
  • Ustawienie właściwości systemowej „file.encoding” i Charset.defaultCharset()ponowne wywołanie powoduje drugą ocenę właściwości systemowej, nie znaleziono zestawu znaków o nazwie „Latin-1”, więc Charset.defaultCharset()domyślnie jest to „UTF-8”.
  • OutputStreamWriterJest jednak buforowanie domyślny zestaw znaków i jest prawdopodobnie już używany podczas inicjalizacji VM, tak że domyślny zestaw znaków przekazy od Charset.defaultCharset()jeśli właściwość systemu „file.encoding” został zmieniony w czasie wykonywania.

Jak już wspomniano, nie jest udokumentowane, jak maszyna wirtualna musi zachowywać się w takiej sytuacji. Dokumentacja Charset.defaultCharset()interfejsu API nie jest zbyt dokładna, jeśli chodzi o sposób określania domyślnego zestawu znaków, wspominając tylko, że jest to zwykle wykonywane podczas uruchamiania maszyny wirtualnej, w oparciu o takie czynniki, jak domyślny zestaw znaków systemu operacyjnego lub domyślne ustawienia regionalne.


3

Ustawiłem argument vm na serwerze WAS jako -Dfile.encoding = UTF-8, aby zmienić domyślny zestaw znaków serwera.


1

czek

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

wygląda na to, że kodowanie jest takie samo, jak to używane w linii poleceń twojego systemu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.