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.
źródło
Odpowiedzi:
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 naSystem.setProperty("file.encoding", "Latin-1");
nic nie robi. Za każdym razem, gdyCharset.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-1
na następne wywołanieCharset.defaultCharset()
, co się stanie, ponieważ buforowany domyślny zestaw znaków nie jest ustawiony, spróbuje znaleźć odpowiedni zestaw znaków dla nazwyLatin-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
OutputStreamWriter
zwracają 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 naCharset.defaultCharset()
metodzie uzyskiwania domyślnego kodowania, jeśli nie zostało ono dostarczone do klas IO. Implementacja JVM 1.5 korzysta z innej metodyConverters.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.
źródło
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:
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 .
źródło
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.
źródło
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.Charset.defaultCharset()
ponowne wywołanie powoduje drugą ocenę właściwości systemowej, nie znaleziono zestawu znaków o nazwie „Latin-1”, więcCharset.defaultCharset()
domyślnie jest to „UTF-8”.OutputStreamWriter
Jest jednak buforowanie domyślny zestaw znaków i jest prawdopodobnie już używany podczas inicjalizacji VM, tak że domyślny zestaw znaków przekazy odCharset.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.źródło
Ustawiłem argument vm na serwerze WAS jako -Dfile.encoding = UTF-8, aby zmienić domyślny zestaw znaków serwera.
źródło
czek
System.getProperty("sun.jnu.encoding")
wygląda na to, że kodowanie jest takie samo, jak to używane w linii poleceń twojego systemu.
źródło