Jawaban yang jelas adalah dengan menggunakan Charset.defaultCharset()
tetapi kami baru-baru ini menemukan bahwa ini mungkin bukan jawaban yang tepat. Saya diberitahu bahwa hasilnya berbeda dari charset default nyata yang digunakan oleh kelas java.io dalam beberapa kesempatan. Sepertinya Java menyimpan 2 set rangkaian karakter default. Apakah ada yang punya wawasan tentang masalah ini?
Kami dapat mereproduksi satu kasus gagal. Ini semacam kesalahan pengguna tetapi mungkin masih mengekspos akar penyebab semua masalah lainnya. Ini kodenya,
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;
}
}
Server kami memerlukan charset default dalam Latin-1 untuk menangani beberapa encoding campuran (ANSI / Latin-1 / UTF-8) dalam protokol lama. Jadi semua server kami berjalan dengan parameter JVM ini,
-Dfile.encoding=ISO-8859-1
Inilah hasilnya di Java 5,
Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=UTF-8
Default Charset in Use=ISO8859_1
Seseorang mencoba mengubah runtime pengkodean dengan mengatur file.encoding dalam kode. Kita semua tahu itu tidak berhasil. Namun, ini tampaknya membuang defaultCharset () tetapi tidak mempengaruhi charset default nyata yang digunakan oleh OutputStreamWriter.
Apakah ini bug atau fitur?
EDIT: Jawaban yang diterima menunjukkan akar penyebab masalah. Pada dasarnya, Anda tidak dapat mempercayai defaultCharset () di Java 5, yang bukan merupakan pengkodean default yang digunakan oleh kelas I / O. Sepertinya Java 6 memperbaiki masalah ini.
sumber
Jawaban:
Ini benar-benar aneh ... Setelah disetel, Charset default di-cache dan tidak diubah saat kelas ada di memori. Mengatur
"file.encoding"
properti denganSystem.setProperty("file.encoding", "Latin-1");
tidak melakukan apa pun. Setiap kaliCharset.defaultCharset()
dipanggil, ia mengembalikan charset yang di-cache.Ini hasil saya:
Default Charset=ISO-8859-1 file.encoding=Latin-1 Default Charset=ISO-8859-1 Default Charset in Use=ISO8859_1
Saya menggunakan JVM 1.6.
(memperbarui)
Baik. Saya mereproduksi bug Anda dengan JVM 1.5.
Melihat kode sumber 1.5, kumpulan karakter default yang di-cache tidak disetel. Saya tidak tahu apakah ini bug atau bukan tetapi 1.6 mengubah implementasi ini dan menggunakan charset yang di-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; }
Saat Anda menyetel pengkodean file ke
file.encoding=Latin-1
saat Anda memanggil berikutnyaCharset.defaultCharset()
, yang terjadi adalah, karena charset default yang di-cache tidak disetel, ia akan mencoba menemukan charset yang sesuai untuk namanyaLatin-1
. Nama ini tidak ditemukan, karena salah, dan mengembalikan defaultUTF-8
.Adapun mengapa kelas IO seperti
OutputStreamWriter
mengembalikan hasil yang tidak diharapkan,implementasi
sun.nio.cs.StreamEncoder
(witch digunakan oleh kelas IO ini) berbeda juga untuk JVM 1.5 dan JVM 1.6. Implementasi JVM 1.6 didasarkan padaCharset.defaultCharset()
metode untuk mendapatkan pengkodean default, jika tidak disediakan untuk kelas IO. Implementasi JVM 1.5 menggunakan metode berbedaConverters.getDefaultEncodingName();
untuk mendapatkan charset default. Metode ini menggunakan cache-nya sendiri dari charset default yang disetel pada inisialisasi 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); }
Tapi saya setuju dengan komentarnya. Anda tidak boleh mengandalkan properti ini . Ini adalah detail implementasi.
sumber
Sepertinya perilaku tidak terdefinisi. Saya tahu bahwa, dalam praktiknya, Anda dapat mengubah pengkodean default menggunakan properti baris perintah, tetapi saya tidak berpikir apa yang terjadi ketika Anda melakukan ini ditentukan.
Bug ID: 4153515 tentang masalah pengaturan properti ini:
Saya merasa ngeri ketika melihat orang-orang mengatur pengkodean pada baris perintah - Anda tidak tahu kode apa yang akan mempengaruhi.
Jika Anda tidak ingin menggunakan encoding default, setel encoding yang Anda inginkan secara eksplisit melalui metode / konstruktor yang sesuai .
sumber
Pertama, Latin-1 sama dengan ISO-8859-1, jadi defaultnya sudah OK untuk Anda. Baik?
Anda berhasil menyetel encoding ke ISO-8859-1 dengan parameter baris perintah Anda. Anda juga menyetelnya secara terprogram ke "Latin-1", tapi, itu bukan nilai yang dikenali dari pengkodean file untuk Java. Lihat http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
Saat Anda melakukannya, sepertinya Charset disetel ulang ke UTF-8, dari melihat sumbernya. Setidaknya itu menjelaskan sebagian besar perilaku.
Saya tidak tahu mengapa OutputStreamWriter menampilkan ISO8859_1. Ini mendelegasikan ke kelas sun.misc. * Sumber tertutup. Saya menduga itu tidak cukup berurusan dengan pengkodean melalui mekanisme yang sama, yang aneh.
Tetapi tentu saja Anda harus selalu menentukan pengkodean apa yang Anda maksud dalam kode ini. Saya tidak pernah mengandalkan default platform.
sumber
Perilakunya tidak terlalu aneh. Melihat ke dalam implementasi kelas, ini disebabkan oleh:
Charset.defaultCharset()
tidak sedang meng-cache set karakter yang ditentukan di Java 5.Charset.defaultCharset()
lagi menyebabkan evaluasi kedua terhadap properti sistem, tidak ada kumpulan karakter dengan nama "Latin-1" yang ditemukan, jadiCharset.defaultCharset()
defaultnya adalah "UTF-8".OutputStreamWriter
adalah namun caching karakter default set dan mungkin digunakan sudah selama VM inisialisasi, sehingga karakter default set pengalihan nya dariCharset.defaultCharset()
jika sistem properti "file.encoding" telah berubah pada saat runtime.Seperti yang telah ditunjukkan, tidak didokumentasikan bagaimana VM harus berperilaku dalam situasi seperti itu. The
Charset.defaultCharset()
dokumentasi API tidak sangat tepat tentang bagaimana set karakter default ditentukan, hanya menyebutkan bahwa itu biasanya dilakukan pada VM startup, berdasarkan faktor-faktor seperti set karakter default OS atau lokal default.sumber
Saya telah menetapkan argumen vm di server WS sebagai -Dfile.encoding = UTF-8 untuk mengubah set karakter default server.
sumber
memeriksa
System.getProperty("sun.jnu.encoding")
tampaknya pengkodean yang sama dengan yang digunakan di baris perintah sistem Anda.
sumber