Java'da Varsayılan Karakter Seti / Kodlama Nasıl Bulunur?


92

Açık cevap kullanmaktır, Charset.defaultCharset()ancak yakın zamanda bunun doğru cevap olmayabileceğini öğrendik. Bana sonucun java.io sınıfları tarafından birkaç durumda kullanılan gerçek varsayılan karakter setinden farklı olduğu söylendi. Görünüşe göre Java, 2 set varsayılan karakter kümesini tutuyor. Bu konuyla ilgili herhangi bir fikri olan var mı?

Bir başarısızlık durumunu yeniden oluşturabildik. Bu bir tür kullanıcı hatasıdır, ancak yine de diğer tüm sorunların temel nedenini ortaya çıkarabilir. İşte 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;
    }
}

Sunucumuz, eski bir protokoldeki bazı karma kodlamalarla (ANSI / Latin-1 / UTF-8) ilgilenmek için Latin-1'de varsayılan karakter kümesini gerektirir. Dolayısıyla tüm sunucularımız bu JVM parametresiyle çalışır,

-Dfile.encoding=ISO-8859-1

İşte Java 5'teki sonuç,

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

Birisi kodda file.encoding ayarını yaparak kodlama çalışma zamanını değiştirmeye çalışır. Hepimiz bunun işe yaramadığını biliyoruz. Ancak, bu görünüşe göre defaultCharset () 'i atar, ancak OutputStreamWriter tarafından kullanılan gerçek varsayılan karakter kümesini etkilemez.

Bu bir hata mı yoksa özellik mi?

DÜZENLEME: Kabul edilen yanıt, sorunun temel nedenini gösterir. Temel olarak, G / Ç sınıfları tarafından kullanılan varsayılan kodlama olmayan Java 5'te defaultCharset () 'e güvenemezsiniz. Görünüşe göre Java 6 bu sorunu düzeltir.


Bu garip, çünkü defaultCharset yalnızca bir kez ayarlanan statik bir değişken kullanıyor (belgelere göre - VM başlangıcında). Hangi Sanal Makine Satıcısını kullanıyorsunuz?
Bozho

Bunu hem Sun / Linux hem de Apple / OS X üzerinde Java 5'te yeniden oluşturabildim.
ZZ Coder

Bu, defaultCharset () işlevinin sonucu neden önbelleğe almadığını açıklar. Hala IO sınıfları tarafından kullanılan gerçek varsayılan karakter setinin ne olduğunu bulmam gerekiyor. Başka bir yerde önbelleğe alınmış başka bir varsayılan karakter kümesi olmalıdır.
ZZ Coder

@ZZ Coder, hala bunun üzerinde araştırma yapıyorum. Bildiğim tek düşünce, JVM 1.5'te Charset.defaulyCharset () 'in sun.nio.cs.StreamEncoder'dan çağrılmadığıdır. JVM 1.6'da Charset.defaulyCharset () yöntemi çağrılır ve beklenen sonuçları verir. StreamEncoder'ın JVM 1.5 uygulaması, bir şekilde önceki kodlamayı önbelleğe alıyor.
bruno conde

Yanıtlar:


62

Bu gerçekten garip ... Bir kez ayarlandıktan sonra, varsayılan Karakter seti önbelleğe alınır ve sınıf hafızadayken değiştirilmez. "file.encoding"Özelliği ile ayarlamak System.setProperty("file.encoding", "Latin-1");hiçbir şey yapmaz. Her Charset.defaultCharset()çağrıldığında önbelleğe alınmış karakter kümesini döndürür.

İşte sonuçlarım:

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

Yine de JVM 1.6 kullanıyorum.

(Güncelleme)

Tamam. Hatanızı JVM 1.5 ile yeniden oluşturdum.

1.5 kaynak koduna bakıldığında, önbelleğe alınan varsayılan karakter kümesi ayarlanmıyor. Bunun bir hata olup olmadığını bilmiyorum ama 1.6 bu uygulamayı değiştiriyor ve önbelleğe alınmış karakter kümesini kullanıyor:

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

Dosya kodlamasını bir file.encoding=Latin-1sonraki aramaya Charset.defaultCharset()ayarladığınızda, önbelleğe alınan varsayılan karakter kümesi ayarlanmadığı için, ad için uygun karakter kümesini bulmaya çalışacaktır Latin-1. Bu ad bulunamadı, çünkü yanlıştır ve varsayılanı döndürür UTF-8.

ES sınıfları gibi neden gelince OutputStreamWriterbeklenmedik bir sonuç dönüş
uygulanması sun.nio.cs.StreamEncoder(cadı bu ES sınıfları tarafından kullanılır) de JVM 1.5 ve JVM 1.6 olarak farklıdır. JVM 1.6 uygulaması, Charset.defaultCharset()GÇ sınıflarına sağlanmamışsa, varsayılan kodlamayı alma yöntemine dayanır . JVM 1.5 uygulaması Converters.getDefaultEncodingName();, varsayılan karakter kümesini almak için farklı bir yöntem kullanır . Bu yöntem, JVM başlatıldığında ayarlanan varsayılan karakter kümesinin kendi önbelleğini kullanır:

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

Ama yorumlara katılıyorum. Sen bu özelliği güvenmemelisiniz . Bu bir uygulama detayıdır.


Bu hatayı yeniden oluşturmak için, Java 5'te olmanız ve JRE varsayılan kodlamanızın UTF-8 olması gerekir.
ZZ Coder

2
Bu, soyutlamaya değil uygulamaya yazmaktır. Belgelenmemiş şeylere güveniyorsanız, platformun daha yeni bir sürümüne yükselttiğinizde kodunuz kırılırsa şaşırmayın.
McDowell

24

Bu bir hata mı yoksa özellik mi?

Tanımlanmamış bir davranışa benziyor. Pratikte, bir komut satırı özelliğini kullanarak varsayılan kodlamayı değiştirebileceğinizi biliyorum, ancak bunu yaptığınızda ne olacağı tanımlı sanmıyorum.

Bu özelliği ayarlama sorunları hakkında Hata Kimliği: 4153515 :

Bu bir hata değil. "File.encoding" özelliği J2SE platform spesifikasyonu tarafından gerekli değildir; bu, Sun uygulamalarının dahili bir detayıdır ve kullanıcı kodu ile incelenmemeli veya değiştirilmemelidir. Ayrıca salt okunur olması amaçlanmıştır; Bu özelliğin, komut satırında veya program yürütülürken başka herhangi bir zamanda rasgele değerlere ayarlanmasını desteklemek teknik olarak imkansızdır.

Sanal makine ve çalışma zamanı sistemi tarafından kullanılan varsayılan kodlamayı değiştirmenin tercih edilen yolu, Java programınızı başlatmadan önce temel alınan platformun yerel ayarını değiştirmektir.

Kodlamayı komut satırında ayarlayan insanları gördüğümde utanıyorum - hangi kodu etkileyeceğini bilmiyorsunuz.

Varsayılan kodlamayı kullanmak istemiyorsanız, istediğiniz kodlamayı uygun yöntem / yapıcı aracılığıyla açıkça ayarlayın .


4

Birincisi, Latin-1, ISO-8859-1 ile aynıdır, bu nedenle varsayılan sizin için zaten uygundur. Sağ?

Komut satırı parametrenizle kodlamayı başarıyla ISO-8859-1'e ayarladınız. Ayrıca bunu programlı olarak "Latin-1" olarak da ayarlarsınız, ancak bu, Java için bir dosya kodlamasının tanınan bir değeri değildir. Bkz. Http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Bunu yaptığınızda, Charset kaynağa bakıldığında UTF-8'e sıfırlanıyor gibi görünüyor. Bu en azından davranışın çoğunu açıklıyor.

OutputStreamWriter'ın neden ISO8859_1 gösterdiğini bilmiyorum. Kapalı kaynak sun.misc. * Sınıflarına delege eder. Sanırım aynı mekanizma aracılığıyla kodlama ile pek ilgilenmiyor ki bu garip.

Ama elbette her zaman bu kodda hangi kodlamayı kastettiğinizi belirtmelisiniz. Platform varsayılanına asla güvenmem.


4

Davranış o kadar da garip değil. Sınıfların uygulanmasına bakıldığında şunlardan kaynaklanır:

  • Charset.defaultCharset() Java 5'te belirlenen karakter kümesini önbelleğe almıyor.
  • "File.encoding" sistem özelliğinin ayarlanması ve Charset.defaultCharset()yeniden çağrılması , sistem özelliğinin ikinci bir değerlendirmesine neden olur, "Latin-1" adında bir karakter kümesi bulunmaz, bu nedenle Charset.defaultCharset()varsayılan olarak "UTF-8" olur.
  • OutputStreamWriterVarsayılan karakter ayarlanmış yönlendirmeler gelen böylece, ancak varsayılan karakter kümesi önbelleğe ve muhtemelen VM başlatma sırasında zaten kullanılıyor Charset.defaultCharset()"file.encoding" sistem özelliği çalışma zamanında değiştirilmiş ise.

Daha önce belirtildiği gibi, VM'nin böyle bir durumda nasıl davranması gerektiği belgelenmemiştir. Charset.defaultCharset()API belgeleri varsayılan karakter kümesi nasıl belirlendiğini, ancak genellikle OS varsayılan karakter kümesi veya varsayılan yerel gibi etkenlere göre VM başlangıçta yapılır söz üzerine çok kesin değildir.


3

Sunucuların varsayılan karakter kümesini değiştirmek için WAS sunucusunda vm bağımsız değişkenini -Dfile.encoding = UTF-8 olarak ayarladım.


1

Kontrol

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

sisteminizin komut satırında kullanılanla aynı kodlama gibi görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.