Java的字符编码

想到这个问题的缘由是如下这段代码:

    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。就是这样。。。。

发表评论