char类型与字符编码

本文要点:

  1. java的内码为UTF-16;

  2. char类型无法处理所有的字符,String的length方法和charAt方法也无法处理所有的字符;

  3. MySQL中,使用utf8编码的表无法存储表情,需要使用utf8mb4。

一、字符编码   

    字符编码有一个发展过程。

  • 最初的ASCII码,使用1个byte,表示出英文中所有需要的字符(缺点:非英文的字符都无法表示)

  • 非英语系的国家,分别做出各种字符编码,满足本国的信息处理需求。例如我国的GB2312,GB18030(缺点:使用错误的编码打开一个文件,就是一堆乱码)

  • 使用统一的编码表示世界上所有的字符,由此产生了unicode

二、Unicode

    参考资料:维基百科Unicode词条 https://zh.wikipedia.org/wiki/Unicode

    unicode本身也有一个发展历程。最开始的unicode,使用2个byte表示1个字符,整个字符集可以容纳65536个字符。尔后,发现这仍然不够用,于是扩展到了使用4个byte表示一个字符。

    在表示一个Unicode字符的时候,会用“U+”开头,后面跟着十六进制的数字,例如“字”的编码就是U+5B57。

    每FFFF个编码,称作一个“位面”。U+0000到U+FFFF的这些字符,称作“基本位面”,也称作“0号位面”,简写作BMP-Basic Multilingual Plane。目前启用的位面有17个,编码从U+0000到U+10FFFF。

    基本位面中,U+D800 ~ U+DFFF用作“代理字符”,不用来单独使用,下面会说到。

 

    注意:为什么我从苹果手机上发送的表情,到了华为手机上,就有了一些差异呢?

    想想,如果苹果手机上用的是“华文黑体”,华为手机上用的是“方正楷体”,一个字传过去,写法必定会产生一些差异,但是这仍然是一个字,只是把这个字“绘制成图像”的过程不一样,这正是字体起到的作用。

    可以认为,不同的手机厂商,对每个编码绘制的“表情”也不一样,这也可以看做“表情符号”的字体。

三、Unicode的实现

    但是,计算机世界中大部分资料都是英文的,使用1个byte表示就足够了。如果每个字符都使用4个byte表示,浪费存储空间,也浪费传输带宽。

    于是,unicode在实际使用中,又发展出了3种编码。这些编码都是基于字符原有Unicode编码的再编码。

    (下面的编码,暂时先不考虑大端小端的问题)

UTF-8

    UTF-8是互联网界广泛使用的编码,其特点为不定长。

范围

Unicode编码(Binary)

UTF-8编码(Binary)

UTF-8编码byte长度

U+0000 ~ U+007F

00000000 00000000 00000000 0ZZZZZZZ

0ZZZZZZZ

1

U+0080 ~ U+07FF

00000000 00000000 00000YYY YYZZZZZZ

110YYYYY 10ZZZZZZ

2

U+0800 ~ U+D7FF

U+E000 ~ U+FFFF

(排除掉“代理字符”)

00000000 00000000 XXXXYYYY YYZZZZZZ

1110XXXX 10YYYYYY 10ZZZZZZ

3

U+010000 ~ U+10FFFF

00000000 000WWWXX XXXXYYYY YYZZZZZZ

11110WWW 10XXXXXX 10YYYYYY 10ZZZZZZ

4

    由上表可见,ASCII范围内的字符,在UTF-8中只占1个byte,编码完全一样。互联网世界大部分资料都是英文,使用UTF-8大大降低了存储、传输需要的字符数量。

    UTF-8将“基本位面”内的字符编为1~3个长度,“基本位面”以外的,就需要4个了。随着Unicode字符集的继续扩大,使用5个、甚至6个byte表示一个字符,也不是不可能。

UTF-16

    在Unicode编码只占2个byte的时候,UTF-16与Unicode编码是完全相同的。

    但是Unicode扩展到4字节的时候,UTF-16也随之做出了一些变化,由“定长码”变成了“变长码”。

范围

Unicode编码(Binary)

UTF-16编码(Binary)

UTF-16编码byte长度

U+0000 ~ U+D7FF

U+E000 ~ U+FFFF

(排除掉“代理字符”)

00000000 00000000 ZZZZZZZZ ZZZZZZZZ

ZZZZZZZZ ZZZZZZZZ

2

U+010000 ~ U+10FFFF

00000000 0000YYYY YYYYYYZZ  ZZZZZZZZ

110110YY YYYYYYYY 110111ZZ ZZZZZZZZ

4

    新的UTF-16需要兼容老的UTF-16。

    由此可见“代理字符”的作用了。大于U+FFFF的字符,在表示为UTF-16编码时,必须使用2个2byte。其中:

  • 第一个必然在D800~DBFF之间,称为“高位代理字符”

  • 第二个必然在DC00~DFFF之间,称为“低位代理字符”

    处理程序在处理UTF-16的字节流时,每次读入2个byte。

  • 如果不是“代理字符”,这2个byte就表示一个合法的字符

  • 如果是“高位代理字符”,再读取2个byte,这两个byte应当是“低位代理字符”,合起来才是一个一个合法的字符。

UTF-32

    UTF-32不需要多讲,与4字节的Unicode编码完全相同,目前是定长码。

四、java的char类型

    程序设计语言中的编码,分为“内码”和“外码”。

  • 内码倾向于使用定长码,便于处理(类似于线性表一样,方便寻址)

  • 外码倾向于使用变长码,变长码将常用字符编为短编码,罕见字符编为长编码,节省存储空间与传输带宽

    java中char和byte是两种不同的类型。1个char占用2个byte。java语言的内码为UTF-16

    java被设计出来的时候,unicode使用2个byte表示一个字符,因此char类型可以处理所有的字符。但是现在,有的字符用1个char表示不了,char类型已无法处理所有字符。因此,有文章建议,java中谨慎使用char类型处理字符

    char类型可以这样被赋值:char ch = 'a';  char ch = '\u3456'; 后一种就是将字符的unicode编码赋给char变量。

 

    以下是java在线文档中,对Character(char包装类)的说明:

Unicode Character Representations

The char data type (and therefore the value that a Character object encapsulates) are based on the original Unicode specification, which defined characters as fixed-width 16-bit entities. The Unicode Standard has since been changed to allow for characters whose representation requires more than 16 bits. The range of legal code points is now U+0000 to U+10FFFF, known as Unicode scalar value. (Refer to the definition of the U+n notation in the Unicode Standard.)

The set of characters from U+0000 to U+FFFF is sometimes referred to as the Basic Multilingual Plane (BMP). Characters whose code points are greater than U+FFFF are called supplementary characters. The Java platform uses the UTF-16 representation in char arrays and in the String and StringBuffer classes. In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogatesrange, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF).

A char value, therefore, represents Basic Multilingual Plane (BMP) code points, including the surrogate code points, or code units of the UTF-16 encoding. An int value represents all Unicode code points, including supplementary code points. The lower (least significant) 21 bits of int are used to represent Unicode code points and the upper (most significant) 11 bits must be zero. Unless otherwise specified, the behavior with respect to supplementary characters and surrogate char values is as follows:

  • The methods that only accept a char value cannot support supplementary characters. They treat char values from the surrogate ranges as undefined characters. For example, Character.isLetter('\uD840') returns false, even though this specific value if followed by any low-surrogate value in a string would represent a letter.

  • The methods that accept an int value support all Unicode characters, including supplementary characters. For example, Character.isLetter(0x2F81A) returns true because the code point value represents a letter (a CJK ideograph).

In the Java SE API documentation, Unicode code point is used for character values in the range between U+0000 and U+10FFFF, and Unicode code unitis used for 16-bit char values that are code units of the UTF-16 encoding. For more information on Unicode terminology, refer to the Unicode Glossary.

    由标红的语句可见,对于大于FFFF的字符,需要两个char来表示,不然就要借助int类型。接收char类型的函数无法处理补充字符。

    String中其实有一个char[]字段,遇到大于FFFF的字符,是用2个char来存储的。

    由此来说,一个char就不完全等效于一个“字符”了,因为大于FFFF的字符需要两个char来表示,这其中每一个char都不是一个完整的字符,只是一个“高位代理字符”或者“低位代理字符”。

 

    java中有一个概念,codePoint(代码点),由一个int来表示,目前,一个代码点可以表示一个完整的字符。

    举几个例子,如果要处理更多的字符,以下几个地方是需要注意的:

  • String类中的charAt方法,只是从char数组中取出某个char。如果这个char是一个代理字符,可能引起后续的问题。因此,如果需要处理更多的字符,应当使用codePoint方法,返回一个int值。

  • String类中的length方法,返回的仅仅是char数组的长度,这不等于字符长度。如果需要处理更多的字符,应当使用codePointCount方法,得到codePoint的数量。

 

    引申出一个问题:java源代码文件也是有编码的,java编译器是怎样知道根据哪种编码来读取源代码的呢?

    查阅资料可得:javac先使用系统默认编码进行编译,也可以通过-encoding参数来指定编码。因此不管是UTF-8源代码中的汉字,还是GBK中的汉字,虽然在源代码文件中的编码不一样,但是经过javac编译之后,都是以UTF-16的格式存储的。

你可能感兴趣的:(char类型与字符编码)