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


92

明白な答えは使用することです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がこの問題を修正したようです。


defaultCharsetは一度だけ設定される静的変数を使用するため、奇妙です(ドキュメントによると-VM起動時)。どのVMベンダーを使用していますか?
Bozho

私は両方の日/ Linuxとアップル/ OS X上で、Java 5の上でこれを再現することができました
ZZコーダ

これがdefaultCharset()が結果をキャッシュしない理由を説明しています。それでも、IOクラスで使用される実際のデフォルトの文字セットは何かを調べる必要があります。別の場所にキャッシュされた別のデフォルト文字セットが必要です。
ZZ Coder

@ZZ Coder、まだ調査中です。私が知っている唯一の考えは、JVM 1.5でsun.nio.cs.StreamEncoderからCharset.defaulyCharset()が呼び出されないことです。JVM 1.6では、Charset.defaulyCharset()メソッドが呼び出され、期待される結果が得られます。StreamEncoderのJVM 1.5実装は、以前のエンコーディングを何らかの方法でキャッシュしています。
bruno conde

回答:


62

これは本当に奇妙です...一度設定すると、デフォルトの文字セットがキャッシュされ、クラスがメモリ内にある間は変更されません。"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の実装はCharset.defaultCharset()、IOクラスに提供されていない場合、デフォルトのエンコーディングを取得するメソッドに基づいています。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);
}

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


このエラーを再現するには、Java 5を使用していて、JREのデフォルトのエンコーディングがUTF-8である必要があります。
ZZ Coder

2
これは、抽象化ではなく実装への書き込みです。ドキュメント化されていないものに依存している場合、プラットフォームの新しいバージョンにアップグレードするときにコードが壊れても驚かないでください。
McDowell、

24

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

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

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

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

VMとランタイムシステムで使用されるデフォルトのエンコーディングを変更する好ましい方法は、Javaプログラムを起動する前に、基盤となるプラットフォームのロケールを変更することです。

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

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


4

まず、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

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

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

すでに指摘したように、このような状況でVMがどのように動作する必要があるかは文書化されていません。Charset.defaultCharset()APIのドキュメントでは、デフォルトの文字セットがどのように決定されるか、だけでは通常、OSのデフォルトの文字セットまたはデフォルトロケールなどの要因に基づいて、VMの起動時に行われることを言及する上で非常に正確ではありません。



1

小切手

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.