java中一个汉字占用几个字节
最近看了Java编程的逻辑 (6) - 如何从乱码中恢复 (上),突然想起一个面试题——char型变量中能不能存贮一个中文汉字。就想确定下一个汉字到底占几个字符?
结论
结论概述
char类型可以存储一个中文汉字。因为Java中char的编码方式为UTF-16BE。UTF-16编码使用2或者4字节,在65536以内的占两个字节。而基本上所有中文的Unicode编码在19968到40869之间——既Unicode至少包含了20902个汉字,所以一个char类型可以存储一个汉字。
是否有超出65536的汉字,我不知道。如果有人知道可以评论一下。
不同编码占用的存储空间
编码方式 | 占用空间 | 说明 |
---|---|---|
ASCII | — | 不能存储中文 |
ISO 8859-1或Windows-1252 | — | 不能存储中文 |
GB2312 | 2字节 | 约7000个汉字,不包括一些罕用词,不包括繁体字 |
GBK | 2字节 | 向下兼容GB2312,GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字 |
GB18030 | 2字节或4字节 | GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包括了很多少数民族字符,以及中日韩统一字符 |
Big5 | 2字节 | 包括1万3千多个繁体字,和GB2312类似 |
UTF-32 | 4字节 | 肯定可以存储汉字,不过使用比较少 |
UTF-16 | 2字节或4字节 | 基本上汉字占用两个字节 |
UTF-8 | 1-4字节,根据存储大小选择。前面提到的中文基本都是3个字节 | 最常用的 |
计算UTF-8存中文时占空间大小的代码
List<Integer> byteLengths = new ArrayList<>();
for (int i = 19968; i <= 40869; i++) {
char c = (char) i;
byteLengths.add(String.valueOf(c).getBytes("utf-8").length);
Map<Integer, Long> collect = byteLengths.stream().collect(groupingBy(Integer::intValue, counting()));
System.out.println(collect);
汇总
通过上面的讨论,可以得出两个结论:
- 在Java里面char类型可以存储一个汉字,因为char指定了编码方式为UTF-16,且常用的中文没有超出2字节的区间。
- 以字符串的形式采用各种方式存储汉字所占的空间大小不一样。
编码方案
- ASCII
- ISO 8859-1或Windows-1252
- 中文的GB2312,GBK,GB18030和Big5
- Unicode
上面四种方案,其中第三和第四种都可以用来表示汉字,后面分别来看看不同的字符方案占用的存储空间大小。
所以,会发现使用不同的编码方案,就会有不同的占用空间大小。
占用的字节
GB2312
GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕用词,不包括繁体字。
GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是Ascii字符。在这两个字节中,其中高位字节范围是0xA1-0xF7,低位字节范围是0xA1-0xFE。
GBK
GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是完全一样的。
GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字。
GBK同样使用固定的两个字节表示,其中高位字节范围是0x81-0xFE,低位字节范围是0x40-0x7E和0x80-0xFE。
需要注意的是,低位字节是从0x40也就是64开始的,也就是说,低位字节最高位可能为0。那怎么知道它是汉字的一部分,还是一个Ascii字符呢?
其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节读进来一起解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。
GB18030
GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包括了很多少数民族字符,以及中日韩统一字符。
用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。
在两字节编码中,字节表示范围与GBK一样。在四字节编码中,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节的值从0x81到0xFE,第四个字节的值从0x30到0x39。
解析二进制时,如何知道是两个字节还是四个字节表示一个字符呢?看第二个字节的范围,如果是0x30到0x39就是四个字节表示,因为两个字节编码中第二字节都比这个大。
Big5
Big5是针对繁体中文的,广泛用于台湾香港等地。
Big5包括1万3千多个繁体字,和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,高位字节范围是0x81-0xFE,低位字节范围是0x40-0x7E和0xA1-0xFE。
Unicode
Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
UTF-8
UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。
存储范围1-4字节都有。
UTF-32
这个最简单,就是字符编号的整数二进制形式,四个字节。
但有个细节,就是字节的排列顺序,如果第一个字节是整数二进制中的最高位,最后一个字节是整数二进制中的最低位,那这种字节序就叫“大端”(Big Endian, BE),否则,正好相反的情况,就叫“小端”(Little Endian, LE)。对应的编码方式分别是UTF-32BE和UTF-32LE。
可以看出,每个字符都用四个字节表示,非常浪费空间,实际采用的也比较少。
UTF-16
UTF-16使用变长字节表示:
- 对于编号在U+0000到U+FFFF的字符 (常用字符集),直接用两个字节表示。需要说明的是,U+D800到U+DBFF之间的编号其实是没有定义的。
- 字符值在U+10000到U+10FFFF之间的字符(也叫做增补字符集),需要用四个字节表示。前两个字节叫高代理项,范围是U+D800到 U+DBFF,后两个字节叫低代理项,范围是U+DC00到U+DFFF。数字编号和这个二进制表示之间有一个转换算法,本文就不介绍了。
区分是两个字节还是四个字节表示一个符号就看前两个字节的编号范围,如果是U+D800到U+DBFF,就是四个字节,否则就是两个字节。
UTF-16也有和UTF-32一样的字节序问题,如果高位存放在前面就叫大端(BE),编码就叫UTF-16BE,否则就叫小端,编码就叫UTF-16LE。
UTF-16常用于系统内部编码,UTF-16比UTF-32节省了很多空间,但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的。