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


92

La risposta ovvia è da 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 reale utilizzato dalle classi java.io in diverse occasioni. Sembra che Java mantenga 2 set di set di caratteri predefiniti. Qualcuno ha qualche intuizione su questo problema?

Siamo stati in grado di riprodurre un caso fallito. È una specie di errore dell'utente, ma potrebbe comunque 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 un 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 cerca di cambiare il runtime di codifica impostando file.encoding nel codice. Sappiamo tutti che non funziona. Tuttavia, questo apparentemente getta defaultCharset () ma non influisce sul 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 utilizzata dalle classi I / O. Sembra che Java 6 risolva questo problema.


È strano, dal momento che defaultCharset utilizza una variabile statica che è impostata solo una volta (in base alla documentazione - all'avvio della VM). Quale fornitore di VM stai utilizzando?
Bozho

Sono stato in grado di riprodurlo su Java 5, sia su Sun / Linux che su Apple / OS X.
ZZ Coder

Questo spiega perché defaultCharset () non memorizza nella cache il risultato. Ho ancora bisogno di scoprire qual è il vero set di caratteri predefinito utilizzato dalle classi IO. Deve essere presente un altro set di caratteri predefinito nella cache da qualche altra parte.
ZZ Coder

@ZZ Coder, sto ancora facendo ricerche su questo. L'unico pensiero che so è che Charset.defaulyCharset () non viene chiamato da sun.nio.cs.StreamEncoder in JVM 1.5. Nella JVM 1.6 viene chiamato il metodo Charset.defaulyCharset () che fornisce i risultati attesi. L'implementazione JVM 1.5 di StreamEncoder sta memorizzando nella cache la codifica precedente, in qualche modo.
bruno conde

Risposte:


62

Questo è davvero strano ... Una volta impostato, il set di caratteri predefinito viene memorizzato nella cache e non viene modificato mentre la classe è in memoria. L'impostazione della "file.encoding"proprietà con System.setProperty("file.encoding", "Latin-1");non fa nulla. Ogni volta che Charset.defaultCharset()viene chiamato, 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

Tuttavia sto usando JVM 1.6.

(aggiornare)

Ok. Ho riprodotto il tuo bug con JVM 1.5.

Guardando il codice sorgente di 1.5, il charset predefinito memorizzato nella cache non è stato impostato. Non so se si tratta di un bug o meno, ma 1.6 modifica questa implementazione e utilizza il set di caratteri memorizzati 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 imposti la codifica del file alla file.encoding=Latin-1prossima chiamata Charset.defaultCharset(), ciò che accade è che, poiché il set di caratteri predefinito nella cache non è impostato, proverà a 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 OutputStreamWriterrestituiscono un risultato inaspettato,
l'implementazione di sun.nio.cs.StreamEncoder(witch è usata da queste classi IO) è diversa anche per JVM 1.5 e JVM 1.6. L'implementazione di JVM 1.6 si basa sul Charset.defaultCharset()metodo per ottenere la codifica predefinita, se non viene fornita alle classi 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 durante l'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.


Per riprodurre questo errore, devi essere su Java 5 e la tua codifica predefinita JRE deve essere UTF-8.
ZZ Coder

2
Questo è scrivere per l'implementazione, non per l'astrazione. Se fai affidamento su materiale non documentato, non sorprenderti se il tuo codice si rompe quando esegui l'aggiornamento a una versione più recente della piattaforma.
McDowell,

24

È un bug o una funzionalità?

Sembra un comportamento indefinito. So che, in pratica, puoi modificare la codifica predefinita utilizzando una proprietà della riga di comando, ma non penso che cosa succede quando lo fai sia definito.

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

Questo non è un bug. La proprietà "file.encoding" non è richiesta dalla specifica della piattaforma J2SE; è un dettaglio interno delle implementazioni di Sun e non dovrebbe essere esaminato o modificato dal codice utente. È anche inteso per essere di sola lettura; è tecnicamente impossibile 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 modificare la codifica predefinita utilizzata dalla VM e dal sistema di runtime è modificare la locale della piattaforma sottostante prima di avviare il programma Java.

Rabbrividisco quando vedo persone che impostano la codifica sulla riga di comando: non sai quale codice influenzerà.

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


4

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

Hai impostato correttamente la codifica su ISO-8859-1 con il parametro della riga di comando. Lo imposti anche a livello di programmazione su "Latin-1", ma questo 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 il set di caratteri si ripristini su UTF-8, guardando la sorgente. Questo almeno spiega la maggior parte del comportamento.

Non so perché OutputStreamWriter mostri ISO8859_1. Delega alle classi sun.misc. * Closed-source. Immagino che non abbia a che fare con la codifica tramite lo stesso meccanismo, il che è strano.

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


4

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.
  • L'impostazione della proprietà di sistema "file.encoding" e il richiamo Charset.defaultCharset()successivo provoca una seconda valutazione della proprietà di sistema, non viene trovato alcun set di caratteri con il nome "Latin-1", quindi il Charset.defaultCharset()valore predefinito è "UTF-8".
  • Tuttavia, OutputStreamWritersta memorizzando nella cache il set di caratteri predefinito ed è probabilmente utilizzato già durante l'inizializzazione della VM, in modo che il suo set di caratteri predefinito devia da Charset.defaultCharset()se la proprietà di sistema "file.encoding" è stata modificata in fase di esecuzione.

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


3

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


1

dai un'occhiata

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

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.