1
1
# 2.5 unicode — Unicode 码点、UTF-8/16 编码 #
2
2
3
- 世界中的字符有许许多多,有英文,中文,韩文等。我们强烈需要一个大大的映射表把世界上的字符映射成计算机可以阅读的二进制数字(字节)。
4
- 这样,每个字符都给予一个独一无二的编码,就不会出现写文字的人和阅读文字的人编码不同而出现无法读取的乱码现象了。
3
+ 世界中的字符有许许多多,有英文,中文,韩文等。随着全球化进程不断深入,我们强烈需要一个能够容纳世界上所有字符的字符集,方便编码为计算机能处理的二进制数。每个字符都给予一个独一无二的编号,就不会出现写文字的人和阅读文字的人使用不同的编码而出现乱码现象。
5
4
6
- 于是 Unicode 就出现了,它是一种所有符号的编码映射。最开始的时候,unicode 认为使用两个字节,也就是 16 位就能包含所有的字符了。
7
- 但是非常可惜,两个字节最多只能覆盖 65536 个字符,这显然是不够的,于是附加了一套字符编码,即 unicode4.0,附加的字符用 4 个字节表示。
8
- 现在为止,大概 Unicode 可以覆盖 100 多万个字符了。
5
+ 于是 Unicode 就出现了,它将所有的字符用一个唯一的数字表示。最开始的时候,unicode 认为使用两个字节,也就是 16 位就能包含所有的字符了。但是非常可惜,两个字节最多只能覆盖 65536 个字符,这显然是不够的,于是出现了 unicode4.0,附加的字符用 4 个字节表示。现在为止,大概 Unicode 可以覆盖 100 多万个字符了。
9
6
10
- Unicode 只是定义了一个字符和一个编码的映射,但是呢,对应的存储却没有制定。
11
- 比如一个编码 0x0041 代表大写字母 A,那么可能有一种存储至少有 4 个字节,那可能 0x00000041 来存储代表 A。
12
- 这个就是 unicode 的具体实现。unicode 的具体实现有很多种,UTF-8 和 UTF-16 就是其中两种。
7
+ Unicode 就只是代表字符集,也就是只定义了字符到码点(Code Point)的映射(可以理解为 Unicode 定义了一个表,表中每一行记录是一个字符到一个唯一 ID 的映射,而这个 ID 就是码点),并没有定义码点具体如何编码。对应的字符编码有多种,比如 UTF-8、UTF-16 等。所以需要理解字符集和字符编码是不一样的。更详细的说明可以参考该文:< https://polarisxu.studygolang.com/posts/basic/char-set-encoding/ > 。
13
8
14
9
UTF-8 表示最少用一个字节就能表示一个字符的编码实现。它采取的方式是对不同的语言使用不同的方法,将 unicode 编码按照这个方法进行转换。
10
+
15
11
我们只要记住最后的结果是英文占一个字节,中文占三个字节。这种编码实现方式也是现在应用最为广泛的方式了。
16
12
17
13
UTF-16 表示最少用两个字节能表示一个字符的编码实现。同样是对 unicode 编码进行转换,它的结果是英文占用两个字节,中文占用两个或者四个字节。
18
- 实际上,UTF-16 就是最严格实现了 unicode4.0。但由于英文是最通用的语言,所以推广程度没有 UTF-8 那么普及。
19
14
20
- 回到 go 对 unicode 包的支持,由于 UTF-8 的作者 Ken Thompson 同时也是 go 语言的创始人,所以说,在字符支持方面,几乎没有语言的理解会高于 go 了。
21
- go 对 unicode 的支持包含三个包 :
15
+ 实际上,UTF-16 就是最严格实现了 unicode4.0 的编码方式。但由于英文是最通用的语言,所以推广程度没有 UTF-8 那么普及。
16
+
17
+ 回到 Go 对 unicode 包的支持,由于 UTF-8 的作者 Ken Thompson 同时也是 Go 语言的创始人,所以说,在字符支持方面,几乎没有语言的理解会高于 Go 了。
18
+
19
+ Go 对 unicode 的支持包含三个包 :
22
20
23
21
* unicode
24
22
* unicode/utf8
25
23
* unicode/utf16
26
24
27
25
unicode 包包含基本的字符判断函数。utf8 包主要负责 rune 和 byte 之间的转换。utf16 包负责 rune 和 uint16 数组之间的转换。
28
26
29
- 由于字符的概念有的时候比较模糊,比如字符(小写 a)普通显示为 a,在重音字符中(grave-accented)中显示为à。
30
- 这时候字符(character)的概念就有点不准确了,因为 a 和à显然是两个不同的 unicode 编码,但是却代表同一个字符,所以引入了 rune。
31
- 一个 rune 就代表一个 unicode 编码,所以上面的 a 和à是两个不同的 rune。
27
+ 由于字符的概念有的时候比较模糊,比如字符(小写 a)普通显示为 a,在重音字符中(grave-accented)中显示为 à。
28
+ 这时候字符(character)的概念就有点不准确了,因为 a 和 à 显然是两个不同的 unicode 编码,但是却代表同一个字符,所以引入了 rune。
32
29
33
- 这里有个要注意的事情,go 语言的所有代码都是 UTF8 的,所以如果我们在程序中的字符串都是 utf8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的。
30
+ 一个 rune 就代表一个 unicode 码点,所以上面的 a 和 à 是两个不同的 rune。
31
+
32
+ 这里有个要注意的事情,Go 语言的所有代码都是 UTF-8 的,所以我们在程序中的字符串都是 UTF-8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的(码点)。
34
33
35
34
## 2.5.1 unicode 包 ##
36
35
37
- unicode 包含了对 rune 的判断。这个包把所有 unicode 涉及到的编码进行了分类,使用结构
36
+ unicode 提供数据和函数来测试 Unicode 代码点(Code Point,用 rune 存储)的某些属性。
37
+
38
+ > 注意,在 Go1.16 之前,unicode 包实现的 unicode 版本是 12.0,Go1.16 实现了 13.0
39
+
40
+ 这个包把所有 unicode 涉及到的码点进行了分类,使用结构 RengeTable 来表示不同类别的字符集合。这些类别都列在 table.go 这个源文件里。
38
41
39
42
``` go
43
+ // RangeTable 通过列出一组 Unicode 码点的范围来定义它。为了节省空间,在两个切片中列出了范围:切片的 16 位范围(R16)和切片的 32 位(R32)范围。这两个切片必须按排序顺序且不重叠。同样,R32 应该仅包含 > = 0x10000(1 << 16)的值(即附加半部分字符)。
40
44
type RangeTable struct {
41
45
R16 []Range16
42
46
R32 []Range32
43
- LatinOffset int
47
+ LatinOffset int // Hi <= MaxLatin1 的 R16 中的条目数;在 Go 1.1 中添加
48
+ }
49
+
50
+ type Range16 struct {
51
+ Lo uint16
52
+ Hi uint16
53
+ Stride uint16
44
54
}
45
55
```
46
- 来表示这个功能的字符集。这些字符集都集中列表在 table.go 这个源码里面。
47
56
48
- 比如控制字符集 :
57
+ 比如控制字符集合 :
49
58
50
59
``` go
51
60
var _Pc = &RangeTable{
@@ -96,7 +105,7 @@ func IsGraphic(r rune) bool // 是否图形字符
96
105
func IsLetter(r rune) bool // 是否字母
97
106
func IsLower(r rune) bool // 是否小写字符
98
107
func IsMark(r rune) bool // 是否符号字符
99
- func IsNumber(r rune) bool // 是否数字字符,比如罗马数字Ⅷ也是数字字符
108
+ func IsNumber(r rune) bool // 是否数字字符,比如罗马数字 Ⅷ 也是数字字符
100
109
func IsOneOf(ranges []*RangeTable, r rune) bool // 是否是 RangeTable 中的一个
101
110
func IsPrint(r rune) bool // 是否可打印字符
102
111
func IsPunct(r rune) bool // 是否标点符号
@@ -133,6 +142,7 @@ func main() {
133
142
```
134
143
135
144
输出结果:
145
+
136
146
``` bash
137
147
true
138
148
false
@@ -144,37 +154,45 @@ false
144
154
true
145
155
false
146
156
```
157
+
147
158
## 2.5.2 utf8 包 ##
148
159
149
- utf8 里面的函数就有一些字节和字符的转换。
160
+ utf8 包用于处理 UTF-8 编码的文本,提供一些常量和函数,包括在 rune(码点) 和 UTF-8 字节序列之间的转换。
161
+
162
+ 1)判断是否是有效 utf8 编码的函数:
150
163
151
- 判断是否符合 utf8 编码的函数:
152
164
* func Valid(p [ ] byte) bool
153
165
* func ValidRune(r rune) bool
154
166
* func ValidString(s string) bool
155
167
156
- 判断 rune 所占字节数:
168
+ 2)得到 rune 所占字节数:
169
+
157
170
* func RuneLen(r rune) int
158
171
159
- 判断字节串或者字符串的 rune 数:
172
+ 3)判断字节数组或者字符串的 rune 数:
173
+
160
174
* func RuneCount(p [ ] byte) int
161
175
* func RuneCountInString(s string) (n int)
162
176
163
- 编码和解码到 rune:
177
+ 4)编码、解码 rune:
178
+
164
179
* func EncodeRune(p [ ] byte, r rune) int
165
180
* func DecodeRune(p [ ] byte) (r rune, size int)
166
181
* func DecodeRuneInString(s string) (r rune, size int)
167
182
* func DecodeLastRune(p [ ] byte) (r rune, size int)
168
183
* func DecodeLastRuneInString(s string) (r rune, size int)
169
184
170
- 是否为完整 rune:
185
+ 5)是否为完整 rune:
186
+
171
187
* func FullRune(p [ ] byte) bool
172
188
* func FullRuneInString(s string) bool
173
189
174
- 是否为 rune 第一个字节:
190
+ 6)判断一个字节是否为 rune 的第一个字节:
191
+
175
192
* func RuneStart(b byte) bool
176
193
177
194
示例:
195
+
178
196
``` go
179
197
word := []byte (" 界" )
180
198
@@ -203,6 +221,7 @@ fmt.Println(utf8.RuneStart(word[0]))
203
221
```
204
222
205
223
运行结果:
224
+
206
225
``` bash
207
226
false
208
227
true
223
242
224
243
## 2.5.3 utf16 包 ##
225
244
226
- utf16 的包的函数就比较少了 。
245
+ utf16 包的函数就比较少了,主要是 UTF-16 序列的编码和解码 。
227
246
228
- 将 uint16 和 rune 进行转换
247
+ 将 uint16 和 rune 进行转换:
229
248
230
249
``` go
231
250
func Encode (s []rune ) []uint16
@@ -236,27 +255,30 @@ func IsSurrogate(r rune) bool // 是否为有效代理对
236
255
```
237
256
238
257
unicode 有个基本字符平面和增补平面的概念,基本字符平面只有 65535 个字符,增补平面(有 16 个)加上去就能表示 1114112 个字符。
239
- utf16 就是严格实现了 unicode 的这种编码规范。
258
+
259
+ utf16 严格地实现了 unicode 的这种编码规范。
240
260
241
261
而基本字符和增补平面字符就是一个代理对(Surrogate Pair)。一个代理对可以和一个 rune 进行转换。
242
262
243
263
示例:
264
+
244
265
```go
245
- words :=[]rune{' 𝓐' ,' 𝓑' }
266
+ words := []rune{' 𝓐' ,' 𝓑' }
246
267
247
- u16 := utf16.Encode (words)
268
+ u16 := utf16.Encode (words)
248
269
fmt.Println (u16)
249
270
fmt.Println (utf16.Decode (u16))
250
271
251
- r1,r2 := utf16.EncodeRune (' 𝓐' )
272
+ r1 , r2 := utf16.EncodeRune (' 𝓐' )
252
273
fmt.Println (r1,r2)
253
- fmt.Println (utf16.DecodeRune (r1,r2))
274
+ fmt.Println (utf16.DecodeRune (r1, r2))
254
275
fmt.Println (utf16.IsSurrogate (r1))
255
276
fmt.Println (utf16.IsSurrogate (r2))
256
277
fmt.Println (utf16.IsSurrogate (1234 ))
257
278
```
258
279
259
280
输出结果:
281
+
260
282
``` bash
261
283
[55349 56528 55349 56529]
262
284
[120016 120017]
@@ -269,5 +291,6 @@ false
269
291
270
292
# 导航 #
271
293
272
- - 上一节:[ strings — 字符串操作] ( 02.1.md )
273
- - 下一节:[ strconv — 字符串和基本数据类型之间转换] ( 02.3.md )
294
+ - 上一节:[ regexp — 正则表达式] ( 02.4.md )
295
+ - 第三章:[ 数据结构和算法] ( /chapter03/03.0.md )
296
+
0 commit comments