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

显而易见的答案是使用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使用的真正的默认字符集。

这是一个错误或function?

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

这真是奇怪…一旦设置了,默认的Charset就会被caching起来,并且当类在内存中的时候不会被改变。 使用System.setProperty("file.encoding", "Latin-1");设置"file.encoding"属性System.setProperty("file.encoding", "Latin-1"); 什么也没做。 每次调用Charset.defaultCharset() ,都会返回caching的字符集。

这是我的结果:

 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重现了你的bug。

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

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

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

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

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

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

这是一个错误或function?

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

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

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

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

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

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

首先,Latin-1和ISO-8859-1是一样的,所以默认对你来说已经OK了。 对?

您使用您的命令行参数成功地将编码设置为ISO-8859-1。 您也可以通过编程将其设置为“Latin-1”,但这不是Java文件编码的公认值。 请参阅http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

当你这样做,看起来像Charset重置为UTF-8,从源头上看。 这至less解释了大部分的行为。

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

但是,当然你应该一直指定在这个代码中你的意思是什么编码。 我永远不会依赖平台默认。

行为并不是那么奇怪。 考虑到类的实现,它是由以下原因造成的:

  • Charset.defaultCharset()不会cachingJava 5中确定的字符集。
  • 设置系统属性“file.encoding”并再次调用Charset.defaultCharset()会导致对系统属性进行第二次评估,因此没有find名称为“Latin-1”的字符集,所以Charset.defaultCharset默认为“UTF-8 ”。
  • 然而,OutputStreamWriter正在caching默认的字符集,可能在虚拟机初始化过程中已经被使用了,所以如果系统属性“file.encoding”在运行时被改变了,那么它的默认字符集将从Charset.defaultCharset()中转移。

正如已经指出的,没有logging虚拟机在这种情况下的行为。 Charset.defaultCharset()API文档在确定默认字符集的方式上并不十分精确,只是提到它通常是在VM启动时完成的,基于诸如操作系统默认字符集或默认语言环境等因素。

我将WAS服务器中的vm参数设置为-Dfile.encoding = UTF-8来更改服务器的缺省字符集。

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

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