显而易见的答案是使用,Charset.defaultCharset()但是我们最近发现这可能不是正确的答案。有人告诉我,结果在某些情况下不同于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; } }
我们的服务器要求使用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可以解决此问题。
这真的很奇怪。。。一旦设置,默认的Charset将被缓存,并且当类在内存中时不会更改。将"file.encoding"属性设置为System.setProperty("file.encoding", "Latin-1");无。每次Charset.defaultCharset()调用时,它都会返回缓存的字符集。
"file.encoding"
System.setProperty("file.encoding", "Latin-1")
这是我的结果:
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-1为下次调用时Charset.defaultCharset(),会发生这种情况,因为未设置缓存的默认字符集,它将尝试为name查找合适的字符集Latin-1。找不到此名称,因为它不正确,并返回default UTF-8。
file.encoding=Latin-1
Latin-1
至于诸如此类的IO类为何OutputStreamWriter返回意外结果的原因,对于JVM 1.5和JVM 1.6 ,sun.nio.cs.StreamEncoder(这些IO类使用巫婆的)实现也有所不同。Charset.defaultCharset()如果未为IO类提供默认编码,则JVM 1.6实现基于该方法来获取默认编码。JVM 1.5实现使用另一种方法Converters.getDefaultEncodingName();来获取默认字符集。此方法使用自己的默认字符集缓存,该缓存是在JVM初始化时设置的:
OutputStreamWriter
sun.nio.cs.StreamEncoder
Converters.getDefaultEncodingName()
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); }
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); }
但是我同意这些意见。你不应该依赖此属性。这是一个实现细节。