如何在Java中找到默认字符集/编码?

2022-08-31 10:40:25

显而易见的答案是使用,但我们最近发现这可能不是正确的答案。我被告知,结果与 java.io 类在几次使用的实际默认字符集不同。看起来Java保留了2组默认字符集。有人对这个问题有任何见解吗?Charset.defaultCharset()

我们能够重现一个失败的案例。这是一种用户错误,但它仍然可能暴露所有其他问题的根本原因。这是代码,

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

我们的服务器需要拉丁语-1中的默认字符集来处理传统协议中的一些混合编码(ANSI/Latin-1/UTF-8)。因此,我们所有的服务器都使用此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纠正了这个问题。


答案 1

这真的很奇怪...设置后,将缓存默认字符集,并且在类位于内存中时不会更改该字符集。设置属性不执行任何操作。每次调用时,它都会返回缓存的字符集。"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。

(更新)

还行。我确实用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-1Charset.defaultCharset()Latin-1UTF-8

至于为什么诸如返回意外结果之类的IO类,
JVM 1.5和JVM 1.6的实现(这些IO类使用女巫)也是不同的。JVM 1.6 实现基于获取默认编码的方法(如果未向 IO 类提供默认编码)。JVM 1.5 实现使用不同的方法来获取缺省字符集。此方法使用自己的在 JVM 初始化时设置的缺省字符集的缓存:OutputStreamWritersun.nio.cs.StreamEncoderCharset.defaultCharset()Converters.getDefaultEncodingName();

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

但我同意这些评论。您不应依赖此属性。这是一个实现细节。


答案 2

这是一个错误或功能吗?

看起来像未定义的行为。我知道,在实践中,您可以使用命令行属性更改默认编码,但我不认为这样做时会发生什么。

错误 ID:4153515设置此属性的问题:

这不是一个错误。J2SE 平台规范不需要“file.encoding”属性;它是Sun实现的内部细节,不应该被用户代码检查或修改。它也应该是只读的;从技术上讲,不可能支持在命令行上或在程序执行期间的任何其他时间将此属性设置为任意值。

更改 VM 和运行时系统使用的默认编码的首选方法是在启动 Java 程序之前更改基础平台的区域设置。

当我看到人们在命令行上设置编码时,我感到畏缩 - 你不知道这会影响什么代码。

如果不想使用默认编码,请通过适当的方法/构造函数显式设置所需的编码。


推荐