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

时间:2009-11-17 13:55:45

标签: 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;
    }
}

我们的服务器需要Latin-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纠正了这个问题。

6 个答案:

答案 0 :(得分: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

我正在使用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;
    }
}

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类会返回意外结果,为什么呢? 对于JVM 1.5和JVM 1.6,sun.nio.cs.StreamEncoder(这些IO类使用的)的实现也是不同的。 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);
}

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

答案 1 :(得分:24)

  

这是一个错误或功能吗?

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

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

  

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

     

更改VM和运行时使用的默认编码的首选方法   system是在开始之前更改底层平台的语言环境   Java程序。

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

如果您不想使用默认编码,请通过适当的方法/ constructor明确设置您想要的编码。

答案 2 :(得分: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。*类。我猜它不是通过相同的机制处理编码,这很奇怪。

但是当然你应该总是在这段代码中指定你的意思。我永远不会依赖平台默认。

答案 3 :(得分:4)

这种行为并不是那么奇怪。查看类的实现,它是由:

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

正如已经指出的那样,没有记录VM在这种情况下必须如何表现。 Charset.defaultCharset() API文档对于如何确定默认字符集并不十分精确,只是提到它通常是在VM启动时完成的,基于操作系统默认字符集或默认语言环境等因素。

答案 4 :(得分:3)

我已将WAS服务器中的vm参数设置为-Dfile.encoding = UTF-8以更改服务器的默认字符集。

答案 5 :(得分:0)

检查

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

它似乎与系统命令行中使用的编码相同。