世界中的字符有许许多多,有英文,中文,韩文等。随着全球化进程不断深入,我们强烈需要一个能够容纳世界上所有字符的字符集,方便编码为计算机能处理的二进制数。每个字符都给予一个独一无二的编号,就不会出现写文字的人和阅读文字的人使用不同的编码而出现乱码现象。
于是 Unicode 就出现了,它将所有的字符用一个唯一的数字表示。最开始的时候,unicode 认为使用两个字节,也就是 16 位就能包含所有的字符了。但是非常可惜,两个字节最多只能覆盖 65536 个字符,这显然是不够的,于是出现了 unicode4.0,附加的字符用 4 个字节表示。现在为止,大概 Unicode 可以覆盖 100 多万个字符了。
Unicode 就只是代表字符集,也就是只定义了字符到码点(Code Point)的映射(可以理解为 Unicode 定义了一个表,表中每一行记录是一个字符到一个唯一 ID 的映射,而这个 ID 就是码点),并没有定义码点具体如何编码。对应的字符编码有多种,比如 UTF-8、UTF-16 等。所以需要理解字符集和字符编码是不一样的。更详细的说明可以参考该文:https://polarisxu.studygolang.com/posts/basic/char-set-encoding/。
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 语言的所有代码都是 UTF-8 的,所以我们在程序中的字符串都是 UTF-8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的(码点)。
unicode 提供数据和函数来测试 Unicode 代码点(Code Point,用 rune 存储)的某些属性。
注意,在 Go1.16 之前,unicode 包实现的 unicode 版本是 12.0,Go1.16 实现了 13.0
这个包把所有 unicode 涉及到的码点进行了分类,使用结构 RengeTable 来表示不同类别的字符集合。这些类别都列在 table.go 这个源文件里。
// RangeTable 通过列出一组 Unicode 码点的范围来定义它。为了节省空间,在两个切片中列出了范围:切片的 16 位范围(R16)和切片的 32 位(R32)范围。这两个切片必须按排序顺序且不重叠。同样,R32 应该仅包含 > = 0x10000(1 << 16)的值(即附加半部分字符)。
type RangeTable struct {
R16 []Range16
R32 []Range32
LatinOffset int // Hi <= MaxLatin1 的 R16 中的条目数;在 Go 1.1 中添加
}
type Range16 struct {
Lo uint16
Hi uint16
Stride uint16
}
比如控制字符集合:
var _Pc = &RangeTable{
R16: []Range16{
{0x005f, 0x203f, 8160},
{0x2040, 0x2054, 20},
{0xfe33, 0xfe34, 1},
{0xfe4d, 0xfe4f, 1},
{0xff3f, 0xff3f, 1},
},
}
比如对国内开发者很实用的汉字字符集:
var _Han = &RangeTable{
R16: []Range16{
{0x2e80, 0x2e99, 1},
{0x2e9b, 0x2ef3, 1},
{0x2f00, 0x2fd5, 1},
{0x3005, 0x3005, 1},
{0x3007, 0x3007, 1},
{0x3021, 0x3029, 1},
{0x3038, 0x303b, 1},
{0x3400, 0x4db5, 1},
{0x4e00, 0x9fea, 1},
{0xf900, 0xfa6d, 1},
{0xfa70, 0xfad9, 1},
},
R32: []Range32{
{0x20000, 0x2a6d6, 1},
{0x2a700, 0x2b734, 1},
{0x2b740, 0x2b81d, 1},
{0x2b820, 0x2cea1, 1},
{0x2ceb0, 0x2ebe0, 1},
{0x2f800, 0x2fa1d, 1},
},
}
回到包的函数,我们看到有下面这些判断函数:
func IsControl(r rune) bool // 是否控制字符
func IsDigit(r rune) bool // 是否阿拉伯数字字符,即 0-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 Is(rangeTab *RangeTable, r rune) bool // r 是否为 rangeTab 类型的字符
func In(r rune, ranges ...*RangeTable) bool // r 是否为 ranges 中任意一个类型的字符
看下面这个例子:
func main() {
single := '\u0015'
fmt.Println(unicode.IsControl(single))
single = '\ufe35'
fmt.Println(unicode.IsControl(single))
digit := '1'
fmt.Println(unicode.IsDigit(digit))
fmt.Println(unicode.IsNumber(digit))
letter := 'Ⅷ'
fmt.Println(unicode.IsDigit(letter))
fmt.Println(unicode.IsNumber(letter))
han:='你'
fmt.Println(unicode.IsDigit(han))
fmt.Println(unicode.Is(unicode.Han,han))
fmt.Println(unicode.In(han,unicode.Gujarati,unicode.White_Space))
}
输出结果:
true
false
true
true
false
true
false
true
false
utf8 包用于处理 UTF-8 编码的文本,提供一些常量和函数,包括在 rune(码点) 和 UTF-8 字节序列之间的转换。
1)判断是否是有效 utf8 编码的函数:
- func Valid(p []byte) bool
- func ValidRune(r rune) bool
- func ValidString(s string) bool
2)得到 rune 所占字节数:
- func RuneLen(r rune) int
3)判断字节数组或者字符串的 rune 数:
- func RuneCount(p []byte) int
- func RuneCountInString(s string) (n int)
4)编码、解码 rune:
- func EncodeRune(p []byte, r rune) int
- func DecodeRune(p []byte) (r rune, size int)
- func DecodeRuneInString(s string) (r rune, size int)
- func DecodeLastRune(p []byte) (r rune, size int)
- func DecodeLastRuneInString(s string) (r rune, size int)
5)是否为完整 rune:
- func FullRune(p []byte) bool
- func FullRuneInString(s string) bool
6)判断一个字节是否为 rune 的第一个字节:
- func RuneStart(b byte) bool
示例:
word:=[]byte("界")
fmt.Println(utf8.Valid(word[:2]))
fmt.Println(utf8.ValidRune('界'))
fmt.Println(utf8.ValidString("世界"))
fmt.Println(utf8.RuneLen('界'))
fmt.Println(utf8.RuneCount(word))
fmt.Println(utf8.RuneCountInString("世界"))
p:=make([]byte,3)
utf8.EncodeRune(p,'好')
fmt.Println(p)
fmt.Println(utf8.DecodeRune(p))
fmt.Println(utf8.DecodeRuneInString("你好"))
fmt.Println(utf8.DecodeLastRune([]byte("你好")))
fmt.Println(utf8.DecodeLastRuneInString("你好"))
fmt.Println(utf8.FullRune(word[:2]))
fmt.Println(utf8.FullRuneInString("你好"))
fmt.Println(utf8.RuneStart(word[1]))
fmt.Println(utf8.RuneStart(word[0]))
运行结果:
false
true
true
3
1
2
[229 165 189]
22909 3
20320 3
22909 3
22909 3
false
true
false
true
utf16 包的函数就比较少了,主要是 UTF-16 序列的编码和解码。
将 uint16 和 rune 进行转换:
func Encode(s []rune) []uint16
func EncodeRune(r rune) (r1, r2 rune)
func Decode(s []uint16) []rune
func DecodeRune(r1, r2 rune) rune
func IsSurrogate(r rune) bool // 是否为有效代理对
unicode 有个基本字符平面和增补平面的概念,基本字符平面只有 65535 个字符,增补平面(有 16 个)加上去就能表示 1114112 个字符。
utf16 严格地实现了 unicode 的这种编码规范。
而基本字符和增补平面字符就是一个代理对(Surrogate Pair)。一个代理对可以和一个 rune 进行转换。
示例:
words := []rune{'𝓐','𝓑'}
u16 := utf16.Encode(words)
fmt.Println(u16)
fmt.Println(utf16.Decode(u16))
r1, r2 := utf16.EncodeRune('𝓐')
fmt.Println(r1,r2)
fmt.Println(utf16.DecodeRune(r1, r2))
fmt.Println(utf16.IsSurrogate(r1))
fmt.Println(utf16.IsSurrogate(r2))
fmt.Println(utf16.IsSurrogate(1234))
输出结果:
[55349 56528 55349 56529]
[120016 120017]
55349 56528
120016
true
true
false
- 上一节:regexp — 正则表达式
- 第三章:数据结构和算法