1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

修改错字

统一Unicode拼写
This commit is contained in:
启程 2018-01-09 21:22:55 +08:00
parent f16ece9385
commit 2fe1f5e044

View File

@ -185,7 +185,7 @@ fmt.Println("len(s1):", (*reflect.StringHeader)(unsafe.Pointer(&s1)).Len) // 5
fmt.Println("len(s2):", (*reflect.StringHeader)(unsafe.Pointer(&s2)).Len) // 5 fmt.Println("len(s2):", (*reflect.StringHeader)(unsafe.Pointer(&s2)).Len) // 5
``` ```
根据Go语言规范Go语言的源文件都是采用UTF8编码。因此Go源文件中出现的字符串面值常量一般也是UTF8编码的对于转义字符则没有这个限制。提到Go字符串时我们一般都会假设字符串对应的是一个合法的UTF8编码的字符序列。可以用内置的`print`调试函数或`fmt.Print`函数直接打印,也可以用`for range`循环直接遍历UTF8解码后的UNICODE码点值。 根据Go语言规范Go语言的源文件都是采用UTF8编码。因此Go源文件中出现的字符串面值常量一般也是UTF8编码的对于转义字符则没有这个限制。提到Go字符串时我们一般都会假设字符串对应的是一个合法的UTF8编码的字符序列。可以用内置的`print`调试函数或`fmt.Print`函数直接打印,也可以用`for range`循环直接遍历UTF8解码后的Unicode码点值。
下面的“Hello, 世界”字符串中包含了中文字符,可以通过打印转型为字节类型来查看字符底层对应的数据: 下面的“Hello, 世界”字符串中包含了中文字符,可以通过打印转型为字节类型来查看字符底层对应的数据:
@ -210,7 +210,7 @@ fmt.Println("\xe7\x95\x8c") // 打印: 界
![](../images/ch1-03-string-2.png) ![](../images/ch1-03-string-2.png)
Go语言的字符串中可以存放任意的二进制字节序列而且即是UTF8字符序列也可能会遇到坏的编码。如果遇到一个错误的UTF8编码输入将生成一个特别的UNICODE字符‘\uFFFD这个字符在不同的软件中的显示效果可能不太一样在印刷中这个符号通常是一个黑色六角形或钻石形状里面包含一个白色的问号<E58FB7> Go语言的字符串中可以存放任意的二进制字节序列而且即使是UTF8字符序列也可能会遇到坏的编码。如果遇到一个错误的UTF8编码输入将生成一个特别的Unicode字符‘\uFFFD这个字符在不同的软件中的显示效果可能不太一样在印刷中这个符号通常是一个黑色六角形或钻石形状里面包含一个白色的问号<E58FB7>
下面的字符串中我们故意损坏了第一字符的第二和第三字节因此第一字符将会打印为“<EFBFBD>第二和第三字节则被忽略后面的“abc”依然可以正常解码打印错误编码不会向前扩散是UTF8编码的优秀特性之一 下面的字符串中我们故意损坏了第一字符的第二和第三字节因此第一字符将会打印为“<EFBFBD>第二和第三字节则被忽略后面的“abc”依然可以正常解码打印错误编码不会向前扩散是UTF8编码的优秀特性之一
@ -257,7 +257,7 @@ fmt.Printf("%#v\n", []rune("Hello, 世界")) // []int32{19990, 30028}
fmt.Printf("%#v\n", string([]rune{'世', '界'})) // 世界 fmt.Printf("%#v\n", string([]rune{'世', '界'})) // 世界
``` ```
从上面代码的输出结果来看,我们可以发现`[]rune`其实是`[]int32`类型,这里的`rune`只是`int32`类型的别名,并不是重新定义的类型。`rune`用于表示每个UNICODE码点目前只使用了21个bit位。 从上面代码的输出结果来看,我们可以发现`[]rune`其实是`[]int32`类型,这里的`rune`只是`int32`类型的别名,并不是重新定义的类型。`rune`用于表示每个Unicode码点目前只使用了21个bit位。
字符串相关的强制类型转换主要涉及到`[]byte``[]rune`两种类型。每个转换都可能隐含重新分配内存的代价,最坏的情况下它们的运算时间复杂度都是`O(n)`。不过字符串和`[]rune`的转换要更为特殊一些,因为一般这种强制类型转换要求两个类型的底层内存结构要尽量一致,显然它们底层对应的`[]byte``[]int32`类型是完全不同的内部布局,因此这种转换可能隐含重新分配内存的操作。 字符串相关的强制类型转换主要涉及到`[]byte``[]rune`两种类型。每个转换都可能隐含重新分配内存的代价,最坏的情况下它们的运算时间复杂度都是`O(n)`。不过字符串和`[]rune`的转换要更为特殊一些,因为一般这种强制类型转换要求两个类型的底层内存结构要尽量一致,显然它们底层对应的`[]byte``[]int32`类型是完全不同的内部布局,因此这种转换可能隐含重新分配内存的操作。
@ -276,7 +276,7 @@ func forOnString(s string, forBody func(i int, r rune)) {
} }
``` ```
`for range`迭代字符串时每次解码一个UNICODE字符,然后进入`for`循环体,遇到崩坏的编码并不会导致迭代停止。 `for range`迭代字符串时每次解码一个Unicode字符,然后进入`for`循环体,遇到崩坏的编码并不会导致迭代停止。
**`[]byte(s)`转换模拟实现** **`[]byte(s)`转换模拟实现**
@ -325,7 +325,7 @@ func str2runes(s []byte) []rune {
} }
``` ```
因为底层内存结构的差异,字符串到`[]rune`的转换必然会导致重新分配`[]rune`内存空间然后依次解码并复制对应的UNICODE码点值。这种强制转换并不存在前面提到的字符串和字节切片转化时的优化情况。 因为底层内存结构的差异,字符串到`[]rune`的转换必然会导致重新分配`[]rune`内存空间然后依次解码并复制对应的Unicode码点值。这种强制转换并不存在前面提到的字符串和字节切片转化时的优化情况。
**`string(runes)`转换模拟实现** **`string(runes)`转换模拟实现**
@ -552,14 +552,14 @@ func FindPhoneNumber(filename string) []byte {
} }
``` ```
类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾元素后,被删除的元素依然被切片底层数组引用,从而导致不能即使被自动垃圾回收器回收(这要依赖回收器的实现方式): 类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):
```go ```go
var a []*int{ ... } var a []*int{ ... }
a = a[:len(a)-1] // 本删除的最后一个元素依然被引用, 可能导致GC操作被阻碍 a = a[:len(a)-1] // 本删除的最后一个元素依然被引用, 可能导致GC操作被阻碍
``` ```
保险的方式是先将需要自动内存回收的元素设置为`nil`,保证自动回收器可发现需要回收的对象,然后再进行切片的删除操作: 保险的方式是先将需要自动内存回收的元素设置为`nil`,保证自动回收器可发现需要回收的对象,然后再进行切片的删除操作:
```go ```go
var a []*int{ ... } var a []*int{ ... }
@ -567,12 +567,12 @@ a[len(a)-1] = nil // GC回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素 a = a[:len(a)-1] // 从切片删除最后一个元素
``` ```
当然如果切片存在的周期很短的话可以不用刻意处理这个问题。因为如果切片本身已经可以被GC回收的话切片对应的每个元素自然也就是可以被回收了。 当然如果切片存在的周期很短的话可以不用刻意处理这个问题。因为如果切片本身已经可以被GC回收的话切片对应的每个元素自然也就是可以被回收了。
**切片类型强制转换** **切片类型强制转换**
为了安全,当两个切片类型`[]T``[]Y`的底层原始切片类型不同时Go语言是无法直接转换类型的。不过安全都是有一定代价的有时候这种转换是有它的价值的——可简化编码或者是提升代码的性能。比如在64位系统上需要对一个`[]float64`切片进行高速排序,我们可以将它强制转为`[]int`整数切片,然后以整数的方式进行排序(因为`float64`遵循IEEE754浮点数标准特性当浮点数有序时对应的整数也必然是有序的 为了安全,当两个切片类型`[]T``[]Y`的底层原始切片类型不同时Go语言是无法直接转换类型的。不过安全都是有一定代价的有时候这种转换是有它的价值的——可简化编码或者是提升代码的性能。比如在64位系统上需要对一个`[]float64`切片进行高速排序,我们可以将它强制转为`[]int`整数切片,然后以整数的方式进行排序(因为`float64`遵循IEEE754浮点数标准特性当浮点数有序时对应的整数也必然是有序的
下面的代码通过两种方法将`[]float64`类型的切片转换为`[]int`类型的切片: 下面的代码通过两种方法将`[]float64`类型的切片转换为`[]int`类型的切片:
@ -603,7 +603,7 @@ func SortFloat64FastV2(a []float64) {
} }
``` ```
第一种强制转换是先将切片数据的开始地址转换为一个较大的数组的指针,然后对数组指针对应的数组重新做切片操作。中间需要`unsafe.Pointer`接两个不同类型的指针传递。需要注意的是Go语言实现中非0大小数组的长度不得超过2GB因此需要针对数组元素的类型大小计算数组的最大长度范围`[]uint8`最大2GB`[]uint16`最大1GB以此类推但是`[]struct{}`数组的长度可以超过2GB 第一种强制转换是先将切片数据的开始地址转换为一个较大的数组的指针,然后对数组指针对应的数组重新做切片操作。中间需要`unsafe.Pointer`接两个不同类型的指针传递。需要注意的是Go语言实现中非0大小数组的长度不得超过2GB因此需要针对数组元素的类型大小计算数组的最大长度范围`[]uint8`最大2GB`[]uint16`最大1GB以此类推但是`[]struct{}`数组的长度可以超过2GB
第二种转换操作是分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应`reflect.SliceHeader`结构,然后通过更新结构体方式来更新切片信息,从而实现`a`对应的`[]float64`切片到`c`对应的`[]int`类型切片的转换。 第二种转换操作是分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应`reflect.SliceHeader`结构,然后通过更新结构体方式来更新切片信息,从而实现`a`对应的`[]float64`切片到`c`对应的`[]int`类型切片的转换。