java中的编码
关于字符编码的方式很多,比如常用的有unicode系列的utf8,utf16,utf32,还有中国的gb2312,GBK,GBK18030等等,这些网上的资料很多,但都不是本集要讲的内容,本集要讲的内容只有三个
java的文件编码
java的string的外在编码
java的string的内在编码,jvm用的编码
本文所有的讨论,直接基于jdk17,不见兼顾jdk8,所以,不会讨论历史的情况
1. java的文件编码 很多时候,大家都会说,防止文件乱码,然后统一使用utf-8,这样做没问题,但是为什么呢,这样做是对的,但是这样做的原因一定是对的嘛?
java对于文件的编码方式是可以支持别的,不一定是utf-8,这个javac -help
可以看到
用utf-8的确是一个很好的选择,即使你们公司都在国内,用gb2312编码也可以,但是如果,你如果使用了自动的ci,在服务器上编码代码,如果服务器是linux的,那么它的 默认编码就可以是utf-8,那你代码编译的时候,就需要手动指定编码方式,而且如果你的项目是开源的,放到github上,那么世界各地都可能有committer,如果各个个国家的人,都用自己 本国的编码,那项目就没法玩了,这个时候utf-8就是最佳的选择,所以,无论如何,文件编码使用utf-8都是很好的选择,至少现在就是
1 2 3 # https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html -encoding encoding Specifies character encoding used by source files, such as EUC-JP and UTF-8. If the -encoding option is not specified, then the platform default converter is used.
java的string的外在编码 java的string的外在编码,指的是String.getBytes()
方法使用的编码,这个可以看jdk的源码得到答案,一般oracle jdk会有一些私有代码,代码不是全部公开的,如果有些 时候看不到部分代码,可以使用开发的jdk,比如azul,这个时候,几乎就可以看到所有的代码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public byte [] getBytes() { return encode(Charset.defaultCharset(), coder(), value); } public static Charset defaultCharset () { if (defaultCharset == null ) { synchronized (Charset.class) { String csn = GetPropertyAction .privilegedGetProperty("file.encoding" ); Charset cs = lookup(csn); if (cs != null ) defaultCharset = cs; else defaultCharset = sun.nio.cs.UTF_8.INSTANCE; } } return defaultCharset; }
java的string的内在编码,jvm用的编码 先下结论,java内在的编码,是utf-16,但是也不是,是UTF-16LE,因为java string的外在编码你只要看到的时候,其实就已经发生转换了,所以,你不能直接窥探到,这里我们 需要使用反射来窥探到,需要增加参数
1 2 3 4 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package pers.apricot.character;import org.junit.jupiter.api.Test;import java.lang.reflect.Field;import java.nio.charset.StandardCharsets;import java.util.HexFormat;public class StringUnicodeTest { @Test void test1 () throws NoSuchFieldException, IllegalAccessException { HexFormat of = HexFormat.of(); String 我爱你 = new String ("我爱你" ); byte [] bytes = 我爱你.getBytes(); System.out.println(bytes.length); System.out.println(of.formatHex(bytes)); Class<String> stringClass = String.class; Field value = stringClass.getDeclaredField("value" ); value.setAccessible(true ); byte [] originByteArr = (byte []) value.get(我爱你); System.out.println(originByteArr.length); System.out.println(of.formatHex(originByteArr)); byte [] bytes1 = 我爱你.getBytes(StandardCharsets.UTF_16); System.out.println(bytes1.length); System.out.println(of.formatHex(bytes1)); byte [] bytes2 = 我爱你.getBytes(StandardCharsets.UTF_16BE); System.out.println(bytes2.length); System.out.println(of.formatHex(bytes2)); byte [] bytes3 = 我爱你.getBytes(StandardCharsets.UTF_16LE); System.out.println(bytes3.length); System.out.println(of.formatHex(bytes3)); } }
输出如下
1 2 3 4 5 6 7 8 9 10 9 e68891e788b1e4bda0 6 11623172604f 8 feff621172314f60 6 621172314f60 6 11623172604f
可以看到直接用反射得到的字节,和UTF_16LE得到的字节是一样的,所以,java的确使用的是UTF_16LE
延伸 java为什么是UTF_16LE 这里应该和cpu的架构有关,比如x86的都是小端,little-endian,因为java是跨平台的吗,所以要入乡随俗,然后比如我的cpu是intel 9400,所以,自然就是小端的,当然这 个只是查的有关资料,并不是真的这样确定
为什么utf16多了两个字节 这里可以看到utf16,多了BOM,而且默认是使用大头
结论
java的文件编码可以支持多种,但是最佳实践是采用utf-8
java的string,默认编码也是utf-8,可以指定默认的,也可以转成其它的
jvm的string编码,是utf16le或者utf16be
java编码终极指南2里面会详细介绍sun.jnu.encoding
和file.encoding