Come trovare il set di caratteri / codifica predefinito in Java?

La risposta ovvia è usare Charset.defaultCharset() ma recentemente abbiamo scoperto che questa potrebbe non essere la risposta giusta. Mi è stato detto che il risultato è diverso dal set di caratteri predefinito utilizzato dalle classi java.io in diverse occasioni. Sembra che Java conservi 2 set di caratteri predefiniti. Qualcuno ha qualche approfondimento su questo problema?

Siamo stati in grado di riprodurre un caso di errore. È una specie di errore dell’utente, ma può ancora esporre la causa principale di tutti gli altri problemi. Ecco il codice,

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

Il nostro server richiede il set di caratteri predefinito in Latin-1 per gestire alcune codifiche miste (ANSI / Latin-1 / UTF-8) in un protocollo legacy. Quindi tutti i nostri server funzionano con questo parametro JVM,

 -Dfile.encoding=ISO-8859-1 

Ecco il risultato su Java 5,

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

Qualcuno prova a cambiare il runtime di codifica impostando il file.encoding nel codice. Sappiamo tutti che non funziona. Tuttavia, questo apparentemente elimina defaultCharset () ma non influenza il set di caratteri predefinito reale utilizzato da OutputStreamWriter.

È un bug o una funzionalità?

EDIT: la risposta accettata mostra la causa principale del problema. Fondamentalmente, non puoi fidarti di defaultCharset () in Java 5, che non è la codifica predefinita usata dalle classi I / O. Sembra che Java 6 risolva questo problema.

Questo è davvero strano … Una volta impostato, il set di caratteri predefinito viene memorizzato nella cache e non viene modificato mentre la class è in memoria. Impostazione della proprietà "file.encoding" con System.setProperty("file.encoding", "Latin-1"); non fa nulla. Ogni volta che viene chiamato Charset.defaultCharset() restituisce il set di caratteri memorizzato nella cache.

Ecco i miei risultati:

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

Sto usando JVM 1.6 però.

(aggiornare)

Ok. Ho riprodotto il tuo bug con JVM 1.5.

Guardando il codice sorgente di 1.5, il set di caratteri di default memorizzato nella cache non viene impostato. Non so se si tratta di un bug o meno, ma 1.6 modifica questa implementazione e utilizza il set di caratteri nella 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; } 

Quando si imposta la codifica del file su file.encoding=Latin-1 la prossima volta che si chiama Charset.defaultCharset() , ciò che accade è, poiché il charset predefinito memorizzato nella cache non è impostato, cercherà di trovare il set di caratteri appropriato per il nome Latin-1 . Questo nome non viene trovato, perché non è corretto e restituisce il valore predefinito UTF-8 .

Per quanto riguarda il motivo per cui le classi IO come OutputStreamWriter restituiscono un risultato inaspettato,
l’implementazione di sun.nio.cs.StreamEncoder (usata da queste classi IO) è diversa anche per JVM 1.5 e JVM 1.6. L’implementazione di JVM 1.6 si basa sul metodo Charset.defaultCharset() per ottenere la codifica predefinita, se non viene fornita una class IO. L’implementazione di JVM 1.5 utilizza un metodo diverso Converters.getDefaultEncodingName(); per ottenere il set di caratteri predefinito. Questo metodo utilizza la propria cache del set di caratteri predefinito impostato sull’inizializzazione 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); } 

Ma sono d’accordo con i commenti. Non dovresti fare affidamento su questa proprietà . È un dettaglio di implementazione.

È un bug o una funzionalità?

Sembra un comportamento indefinito. So che, in pratica, puoi modificare la codifica predefinita usando una proprietà della riga di comando, ma non penso che ciò che accade quando lo fai venga definito.

ID bug: 4153515 sui problemi di impostazione di questa proprietà:

Questo non è un bug. La proprietà “file.encoding” non è richiesta dalle specifiche della piattaforma J2SE; è un dettaglio interno delle implementazioni di Sun e non deve essere esaminato o modificato dal codice utente. È anche inteso per essere di sola lettura; è tecnicamente imansible supportare l’impostazione di questa proprietà su valori arbitrari sulla riga di comando o in qualsiasi altro momento durante l’esecuzione del programma.

Il modo migliore per cambiare la codifica predefinita utilizzata dalla VM e dal sistema di runtime è modificare le impostazioni locali della piattaforma sottostante prima di avviare il programma Java.

Mi fa rabbrividire quando vedo le persone che impostano la codifica sulla riga di comando: non si sa quale codice influenzerà.

Se non si desidera utilizzare la codifica predefinita, impostare la codifica desiderata in modo esplicito tramite il metodo / costruttore appropriato.

Innanzitutto, Latin-1 è uguale a ISO-8859-1, quindi l’impostazione predefinita era già OK per te. Destra?

La codifica è stata impostata con successo su ISO-8859-1 con il parametro della riga di comando. Si imposta anche programmaticamente su “Latin-1”, ma non è un valore riconosciuto di una codifica di file per Java. Vedi http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Quando lo fai, sembra che Charset si reimposti su UTF-8, osservando la fonte. Questo almeno spiega la maggior parte del comportamento.

Non so perché OutputStreamWriter mostri ISO8859_1. Delega a classi sun.misc. * Chiuse. Suppongo che non si tratti di codifica attraverso lo stesso meccanismo, il che è strano.

Ma ovviamente dovresti sempre specificare quale codifica intendi in questo codice. Non farei mai affidamento sul default della piattaforma.

Il comportamento non è poi così strano. Esaminando l’implementazione delle classi, è causato da:

  • Charset.defaultCharset () non memorizza nella cache il set di caratteri determinato in Java 5.
  • Impostando la proprietà di sistema “file.encoding” e richiamando Charset.defaultCharset () nuovamente una seconda valutazione della proprietà di sistema, non viene trovato alcun set di caratteri con il nome “Latin-1”, quindi Charset.defaultCharset imposta come predefinito “UTF-8 “.
  • OutputStreamWriter tuttavia memorizza nella cache il set di caratteri predefinito ed è probabilmente già utilizzato durante l’inizializzazione della VM, in modo che il suo set di caratteri predefinito diventi da Charset.defaultCharset () se la proprietà di sistema “file.encoding” è stata modificata in fase di runtime.

Come già sottolineato, non è documentato come la VM debba comportarsi in tale situazione. La documentazione dell’API Charset.defaultCharset () non è molto precisa su come viene determinato il set di caratteri predefinito, menzionando solo che viene solitamente eseguito all’avvio della VM, in base a fattori come il set di caratteri predefinito del sistema operativo o le impostazioni internazionali predefinite.

Ho impostato l’argomento vm nel server WAS come -Dfile.encoding = UTF-8 per modificare il set di caratteri predefinito dei server.

dai un’occhiata

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

sembra essere la stessa codifica utilizzata nella riga di comando del sistema.