代码之家  ›  专栏  ›  技术社区  ›  ZZ Coder

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

  •  86
  • ZZ Coder  · 技术社区  · 16 年前

    显而易见的答案是使用 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;
        }
    }
    

    我们的服务器需要拉丁语-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
    

    这是一个bug还是一个特性?

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

    6 回复  |  直到 11 年前
        1
  •  64
  •   Moshe Slavin    7 年前

    这真的很奇怪。。。一旦设置,默认的字符集将被缓存,并且当类在内存中时,它不会被更改。设定 "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
    

    (更新)

    好啊我确实用JVM1.5复制了您的bug。

    查看1.5的源代码,没有设置缓存的默认字符集。我不知道这是否是一个bug,但1.6更改了此实现并使用了缓存的字符集:

    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 下次你打电话的时候 默认字符集() ,发生的情况是,由于未设置缓存的默认字符集,它将尝试为名称查找适当的字符集 Latin-1 . 找不到此名称,因为它不正确,并返回默认名称 UTF-8 .

    至于为什么IO类如 OutputStreamWriter 返回一个意外的结果,
    实施 sun.nio.cs.StreamEncoder 默认字符集() 方法获取默认编码(如果未向IO类提供)。JVM1.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);
    }
    

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

        2
  •  25
  •   McDowell rahul gupta    16 年前

    这是一个bug还是一个特性?

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

    Bug ID: 4153515

    这不是一个bug。J2SE不需要“file.encoding”属性 平台规范;这是Sun实现和应用的内部细节 只读;技术上不可能支持此属性的设置 执行。

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

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

    如果不希望使用默认编码,请通过适当的方法显式设置所需的编码/ constructor .

        3
  •  6
  •   Andrii Abramov Mk.Sl.    7 年前

    这种行为其实并不奇怪。从类的实现来看,它是由以下原因造成的:

    • Charset.defaultCharset()
    • 设置系统属性“file.encoding”并调用 默认字符集() 再次导致对系统属性进行第二次求值,未找到名为“Latin-1”的字符集,因此 默认为“UTF-8”。
    • 这个 OutputStreamWriter 默认字符集() 如果系统属性“file.encoding”在运行时已更改。

    正如已经指出的,没有记录VM在这种情况下的行为。这个 默认字符集()

        4
  •  4
  •   Sean Owen    16 年前

    首先,拉丁语-1与ISO-8859-1相同,因此,默认值对您来说已经可以了。对吗?

    http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

    这样做时,从源代码看,字符集似乎重置为UTF-8。这至少解释了大部分行为。

    我不知道OutputStreamWriter为什么显示ISO8859_1。它委托给封闭源代码的sun.misc.*类。我猜它不是通过同样的机制来处理编码,这很奇怪。

    当然,您应该始终指定此代码中的编码含义。我从不依赖平台默认值。

        5
  •  3
  •   Davy Jones    12 年前

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

        6
  •  2
  •   neoedmund    13 年前

    检查

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

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