邻桌手指在键盘上飞舞,忽然他抬起头很严肃地问我:一个汉字编码占用几个字节?我头也不抬很肯定的回答:两个!直到他连续重复问了三遍,我才发现有点不对劲,犹豫了下回答到:应该是两个吧。他笑了,笑声让我感到恐惧,一个入行级的问题问得我毫无自信,头皮发麻。于是,带着疑问好好梳理了下这个问题,结果发现自己被打脸!

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(1)

先给出一种答案:中文在不同编码是不定长的 2~4个字节。

编码?刚入行的小伙伴可能会有一连串小问号,为什么需要编码?直接使用汉字或者数字或者英文字符不行吗?工欲善其事必先利其器,我们就先来介绍一下计算机中为什么需要编码。当今世界上共有233个国家和地区,其中包括197个国家和36个地区,这么多国家和地区,自然就有很多的语言,表达这些语言的符号就有很多,如果计算机全部采用这些这些符号,那计算机的压力就十分的巨大,而且要表达的符号太多,也无法简单地使用一个字节来表示。考虑到这些问题,借鉴多国之间语言存在差异,但是依然可以通过翻译来畅谈无阻,计算机也可以约定一套翻译的规则,在于计算机通信时,进项翻译,这个过程,可以理解为编码,编码之后又可以解码,还原成原来的内容。明白了这些之后,接下来就是了解有哪些编码规则,以及他们之间有什么区别。

常见的编码方式

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(2)

(1)ASCII编码

美国制定的一套字符编码,ASCII码一共规定了128个字符的编码。在计算机内部,信息都可以表示成一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。

(2)GB2312/GBK/GB18030编码

GB2312是由中国国家标准总局1980年发布的,1981年5月1日开始实施的一套国家标准,标准号是GB 2312—1980。GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,每个汉字及符号以两个字节来表示,但他不够全面,基本集共收入汉字6763个和非汉字图形字符682个,有一些罕见字并未收录,这就出现了后面的GBK及GB 18030。

GBK全称《汉字内码扩展规范》,又称国标码,1995年12月1日制订的,GBK是采用单双字节变长编码,英文使用单字节编码,完全兼容ASCII字符编码,中文部分采用双字节编码。共收录了21003个汉字。目前依然有一部分项目采用的是GBK编码。

GB18030,全称《信息技术中文编码字符集》,是中华人民共和国国家标准所规定的变长多字节字符集。其对GB 2312-1980完全向后兼容,与GBK基本向后兼容,并支持Unicode(GB 13000)的所有码位。GB 18030共收录汉字70,244个。GB 18030采用变长多字节编码,每个字可以由1个、2个或4个字节组成,他也是完全支持Unicode字符集的。

(3)Unicode字符集

Unicode 也叫万国码,Unicode 是一个标准,严格意义上定位的是字符集,Unicode 字符集为每一个字符分配一个编号,通过这个编号就能找到对应的字符。从这个定义也可以看出,Unicode 只是对各个语言字符进行了编号,并没有约定对编号如何存储以及存储几个字节等,而这些是由Unicode 字符集实现格式来约定的,比如下面提到的UTF-8、UTF-16、UTF-32等。

(4)UTF-8编码

UTF-8是Unicode编码的具体实现方式之一。它最主要的优点就是可变长度,字符编码通常由 1-4 个字节来表示,相比定长的实现方式,能够节省更多的空间。除此之外,UTF-8 兼容 ASCII,而Unicode的其他两种方式UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。

(5)UTF-32编码

UTF-32是Unicode编码的具体实现方式之一。一种固定长度的编码方案,不管字符编号大小,始终使用 4 个字节来存储;

(6)UTF-16编码

UTF-16是Unicode编码的具体实现方式之一。它介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。为什么说是既固定又可变呢?UTF-16对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储。 UTF-16没有指定后缀,即不知道其是大小端,所以其开始的两个字节表示该字节数组是大端还是小端。即FE FF表示大端,FFFE表示小端。故而又有两种细化的编码方式UTF-16BE和UTF-16LE。

UTF-16BE:其后缀是 BE 即 big-endian,大端的意思。大端就是将高位的字节放在低地址表示。

UTF-16LE:其后缀是 LE 即 little-endian,小端的意思。小端就是将高位的字节放在高地址表示。

可能有人会问,一个汉字占用几个字节为什么会衍生出上面那么多问题,正是因为使用不同的编码方式可能占用的字节数就不一样了。了解了编码方式之后就比较容易有一个清晰的概念。

中文在不同编码是不定长的 2~4个字节(至少两个字节,由汉字的总数超过6万字,2^16=65536)

(1) GBK编码,一个汉字占两个字节。

(2) UTF-16编码,通常汉字占两个字节,CJKV扩展B区、扩展C区、扩展D区中的汉字占四个字节(一般字符的Unicode范围是U 0000至U FFFF,而这些扩展部分的范围大于U 20000,因而要用两个UTF-16)。

(3) UTF-8编码是变长编码,通常汉字占三个字节,扩展B区以后的汉字占四个字节。

通过程序来试验一下:

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(3)

控制台输出

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(4)

按照上面的运行结果,“你”在UTF-16编码下占用4个字节。那么猜测两个汉字“你好”,在UTF-16编码下应该是8个字节,但是运行结果却和想象中的不一样,“你好”在UTF-16编码下共占6个字节。

运行结果好像和上面讲到的有点不相符啊!为什么会出现这样的结果的?

原因:java的字节码文件(.class)文件采用的是UTF-8编码,但是在java 运行时会使用UTF-16编码。在转码的时候会在前面加上表示字节顺序的字符,这个字符称为”零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。FEFF占用两个字节,所以就解释了为什么java环境下在UTF-16编码下,第一个汉字占4个字符,后面每个占用2个字符。

我们不妨将这些字符的在不同编码下的二进制转换为16进制并打印出来。将代码修改如下:

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(5)

控制台输出:

同音不同字闹出大的笑话(邻桌问我一个汉字占几个字节)(6)

通过对比之后,大家是不是有一个更加清晰的认识了呢?介绍完编码的这些知识,下次如果有人再问你同样的问题,我们就可以反问了:你说的是在哪种编码条件下呢?

,