最近深入研究了java内部字符串编码方式,发现大家在对java字符串编码认识上存在很大的分歧。网上的说法很多,其中很多都是不严谨的说法或者说是错误的说法。现在把常见认识误区列举如下:
误区一:Java的字符串是unicode编码,java编码方式是unicode,Java内字符编码是unicode。
说明:上面的说法都提到unicode编码,这是非常不正确的说法。
首先说下什么是unicode,以下摘自百度百科:
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
简单说unicode只是规定了每个字符对应的数字,即码位,只是作字符集(charset)用,而不是程序编码(encoding)。
unicode是字符集,而utf-8,utf-16才是具体的编码方式,两者千万不能搞混了。
我假设java是unicode编码,那岂不是意味着每个字符至少要用3个字节(因最大数字0x10FFFF占用三个字节)来表示,这显然是不对的。
众所周知,java中的String类型实际是char数组构成的,一个char占用两个字节。
那为什么一个char占用两个字节?因为String实际采用UTF-16 编码方式存储所有字符。UTF-16规定用两个字节或四个字节来唯一表示unicode字符,因此java约定一个基本char类型占用两个字节,这样通过一个char或两个char就能唯一表示unicode字符。
综上所述,可以说java字符串采用的字符集是unicode,而编码方式是utf-16。
误区二:java中一个汉字用一个char就能表示。
根据上面所述,String采用UTF-16编码方式来存储字符,因此对于基本平面(BMP或平面0)的字符用一个char就能表示,但是对于其它平面的字符则需要两个char才能表示。简单说就是对于unicode的0-0xFFFF范围的字符用一个char就能表示,但是0x10000-0x10FFFF范围的字符就要用两个char才能表示。
那么就存在一个问题,java对内存中的两个连续的char,它怎么知道是一个char对应一个字符,还是这两个连续的char构成一个字符。这就涉及到unicode的特殊约定和utf-16编码的具体实现了。详细如下:
1、unicode约定平面0的0xD800-0xDFFF,共2048个码位,是一个被称作代理区(Surrogate)的特殊区域。代理区的目的用两个UTF-16字符表示BMP以外的字符。
2、UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:
如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。
如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
这样当程序遇到第一个char在D800-DBFF或DC00-DFFF范围时,就知道这个char是需要跟后面的char合并来对应一个字符。