Java中如何存储汉字
因为笔者了解有限,文中难免会出现一些错误,如有发现,望指出,谢谢。
依稀记得当初刷面试题时,有道题是这样的
char 型变量中能不能存贮一个中文汉字,为什么?
答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。
记得当时也很疑惑,只是学疏才浅,加上忙于找工作,也没多在意。直到无意间又遇到了这个问题,便一步一步踏上了寻找之旅。
首先,汉字的存储和字符集有关,字符集不同,存储汉字所用的字节也不同。
如上题所述,Java中使用的编码是Unicode,但是Unicode 是「字符集」(为每一个「字符」分配一个唯一的 ID),不负责具体的编码规则(将「码位」转换为字节序列的规则)。那么在汉字Java中的编码规则是什么呢?
char
原本想从char找起,奈何char是基本类型,没有办法查看类定义信息,然后就想到了包装类Character,果不其然在其中找到了一些信息。
由此可见,Java采用UTF-16编码,但是我了解到汉字(Unicode常见的汉字编码范围0x4E00-0x9FA5)在UTF-16占2个字节(对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码),char有2个字节,可以放下。UTF-16 用两个字节表示一个字符,那么用两个字节表示必然存在字节序的问题(Byte Order Mark(BOM)),即大端小端的问题。UTF-16根据BOM分为UTF-16BE(最低地址存放高位字节,编码前会放置FEFF)和UTF-16LE(最高地址存放高位字节,编码前会放置FFFE),那么Java中采用的是UTF-16BE还是UTF-16LE?由于不想继续找下去,所以就直接用现象去推出结论,测试代码如下:
public static void main(String[] args) {
char ch = '我';
}
在Debug模式下结果如下:
发现’我’在char中存储的是25105,然后在编码中发现’我’的编码如下:
将十进制的25105('我’在char中存储的值)转化为十六进制,结果为6211,对比上述编码后四位,最终确定,Java采用的是UTF-16BE。
String
说起char就不得不说起String,因此我就测试了String,String结构如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
...
@Stable
private final byte[] value;
...
}
由此可见真正存放数据的是byte[]数组(被final修饰,这是String不可变的原因),一个byte占一个字节,那么猜想’我’存放在String中是byte[2],果然,结果符合设想,然后查看值,
那么[17,98]到底代表什么含义呢,首先,我想到了转换为十六进制,但是两个值没办法直接转换,所以就先转化为了二进制,因为计算机存储的是补码(byte占一个字节,为8位),转换为补码为[00010001, 01100010],然后根据BE中最低地址存放高位字节,从后往前拼接,[00010001, 01100010]–>0110001000010001–>110001000010001。将二进制110001000010001转换为十六进制为6211,和上述存储结果一致。
至此,得出结论,Java中汉字的编码规则为UTF-16BE。