Java随笔-char存储

388 阅读3分钟

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的子集。

char.png 以'汉'为例,查看其字符集以及存储。

    public static void main(String[] args) {
        char cc = '汉';
        System.out.printf("\\u%04x\n",(int)cc);
    }

存储流程:

char_save.png

在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(" ");
        }
    }

编译后结果:

charlength.jpg 此时会发现结果中是4个字节,多出来fe,ff。多出来的两个字节fe和ff是字节序标志,固定的,其他字符也是这两个,C或C++中这个和平台,CPU有关,而Java中统一采用Big Endian。fe、ff代表存储是按Big Endian(大端);若是出现ff、fe则是按Little Endian(小端)。

以存储“ABC",对应的二进制编码为”41,42,43“为例。

编码格式
UTF-16BE00 41 00 42 00 43
UTF-16LE41 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时空间足够用。