char
Java基本数据类型之一,使用' ' 括起来,用于表示单个字符。Java中char是两个字节,16位,但是字符对应UTF-8会有1-3个字节,char该如何存储???
存储
当给char赋值后,其实char中的两个字节存储的是unicode的码点,也就是字符集。与之常见的还有ASCII码,ASCII是一种标准的单字节字符编码方案,适用于所有拉丁字母,但是只有128个字符,这对于其他语言远远不够,特别是中文,所以出现了Unicode。 Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。Unicode前128的字符和ASCII一样,所以Unicode兼容ASCII,也可以说ASCII是Unicode的子集。
以'汉'为例,查看其字符集以及存储。
public static void main(String[] args) {
char cc = '汉';
System.out.printf("\\u%04x\n",(int)cc);
}
存储流程:
在Java中,字符使用的是UTF-16编码,注意不是UTF-8编码 。
大部分的字符使用一个UTF-16编码就行,一个 UTF-16 代码单元需要16bit,而 Java 的 char 类型占用空间也是 16 bit,UTF-16 编码对应 Unicode 码点。
若是代码写法不同,得到的结果会有点不同。
public static void main(String[] args) {
byte[] bytes = "汉".getBytes(StandardCharsets.UTF_16);
System.out.println(bytes.length);
for (byte aByte : bytes) {
System.out.print(Integer.toHexString(Byte.toUnsignedInt(aByte)));
System.out.print(" ");
}
}
编译后结果:
此时会发现结果中是4个字节,多出来fe,ff。多出来的两个字节fe和ff是字节序标志,固定的,其他字符也是这两个,C或C++中这个和平台,CPU有关,而Java中统一采用Big Endian。fe、ff代表存储是按Big Endian(大端);若是出现ff、fe则是按Little Endian(小端)。
以存储“ABC",对应的二进制编码为”41,42,43“为例。
编码 | 格式 |
---|---|
UTF-16BE | 00 41 00 42 00 43 |
UTF-16LE | 41 00 42 00 43 00 |
UTF-16(Big Endian) | FE FF 00 41 00 42 00 43 |
UTF-16(Little Endian) | FF FE 41 00 42 00 43 00 |
UTF-8,UTF-8存储效率高,变长,也就是不方便内部随机访问,是无字节序的,也就没有BE(Big Endian)和LE(Little Endian)之分,可作为外部编码。而UTF-16和UTF-32都是定长,方便内部随机访问,都有字节序问题,不可作为外部编码,也就是区分BE(Big Endian)和LE(Little Endian)。window平台还有携带BOM(byre order mark)的,有兴趣的可以私下查阅一下。
切记,Java中只有Big Endian。
前面说过大部分字符使用一个UTF-16编码就行,但是Unicode扩展字符集需要两个,比如emoji。
String emoji = "😂";
System.out.println("emoji长度:" + emoji.length());
编译结果:
emoji长度:2
这也说明:字符串长度≠字符数。
若仅仅是拉丁字符,其实使用UTF-16有点浪费空间,Java9对拉丁字符的存储空间进行了优化,也就是拉丁字符直接使用byte存储,不用使用char,这样就节省一个字节的空间。
总结
Java中char存储的Unicode码点,码点对应的是UTF-16编码,不是UTF-8编码,所以计算机存储所有码点byte时空间足够用。