想到这个问题的缘由是如下这段代码:
String inputStr = "abc";
byte[] input = inputStr.getBytes();
System.out.println(input.length);
按照我的常识,Java的String类使用的是utf-16编码。在BMP平面的字符都是可以使用两个字节表示的,四个字节的情况并不常见。对于例子中的inputStr,它应该占用六个字节的存储空间。但是这段程序的执行结果是3。
这是为什么呢?原来String类还有一个getBytes(String charset)方法。直接调用String.getBytes()相当于调用String.getBytes(Charset.getDefaultCharset())。在中文操作系统下,一般的默认编码都是GBK。
首先需要明确的是,中文编码GB18030>GBK>GB2312>ASCII,这里的大于号是兼容包含的含义。在三个中文编码标准中,除了128个ASCII是一个字节外,其他都是两个字节。所以中文编码严格算来也是变长编码格式。然而这些中文编码并不是国际标准,Unicode(又称UCS)才是编码的最高标准。Unicode完整定义了世界上各种字符的统一编号,而且是可扩展的。如果把字符比作人,那么Unicode码就是它们的身份证号。地方性的编码格式就像姓名,一个村里可以保证不会重名,但是“全国”没有重名是根本不可能的事情。
Unicode早期标准的编码长度为2个字节。然而和ipv4的命运一样,六万个“身份证号”根本不够。中文字符(包含繁体、生僻字和少数民族等)就有好几万个,所以编码长度扩张是必然需求。现在的编码长度已经早已超过了两个字节。
然而这并不是故事的结局,Unicode只是字符的唯一id,却没有规定这个id如何传输和保存。这个“身份证号”可以用阿拉伯数字表示也可以用罗马数字表示。Unicode的表示方法就是utf(UCS Transfer Format)。utf这个家族由utf-8,utf-16,utf-32几兄弟组成,后边的数字表示的是编码的最小长度。其中utf-8和utf-16都是变长编码,至于utf-32大概是定长的(最短长度的容量都骇人听闻了)。utf-16其实是最接近原始Unicode的编码,这也是很多语言和操作系统选择utf-16作为内部编码的部分原因。
package security;
import java.util.Arrays;
public abstract class CharsetTest{
public static void printBytesInHex(byte[] bytes) {
for(int i = 0; i< bytes.length; i++) {
System.out.print(Integer.toHexString(bytes[i]) + "\t");
}
System.out.println();
}
public static void main(String[] args) throws Exception
{
String inputStr = "abc";
byte[] inputUTF16 = inputStr.getBytes("UTF-16");
byte[] inputUTF8 = inputStr.getBytes("UTF-8");
byte[] inputGBK = inputStr.getBytes("GBK");
System.out.println("GBK Length = " + inputGBK.length);
System.out.println("UTF-8 Length = " + inputUTF8.length);
System.out.println("UTF-16 Length = " + inputUTF16.length);
System.out.println("GBK" + Arrays.toString(inputGBK));
System.out.println("UTF-8" + Arrays.toString(inputUTF8));
System.out.println("UTF-16" + Arrays.toString(inputUTF16));
//printBytesInHex(input);
}
}
代码的运行结果如下:
GBK Length = 3
UTF-8 Length = 3
UTF-16 Length = 8
GBK[97, 98, 99]
UTF-8[97, 98, 99]
UTF-16[-2, -1, 0, 97, 0, 98, 0, 99]
咦,UTF-16的编码长度竟然是8,而不是6。再看UTF-16的字节序列,前面的-2和-1其实对应着0xFE和0xFF。这是UTF-16的一个标志性字节序列,它的作用是来标定字节序的。简单说来,就是0xFEFF序列说明这段编码是大段字节序(Big Endian),反之0xFFFE代表小端字节序(Little Endian)。对了,Java内部使用UTF-16BE。就是这样。。。。
发表评论