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

ZZ *_*der 88 java encoding character-encoding

显而易见的答案是使用,Charset.defaultCharset()但我们最近发现这可能不是正确的答案.有人告诉我,结果与java.io类在多个场合使用的真正的默认字符集不同.看起来Java保留了2套默认字符集.有没有人对这个问题有任何见解?

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们的服务器需要Latin-1中的默认字符集来处理传统协议中的一些混合编码(ANSI/Latin-1/UTF-8).所以我们所有的服务器都运行这个JVM参数,

-Dfile.encoding=ISO-8859-1
Run Code Online (Sandbox Code Playgroud)

这是Java 5的结果,

Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=UTF-8
Default Charset in Use=ISO8859_1
Run Code Online (Sandbox Code Playgroud)

有人试图通过在代码中设置file.encoding来更改编​​码运行时.我们都知道这不起作用.但是,这显然抛弃了defaultCharset(),但它不会影响OutputStreamWriter使用的实际默认字符集.

这是一个错误或功能吗?

编辑:接受的答案显示了问题的根本原因.基本上,您不能信任Java 5中的defaultCharset(),它不是I/O类使用的默认编码.看起来Java 6纠正了这个问题.

bru*_*nde 62

这真的很奇怪......一旦设置,默认的Charset就会被缓存,并且当类在内存中时它不会被更改.设置"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
Run Code Online (Sandbox Code Playgroud)

我正在使用JVM 1.6.

(更新)

好.我确实用JVM 1.5重现了你的错误.

查看1.5的源代码,未设置缓存的默认字符集.我不知道这是不是一个bug,但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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

当您将文件编码设置file.encoding=Latin-1为下次调用时Charset.defaultCharset(),会发生什么,因为未设置缓存的默认字符集,它将尝试为该名称查找相应的字符集Latin-1.找不到此名称,因为它不正确,并返回默认值UTF-8.

至于为什么IO类,如OutputStreamWriter返回一个意外的结果,
执行sun.nio.cs.StreamEncoder(女巫使用由这些IO类)是不同的,以及对于JVM 1.5和1.6 JVM.JVM 1.6实现基于Charset.defaultCharset()获取默认编码的方法(如果没有提供给IO类).JVM 1.5实现使用不同的方法Converters.getDefaultEncodingName();来获取默认字符集.此方法使用自己的JVM初始化时设置的默认charset缓存:

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

但我同意这些意见.你不应该依赖这个属性.这是一个实现细节.

  • 这是写入实现,而不是抽象.如果您依赖于未记录的内容,那么在升级到较新版本的平台时,如果代码中断,请不要感到惊讶. (2认同)

McD*_*ell 24

这是一个错误或功能吗?

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

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

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

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

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

如果您不想使用默认编码,请通过适当的方法/ 构造函数设置您想要的编码.


jar*_*bjo 6

这种行为并没有那么奇怪。查看类的实现,它是由以下原因引起的:

  • Charset.defaultCharset() 不缓存 Java 5 中确定的字符集。
  • 设置系统属性“file.encoding”并Charset.defaultCharset()再次调用会导致对系统属性进行第二次评估,找不到名称为“Latin-1”的字符集,因此Charset.defaultCharset()默认为“UTF-8”。
  • OutputStreamWriter然而,它正在缓存默认字符集,并且可能在 VM 初始化期间已经使用,因此Charset.defaultCharset()如果系统属性“file.encoding”在运行时已更改,则其默认字符集会转移。

正如已经指出的那样,没有记录 VM 在这种情况下必须如何表现。该Charset.defaultCharset()API文档是不是默认字符集是如何确定的,只提的是,它通常是做了VM启动的基础上,如OS默认字符集或默认的语言环境因素非常精确。