设置默认的Java字符编码

408

如何在程序中正确设置JVM(1.5.x)所使用的默认字符编码?

我了解到在旧版本的JVM中,使用-Dfile.encoding=whatever是一种可行的方式。但出于某些原因,我并没有这种奢侈。

我已经尝试过:

System.setProperty("file.encoding", "UTF-8");

属性被设置了,但似乎不会导致下面的最终getBytes调用使用UTF8:

System.setProperty("file.encoding", "UTF-8");

byte inbytes[] = new byte[1024];

FileInputStream fis = new FileInputStream("response.txt");
fis.read(inbytes);
FileOutputStream fos = new FileOutputStream("response-2.txt");
String in = new String(inbytes, "UTF8");
fos.write(in.getBytes());

非常好的评论,伙计们 - 这些都是我自己已经在思考的事情。不幸的是,有一个我无法控制的底层String.getBytes()调用。目前我唯一看到的解决方法是通过编程方式设置默认编码。还有其他建议吗? - Scott T
7
或许这个问题不太相关,但是在设置UTF8编码时,使用"UTF8"、"UTF-8"或者"utf8"会有区别吗?最近我发现IBM WAS 6.1 EJB和WEB容器对编码定义字符串的大小写处理方式不同。请帮忙翻译。 - igor.beslic
7
只是一个细节,但是: 应该优先使用UTF-8而不是UTF8(只有前者是标准的)。这条规则仍然适用于2012年以后... - Christophe Roussy
5
设置或读取 file.encoding 属性是不受支持的 - McDowell
@erickson 我仍然不清楚这个查询,难道不是真的吗,“file.encoding”只有在使用基于字符的I/O流(class Readerclass Writer的所有子类)时才相关吗?因为class FileInputStream是基于字节的I/O流,所以为什么要关心字节流中的字符集呢? - overexchange
麦克道尔的评论应该得到更多关注。他在Oracle Java Bug数据库中链接的错误(链接在此处:https://bugs.java.com/bugdatabase/view_bug?bug_id=4163515)被拒绝,并且评估结果显示:更改虚拟机和运行时系统使用的默认编码的首选方法是在启动Java程序之前更改底层平台的区域设置。 - undefined
18个回答

360

很遗憾,file.encoding属性需要在JVM启动时指定;在进入主方法时,String.getBytes()InputStreamReader以及OutputStreamWriter的默认构造函数使用的字符编码已经被永久缓存。

正如Edward Grech所指出的那样,在这种特殊情况下,环境变量JAVA_TOOL_OPTIONS可以用来指定此属性,但通常是这样做的:

java -Dfile.encoding=UTF-8 … com.x.Main

Charset.defaultCharset() 方法会反映 file.encoding 属性的更改,但大多数需要确定默认字符编码的 Java 核心库中的代码并不使用此机制。

在编码或解码时,您可以查询 file.encoding 属性或 Charset.defaultCharset() 来查找当前默认编码,并使用适当的方法或构造函数重载来指定它。


12
为了完整起见,我想补充一点小技巧,可以获取实际使用的默认编码(已缓存),感谢Gary Cronin提供帮助:byte[] byteArray = {'a'};InputStream inputStream = new ByteArrayInputStream(byteArray); InputStreamReader reader = new InputStreamReader(inputStream); String defaultEncoding = reader.getEncoding(); http://lists.xcf.berkeley.edu/lists/advanced-java/1999-October/001995.html - Stijn de Witt
2
JDK-4163515 中提供了有关在 JVM 启动后设置 file.encoding 系统属性的更多信息。 - Caspar
4
我正在为在Windows、Linux和Mac上无法完美执行某个命令而感到困惑......然后我将值用引号括起来,就像这样:Java -D"file.encoding=UTF-8" -jar - cabaji99
请检查我的答案,以确保在Java Spring Boot中正确:https://dev59.com/P3RC5IYBdhLWcg3wP-n5#48952844 - Michail Michailidis

191

JVM™工具接口文档中得知…

由于命令行无法始终访问或修改,例如在嵌入式VM或仅在脚本深处启动的VM中,因此提供了JAVA_TOOL_OPTIONS变量,以便代理可以在这些情况下启动。

通过将(Windows)环境变量JAVA_TOOL_OPTIONS设置为-Dfile.encoding=UTF8,每次启动JVM时都会自动设置(Java)System属性。您会知道已经捡起了该参数,因为以下消息将被发布到System.err

Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8


你知道在Tomcat日志中会打印出“Picked up…”语句吗? - thatidiotguy
1
嗨,Edward Grech,感谢您的解决方案。它解决了我在另一个论坛帖子中遇到的问题。http://stackoverflow.com/questions/14814230/the-arabic-input-parameter-passed-as-like-a-junk-input-jasper/14825894#14825894 - Smaug
1
@Tiny Java都能理解。https://dev59.com/Wm025IYBdhLWcg3wZlHd - DLight

80

我有一个可行但有点笨的方法!

System.setProperty("file.encoding","UTF-8");
Field charset = Charset.class.getDeclaredField("defaultCharset");
charset.setAccessible(true);
charset.set(null,null);

这样做可以欺骗JVM,让它认为字符集未设置,并在运行时重新设置为UTF-8!


3
NoSuchFieldException 的意思是“没有这个字段异常”。 - SparK
12
为了让这个黑客技巧起作用,你需要假设安全管理器已经关闭。如果你没有设置Java虚拟机标志的方法,那么很可能你的系统启用了安全管理器。 - Yonatan
8
JDK9不再支持这个黑客技巧。警告:发生了非法的反射访问操作·警告:[...]进行了非法的反射访问·警告:请考虑向[...]的维护者报告此问题·警告:使用--illegal-access=warn启用进一步的非法反射访问操作警告·警告:所有的非法访问操作将在未来版本中被拒绝。 - dotwin
2
@Enerccio:那不是一个好答案,那是一个肮脏的黑客行为,也是一个等待发生的问题。那只应该作为一种紧急措施使用。 - sleske
1
@Enerccio:Java是否“应该”有一种设置方式是有争议的 - 人们也可以争辩说,开发人员在相关情况下“应该”明确指定编码。无论如何,这个解决方案在长期运行中可能会导致严重的问题,因此需要注意“仅限紧急使用”。实际上,即使是紧急情况下使用也是值得商榷的,因为有一种受支持的方法可以做到这一点,就是设置JAVA_TOOL_OPTIONS,如另一个答案所解释的那样。 - sleske
显示剩余7条评论

40

我认为比起设置平台的默认字符集,特别是当你似乎有限制影响应用程序部署,更好的方法是调用更安全的String.getBytes("charsetName")。这样你的应用程序就不会依赖于超出其控制范围的事物。

我个人觉得应该废弃String.getBytes()方法,因为我见过很多情况下开发者没有考虑默认字符集可能会改变而导致严重问题。


是的。默认编码仅用于反映底层操作系统中设置的区域设置,没有人应该更改它。如果您不想使用系统区域设置导致的编码,请始终指定编码。 - undefined

20

我无法回答您的原始问题,但是我想给您提供一些建议——不要依赖JVM的默认编码。最好在您的代码中明确指定所需的编码(即“UTF-8”)。这样,您就知道它可以在不同的系统和JVM配置中正常工作。


9
当然,如果您正在编写桌面应用程序并处理一些没有任何编码元数据的用户指定文本,则平台默认编码是您最好的猜测,以确定用户可能正在使用的编码。 - Michael Borgwardt
@MichaelBorgwardt “那么平台默认编码就是你最好的猜测” 你似乎在建议想要更改默认设置并不是一个好主意。你的意思是,尽可能使用显式编码,在没有其他选择时使用提供的默认编码? - Raedwald
1
@Raedwald:是的,那就是我想表达的意思。平台默认编码(至少在最终用户机器上)通常是该系统所设置区域的用户正在使用的编码。如果您没有更好的(即文档特定的)信息,那么这是您应该使用的信息。 - Michael Borgwardt
1
@MichaelBorgwardt 无稽之谈。使用库来自动检测输入编码,并带有BOM保存为Unicode。这是处理和解决编码问题的唯一方法。 - Aleksandr Dubinsky
我认为你们两个不在同一个页面上。Michael谈论解码,而Raedwald你谈论解码后的处理。 - WesternGun

15

试试这个:

new OutputStreamWriter(new FileOutputStream("Your_file_fullpath" ),Charset.forName("UTF8"))

7

如果您正在使用Spring Boot并想在JVM中传递file.encoding参数,您需要按以下方式运行:

mvn spring-boot:run -Drun.jvmArguments="-Dfile.encoding=UTF-8"

我们需要这个,因为我们正在使用 JTwig 模板,而操作系统使用的是 ANSI_X3.4-1968,我们是通过 System.out.println(System.getProperty("file.encoding")); 才发现的。

希望这能帮助到某些人!


7
我尝试过很多方法,但这里的示例代码非常完美。 链接 代码的关键在于:
String s = "एक गाव में एक किसान";
String out = new String(s.getBytes("UTF-8"), "ISO-8859-1");

7
我们遇到了相同的问题。我们有条不紊地尝试了这篇文章(和其他文章)中的几个建议,但都没有成功。我们还尝试添加 -Dfile.encoding=UTF8 ,但仍然无法解决问题。
对于遇到此问题的人,以下文章最终帮助我们找到了问题所在,描述了如何通过正确设置语言环境来解决 Java/Tomcat 中 unicode/UTF-8 的问题。 http://www.jvmhost.com/articles/locale-breaks-unicode-utf-8-java-tomcat~/.bashrc 文件中正确设置语言环境对我们非常有效。

4

我的团队在使用Windows机器时遇到了相同的问题,然后通过以下两种方式解决了这个问题:

a) 设置环境变量(即使在Windows系统偏好设置中)

JAVA_TOOL_OPTIONS
-Dfile.encoding=UTF8

b) 将以下代码片段引入您的pom.xml文件:

 -Dfile.encoding=UTF-8 

 <jvmArguments>
 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8001
 -Dfile.encoding=UTF-8
 </jvmArguments>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接