Skip to content

Commit 7fb1a60

Browse files
committed
unicode 完善
1 parent 3c166f8 commit 7fb1a60

File tree

2 files changed

+62
-39
lines changed

2 files changed

+62
-39
lines changed

chapter02/02.5.md

+59-36
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,60 @@
11
# 2.5 unicode — Unicode 码点、UTF-8/16 编码 #
22

3-
世界中的字符有许许多多,有英文,中文,韩文等。我们强烈需要一个大大的映射表把世界上的字符映射成计算机可以阅读的二进制数字(字节)。
4-
这样,每个字符都给予一个独一无二的编码,就不会出现写文字的人和阅读文字的人编码不同而出现无法读取的乱码现象了。
3+
世界中的字符有许许多多,有英文,中文,韩文等。随着全球化进程不断深入,我们强烈需要一个能够容纳世界上所有字符的字符集,方便编码为计算机能处理的二进制数。每个字符都给予一个独一无二的编号,就不会出现写文字的人和阅读文字的人使用不同的编码而出现乱码现象。
54

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

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/>
138

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

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

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 的支持包含三个包 :
2220

2321
* unicode
2422
* unicode/utf8
2523
* unicode/utf16
2624

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

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。
3229

33-
这里有个要注意的事情,go 语言的所有代码都是 UTF8 的,所以如果我们在程序中的字符串都是 utf8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的。
30+
一个 rune 就代表一个 unicode 码点,所以上面的 a 和 à 是两个不同的 rune。
31+
32+
这里有个要注意的事情,Go 语言的所有代码都是 UTF-8 的,所以我们在程序中的字符串都是 UTF-8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的(码点)。
3433

3534
## 2.5.1 unicode 包 ##
3635

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 这个源文件里。
3841

3942
```go
43+
// RangeTable 通过列出一组 Unicode 码点的范围来定义它。为了节省空间,在两个切片中列出了范围:切片的 16 位范围(R16)和切片的 32 位(R32)范围。这两个切片必须按排序顺序且不重叠。同样,R32 应该仅包含 > = 0x10000(1 << 16)的值(即附加半部分字符)。
4044
type RangeTable struct {
4145
R16 []Range16
4246
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
4454
}
4555
```
46-
来表示这个功能的字符集。这些字符集都集中列表在 table.go 这个源码里面。
4756

48-
比如控制字符集
57+
比如控制字符集合
4958

5059
```go
5160
var _Pc = &RangeTable{
@@ -96,7 +105,7 @@ func IsGraphic(r rune) bool // 是否图形字符
96105
func IsLetter(r rune) bool // 是否字母
97106
func IsLower(r rune) bool // 是否小写字符
98107
func IsMark(r rune) bool // 是否符号字符
99-
func IsNumber(r rune) bool // 是否数字字符,比如罗马数字Ⅷ也是数字字符
108+
func IsNumber(r rune) bool // 是否数字字符,比如罗马数字 Ⅷ 也是数字字符
100109
func IsOneOf(ranges []*RangeTable, r rune) bool // 是否是 RangeTable 中的一个
101110
func IsPrint(r rune) bool // 是否可打印字符
102111
func IsPunct(r rune) bool // 是否标点符号
@@ -133,6 +142,7 @@ func main() {
133142
```
134143

135144
输出结果:
145+
136146
```bash
137147
true
138148
false
@@ -144,37 +154,45 @@ false
144154
true
145155
false
146156
```
157+
147158
## 2.5.2 utf8 包 ##
148159

149-
utf8 里面的函数就有一些字节和字符的转换。
160+
utf8 包用于处理 UTF-8 编码的文本,提供一些常量和函数,包括在 rune(码点) 和 UTF-8 字节序列之间的转换。
161+
162+
1)判断是否是有效 utf8 编码的函数:
150163

151-
判断是否符合 utf8 编码的函数:
152164
* func Valid(p []byte) bool
153165
* func ValidRune(r rune) bool
154166
* func ValidString(s string) bool
155167

156-
判断 rune 所占字节数:
168+
2)得到 rune 所占字节数:
169+
157170
* func RuneLen(r rune) int
158171

159-
判断字节串或者字符串的 rune 数:
172+
3)判断字节数组或者字符串的 rune 数:
173+
160174
* func RuneCount(p []byte) int
161175
* func RuneCountInString(s string) (n int)
162176

163-
编码和解码到 rune:
177+
4)编码、解码 rune:
178+
164179
* func EncodeRune(p []byte, r rune) int
165180
* func DecodeRune(p []byte) (r rune, size int)
166181
* func DecodeRuneInString(s string) (r rune, size int)
167182
* func DecodeLastRune(p []byte) (r rune, size int)
168183
* func DecodeLastRuneInString(s string) (r rune, size int)
169184

170-
是否为完整 rune:
185+
5)是否为完整 rune:
186+
171187
* func FullRune(p []byte) bool
172188
* func FullRuneInString(s string) bool
173189

174-
是否为 rune 第一个字节:
190+
6)判断一个字节是否为 rune 的第一个字节:
191+
175192
* func RuneStart(b byte) bool
176193

177194
示例:
195+
178196
```go
179197
word:=[]byte("")
180198

@@ -203,6 +221,7 @@ fmt.Println(utf8.RuneStart(word[0]))
203221
```
204222

205223
运行结果:
224+
206225
```bash
207226
false
208227
true
@@ -223,9 +242,9 @@ true
223242

224243
## 2.5.3 utf16 包 ##
225244

226-
utf16 的包的函数就比较少了
245+
utf16 包的函数就比较少了,主要是 UTF-16 序列的编码和解码
227246

228-
将 uint16 和 rune 进行转换
247+
将 uint16 和 rune 进行转换
229248

230249
```go
231250
func Encode(s []rune) []uint16
@@ -236,27 +255,30 @@ func IsSurrogate(r rune) bool // 是否为有效代理对
236255
```
237256

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

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

243263
示例:
264+
244265
```go
245-
words :=[]rune{'𝓐','𝓑'}
266+
words := []rune{'𝓐','𝓑'}
246267

247-
u16:=utf16.Encode(words)
268+
u16 := utf16.Encode(words)
248269
fmt.Println(u16)
249270
fmt.Println(utf16.Decode(u16))
250271

251-
r1,r2:=utf16.EncodeRune('𝓐')
272+
r1, r2 := utf16.EncodeRune('𝓐')
252273
fmt.Println(r1,r2)
253-
fmt.Println(utf16.DecodeRune(r1,r2))
274+
fmt.Println(utf16.DecodeRune(r1, r2))
254275
fmt.Println(utf16.IsSurrogate(r1))
255276
fmt.Println(utf16.IsSurrogate(r2))
256277
fmt.Println(utf16.IsSurrogate(1234))
257278
```
258279

259280
输出结果:
281+
260282
```bash
261283
[55349 56528 55349 56529]
262284
[120016 120017]
@@ -269,5 +291,6 @@ false
269291

270292
# 导航 #
271293

272-
- 上一节:[strings — 字符串操作](02.1.md)
273-
- 下一节:[strconv — 字符串和基本数据类型之间转换](02.3.md)
294+
- 上一节:[regexp — 正则表达式](02.4.md)
295+
- 第三章:[数据结构和算法](/chapter03/03.0.md)
296+

directory.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
- 第1章:[输入和输出](chapter01/01.0.md)
33
- 1.1 [io — 基本的 IO 接口](chapter01/01.1.md)
44
- 1.2 [ioutil — 方便的IO操作函数集](chapter01/01.2.md)
5-
- 1.3 [fmt — 格式化IO](chapter01/01.3.md)
6-
- 1.4 [ bufio — 缓存IO](chapter01/01.4.md)
5+
- 1.3 [fmt — 格式化IO](chapter01/01.3.md)
6+
- 1.4 [ bufio — 缓存IO](chapter01/01.4.md)
77
- 第2章:[文本](chapter02/02.0.md)
88
- 2.1 [strings — 字符串操作](chapter02/02.1.md)
9-
- 2.2 [ bytes — byte slice 便利操作](chapter02/02.2.md)
9+
- 2.2 [bytes — byte slice 便利操作](chapter02/02.2.md)
1010
- 2.3 [strconv — 字符串和基本数据类型之间转换](chapter02/02.3.md)
1111
- 2.4 [regexp — 正则表达式](chapter02/02.4.md)
1212
- 2.5 [unicode — Unicode码点、UTF-8/16编码](chapter02/02.5.md)

0 commit comments

Comments
 (0)