2.5 unicode — Unicode码点、UTF-8/16编码

世界中的字符有许许多多,有英文,中文,韩文等。我们强烈需要一个大大的映射表把世界上的字符映射成计算机可以阅读的二进制数字(字节)。 这样,每个字符都给予一个独一无二的编码,就不会出现写文字的人和阅读文字的人编码不同而出现无法读取的乱码现象了。

于是Unicode就出现了,它是一种所有符号的编码映射。最开始的时候,unicode认为使用两个字节,也就是16位就能包含所有的字符了。 但是非常可惜,两个字节最多只能覆盖65536个字符,这显然是不够的,于是附加了一套字符编码,即unicode4.0,附加的字符用4个字节表示。 现在为止,大概Unicode可以覆盖100多万个字符了。

Unicode只是定义了一个字符和一个编码的映射,但是呢,对应的存储却没有制定。 比如一个编码0x0041代表大写字母A,那么可能有一种存储至少有4个字节,那可能0x00000041来存储代表A。 这个就是unicode的具体实现。unicode的具体实现有很多种,UTF-8和UTF-16就是其中两种。

UTF-8表示最少用一个字节就能表示一个字符的编码实现。它采取的方式是对不同的语言使用不同的方法,将unicode编码按照这个方法进行转换。 我们只要记住最后的结果是英文占一个字节,中文占三个字节。这种编码实现方式也是现在应用最为广泛的方式了。

UTF-16表示最少用两个字节能表示一个字符的编码实现。同样是对unicode编码进行转换,它的结果是英文占用两个字节,中文占用两个或者四个字节。 实际上,UTF-16就是最严格实现了unicode4.0。但由于英文是最通用的语言,所以推广程度没有UTF-8那么普及。

回到go对unicode包的支持,由于UTF-8的作者Ken Thompson同时也是go语言的创始人,所以说,在字符支持方面,几乎没有语言的理解会高于go了。 go对unicode的支持包含三个包:

  • unicode
  • unicode/utf8
  • unicode/utf16

unicode包包含基本的字符判断函数。utf8包主要负责rune和byte之间的转换。utf16包负责rune和uint16数组之间的转换。

由于字符的概念有的时候比较模糊,比如字符(小写a)普通显示为a,在重音字符中(grave-accented)中显示为à。 这时候字符(character)的概念就有点不准确了,因为a和à显然是两个不同的unicode编码,但是却代表同一个字符,所以引入了rune。 一个rune就代表一个unicode编码,所以上面的a和à是两个不同的rune。

这里有个要注意的事情,go语言的所有代码都是UTF8的,所以如果我们在程序中的字符串都是utf8编码的,但是我们的单个字符(单引号扩起来的)却是unicode的。

2.5.1 unicode包

unicode包含了对rune的判断。这个包把所有unicode涉及到的编码进行了分类,使用结构

type RangeTable struct {
	R16         []Range16
	R32         []Range32
	LatinOffset int
}

来表示这个功能的字符集。这些字符集都集中列表在table.go这个源码里面。

比如控制字符集:

var _Pc = &RangeTable{
	R16: []Range16{
		{0x005f, 0x203f, 8160},
		{0x2040, 0x2054, 20},
		{0xfe33, 0xfe34, 1},
		{0xfe4d, 0xfe4f, 1},
		{0xff3f, 0xff3f, 1},
	},
}

回到包的函数,我们看到有下面这些判断函数:

func IsControl(r rune) bool  // 是否控制字符
func IsDigit(r rune) bool  // 是否阿拉伯数字字符,即1-9
func IsGraphic(r rune) bool // 是否图形字符
func IsLetter(r rune) bool // 是否字母
func IsLower(r rune) bool // 是否小写字符
func IsMark(r rune) bool // 是否符号字符
func IsNumber(r rune) bool // 是否数字字符,比如罗马数字Ⅷ也是数字字符
func IsOneOf(ranges []*RangeTable, r rune) bool // 是否是RangeTable中的一个
func IsPrint(r rune) bool // 是否可打印字符
func IsPunct(r rune) bool // 是否标点符号
func IsSpace(r rune) bool // 是否空格
func IsSymbol(r rune) bool // 是否符号字符
func IsTitle(r rune) bool // 是否title case
func IsUpper(r rune) bool // 是否大写字符

看下面这个例子:

func main() {
	single := '\u0015'
	fmt.Println(unicode.IsControl(single))  //true
	single = '\ufe35'
	fmt.Println(unicode.IsControl(single)) // false

	digit := rune('1')
	fmt.Println(unicode.IsDigit(digit)) //true
	fmt.Println(unicode.IsNumber(digit)) //true
	letter := rune('Ⅷ')
	fmt.Println(unicode.IsDigit(letter)) //false
	fmt.Println(unicode.IsNumber(letter)) //true
}

2.5.2 utf8包

utf8里面的函数就有一些字节和字符的转换。

判断是否符合utf8编码的函数:

  • func Valid(p []byte) bool
  • func ValidRune(r rune) bool
  • func ValidString(s string) bool

判断rune的长度的函数:

  • func RuneLen(r rune) int

判断字节串或者字符串的rune数

  • func RuneCount(p []byte) int
  • func RuneCountInString(s string) (n int)

编码和解码rune到byte

  • func DecodeRune(p []byte) (r rune, size int)
  • func EncodeRune(p []byte, r rune) int

2.5.3 utf16包

utf16的包的函数就比较少了。

将int16和rune进行转换

  • func Decode(s []uint16) []rune
  • func Encode(s []rune) []uint16

unicode有个基本字符平面和增补平面的概念,基本字符平面只有65535个字符,增补平面(有16个)加上去就能表示1114112个字符。 utf16就是严格实现了unicode的这种编码规范。

而基本字符和增补平面字符就是一个代理对(Surrogate Pair)。一个代理对可以和一个rune进行转换。

  • func DecodeRune(r1, r2 rune) rune
  • func EncodeRune(r rune) (r1, r2 rune)

导航


书籍推荐