web-dev-qa-db-ja.com

Javaでデフォルトの文字セット/エンコーディングを見つける方法

明らかな答えはCharset.defaultCharset()を使用することですが、最近、これが正しい答えではない可能性があることがわかりました。結果は、いくつかの場面でJava.ioクラスによって使用される実際のデフォルト文字セットとは異なると言われました。 Javaは2セットのデフォルト文字セットを保持しているように見えます。誰もこの問題に関する洞察を持っていますか?

1つのフェイルケースを再現できました。これは一種のユーザーエラーですが、それでも他のすべての問題の根本原因を明らかにする可能性があります。コードは次のとおりです。

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

私たちのサーバーは、レガシープロトコルの混合エンコーディング(ANSI/Latin-1/UTF-8)を処理するために、Latin-1のデフォルト文字セットを必要とします。したがって、すべてのサーバーはこのJVMパラメーターで実行されます。

-Dfile.encoding=ISO-8859-1

Java 5の結果は次のとおりです。

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

誰かがコードでfile.encodingを設定することにより、エンコーディングランタイムを変更しようとします。私たちは皆、それが機能しないことを知っています。ただし、これにより明らかにdefaultCharset()がスローされますが、OutputStreamWriterで使用される実際のデフォルトの文字セットには影響しません。

これはバグですか、それとも機能ですか?

編集:受け入れられた答えは、問題の根本原因を示しています。基本的に、Java 5のdefaultCharset()は信頼できません。これは、I/Oクラスで使用されるデフォルトのエンコーディングではありません。 Java 6がこの問題を修正しているようです。

88
ZZ Coder

これは本当に奇妙です...一度設定すると、デフォルトの文字セットがキャッシュされ、クラスがメモリ内にある間は変更されません。 "file.encoding"プロパティにSystem.setProperty("file.encoding", "Latin-1");を設定しても何も起こりません。 Charset.defaultCharset()が呼び出されるたびに、キャッシュされた文字セットを返します。

私の結果は次のとおりです。

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

ただし、JVM 1.6を使用しています。

(更新)

OK。 JVM 1.5でバグを再現しました。

1.5のソースコードを見ると、キャッシュされているデフォルトの文字セットは設定されていません。これがバグかどうかはわかりませんが、1.6はこの実装を変更し、キャッシュされた文字セットを使用します。

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

ファイルエンコーディングをfile.encoding=Latin-1に設定すると、次にCharset.defaultCharset()を呼び出したときに、キャッシュされたデフォルトの文字セットが設定されていないため、名前に適切な文字セットを見つけようとしますLatin-1。この名前は間違っているため見つからず、デフォルトのUTF-8を返します。

OutputStreamWriterなどのIOクラスが予期しない結果を返す理由については、
Sun.nio.cs.StreamEncoder(これらのIOクラスで使用される魔女)の実装は、JVM 1.5とJVM 1.6でも異なります。 JVM 1.6実装は、IOクラスに提供されていない場合、Charset.defaultCharset()メソッドに基づいてデフォルトのエンコーディングを取得します。 JVM 1.5実装では、異なるメソッドConverters.getDefaultEncodingName();を使用してデフォルトの文字セットを取得します。このメソッドは、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);
}

しかし、私はコメントに同意します。 このプロパティに依存するべきではありません。これは実装の詳細です。

62
bruno conde

これはバグですか、それとも機能ですか?

未定義の動作のように見えます。実際には、コマンドラインプロパティを使用してデフォルトのエンコーディングを変更できることは知っていますが、これを行うとどうなるかは定義されていないと思います。

バグID:4153515 このプロパティの設定に関する問題:

これはバグではありません。 「file.encoding」プロパティは、J2SEプラットフォーム仕様では必要ありません。これは、Sunの実装の内部詳細であり、ユーザーコードによって調査または変更されるべきではありません。また、読み取り専用であることも意図しています。コマンドラインまたはプログラムの実行中の他の時点で、このプロパティの任意の値への設定をサポートすることは技術的に不可能です。

VMおよびランタイムシステムで使用されるデフォルトのエンコーディングを変更する推奨方法は、Javaプログラムを開始する前に、基盤となるプラットフォームのロケールを変更することです。

コマンドラインでエンコードを設定している人を見ると、うんざりします。どのコードが影響するかわかりません。

デフォルトのエンコーディングを使用したくない場合は、適切なメソッド/ constructor を使用して明示的にエンコーディングを設定します。

24
McDowell

まず、Latin-1はISO-8859-1と同じであるため、デフォルトはすでに問題ありません。右?

コマンドラインパラメータでエンコードをISO-8859-1に正常に設定しました。また、プログラムで「Latin-1」に設定しますが、これはJavaのファイルエンコーディングの認識値ではありません。 http://Java.Sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html を参照してください

これを行うと、ソースを見ると、CharsetがUTF-8にリセットされているように見えます。少なくともほとんどの動作を説明しています。

OutputStreamWriterがISO8859_1を表示する理由がわかりません。クローズドソースのSun.misc。*クラスに委任します。私はそれが同じメカニズムを介したエンコードをまったく扱っていないのではないかと推測していますが、これは奇妙です。

ただし、もちろん、このコードでは、エンコードの意味を常に指定する必要があります。プラットフォームのデフォルトに依存することはありません。

4
Sean Owen

動作はそれほど奇妙ではありません。クラスの実装を見ると、次のことが原因です。

  • Charset.defaultCharset()は、Java 5で決定された文字セットをキャッシュしていません。
  • システムプロパティ "file.encoding"を設定してCharset.defaultCharset()を再度呼び出すと、システムプロパティの2回目の評価が行われ、 "Latin-1"という名前の文字セットが見つからないため、Charset.defaultCharset()のデフォルトは "UTF -8 "。
  • ただし、OutputStreamWriterはデフォルトの文字セットをキャッシュしており、おそらくVMの初期化中にすでに使用されているため、システムプロパティ "file.encoding"の場合、デフォルトの文字セットはCharset.defaultCharset()実行時に変更されました。

既に指摘したように、そのような状況でVMがどのように動作する必要があるかは文書化されていません。 Charset.defaultCharset() AP​​Iドキュメントは、デフォルトの文字セットがどのように決定されるかについてはあまり正確ではなく、OSのデフォルトの文字セットやデフォルトのロケールなどの要因に基づいて、通常VM 。

4
jarnbjo

WASサーバーでvm引数を-Dfile.encoding = UTF-8として設定して、サーバーのデフォルトの文字セットを変更しました。

3
Davy Jones

小切手

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

システムのコマンドラインで使用されているものと同じエンコーディングのようです。

0
neoedmund