go1.20 的unsafe包添加了功用SliceData、String和StringData 。
它们完成了独立于完成的切片和字符串操作的函数集。
Go 的类型转化规矩已扩展为答应 从 slice 直接转化为 array。
语言标准现在界说了比较数组元素和结构字段的确切顺序。
这阐明了在比较过程中呈现恐慌时会发生什么。
func SliceData(slice []ArbitraryType) *ArbitraryType
具体怎么完成的,无法得知。 咱们能够从运用者视点看它的功能与之前的做法有何变化。
1 utf8内容
这儿说的所谓符文,也便是 rune,在前文有提到过。
RuneError==unicode.ReplacementChar和 MaxRune==unicode.MaxRune在测试中得到了验证。
在本地界说它们能够避免这个包依赖于unicode包。
1.1 编码基础。
“过错 “符文或 “Unicode替换字符”
RuneError = '\uFFFD'
RuneSelf以下的字符在一个字节中表明为它们自己。
RuneSelf = 0x80
最大的有用Unicode码位。
MaxRune = '\U0010FFFF'
一个UTF-8编码的Unicode字符的最大字节数。
UTFMax = 4
以下代号规模内的代码点对UTF-8是无效的。
surrogateMin = 0xD800
surrogateMax = 0xDFFF
默许的最低和最高延续字节。
locb = 0b10000000
hicb = 0b10111111
这些常量的名称是为了在下面的表格中供给杰出的排列,下面的表格中。
第一个小数点是acceptRanges的索引,或F用于特别的单字节状况。
特别的一个字节的状况。第二个小数点是符文长度或特别的单字节状况下的状况。
acceptRange给出了UTF-8中第二个字节的有用值规模,序列中第二个字节的有用值规模。
var acceptRanges = [16]acceptRange{
0: {locb, hicb},
1: {0xA0, hicb},
2: {locb, 0x9F},
3: {0x90, hicb},
4: {locb, 0x8F},
}
1.2 FullRune回来参数p中的字节是否以一个完好的UTF-8编码的符文开端。
一个无效的编码被以为是一个完好的符文,因为它将转化为一个宽度为1的过错符文 rune。
func FullRune(p []byte) bool {...}
实例:
buf := []byte{228, 184, 150} // 世
fmt.Println(utf8.FullRune(buf))
fmt.Println(utf8.FullRune(buf[:2]))
// Output:
// true
// false
1.3 FullRuneInString与FullRune相似,但其输入是一个字符串。
func FullRuneInString(s string) bool {...}
实例:
str := "世"
fmt.Println(utf8.FullRuneInString(str))
fmt.Println(utf8.FullRuneInString(str[:2]))
// Output:
// true
// false
1.4 DecodeRune 解压编码
解压参数p中的第一个UTF-8编码,并回来符文和它的宽度(字节)。
假如p是空的,则回来(RuneError, 0)。不然,假如编码是无效的,则回来(RuneError, 1)。
这两种状况都是不行能的正确的、非空的UTF-8的成果。
假如一个编码是不正确的UTF-8,或许编码的符文是编码但是超出了规模,或许不是最短的UTF-8编码。
那么编码是无效的。不进行其他验证。
下面的代码模拟了对x == xx的额定查看,并相应地处理ASCII和无效的状况。
这种掩码和或其他的办法能够防止呈现额定的分支。
func DecodeRune(p []byte) (r rune, size int) {...}
实例:
b := []byte("Hello, 国际")
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
fmt.Printf("%c %v\n", r, size)
b = b[size:]
}
// Output:
// H 1
// e 1
// l 1
// l 1
// o 1
// , 1
// 1
// 世 3
// 界 3
1.5 DecodeRuneInString与DecodeRune相似,但其输入是一个字符串。
假如s是是空的,它会回来(RuneError, 0)。不然,假如编码是无效的,它会回来(RuneError, 1)。
关于正确的、非空的UTF-8来说,这两种成果都是不行能的。
假如一个编码是不正确的UTF-8,编码的符文是编码但是超出了规模,或许不是最短的UTF-8编码。
那么传入的编码是无效的。不进行其他验证。
func DecodeRuneInString(s string) (r rune, size int) {...}
str := "Hello, 国际"
for len(str) > 0 {
r, size := utf8.DecodeRuneInString(str)
fmt.Printf("%c %v\n", r, size)
str = str[size:]
}
// Output:
// H 1
// e 1
// l 1
// l 1
// o 1
// , 1
// 1
// 世 3
// 界 3
1.6 DecodeLastRune解压参数p中的最后一个UTF-8编码并回来符文和其宽度(字节)。
假如参数p是空的,则回来(RuneError, 0)。
不然,假如参数p的编码是无效的,则回来(RuneError, 1)。
这两种状况都是不行能的,正确的、非空的UTF-8的成果。
假如一个编码是不正确的UTF-8,编码的符文是编码,但是超出了规模,或许不是最短的UTF-8编码。
那么编码是无效的。不进行其他验证。
func DecodeLastRune(p []byte) (r rune, size int) {...}
运用实例:
b := []byte("Hello, 国际")
for len(b) > 0 {
r, size := utf8.DecodeLastRune(b)
fmt.Printf("%c %v\n", r, size)
b = b[:len(b)-size]
}
// Output:
// 界 3
// 世 3
// 1
// , 1
// o 1
// l 1
// l 1
// e 1
// H 1
1.7 DecodeLastRuneInString与DecodeLastRune相似,但其输入是一个字符串。
假如s是空的,则回来(RuneError, 0)。
不然,假如编码是无效的,它回来(RuneError, 1)。
关于正确的非空的UTF-8来说,这两种成果都是不行能的。
假如一个编码是不正确的UTF-8,编码的符文是编码但是超出了规模,或许不是最短的UTF-8编码。
那么编码是无效的。不进行其他验证。
func DecodeLastRuneInString(s string) (r rune, size int) {...}
实例:
str := "Hello, 国际"
for len(str) > 0 {
r, size := utf8.DecodeLastRuneInString(str)
fmt.Printf("%c %v\n", r, size)
str = str[:len(str)-size]
}
// Output:
// 界 3
// 世 3
// 1
// , 1
// o 1
// l 1
// l 1
// e 1
// H 1
1.8 RuneLen回来对符文进行编码所需的字节数。
假如符文不是一个能够用UTF-8编码的有用值,则回来-1。
func RuneLen(r rune) int {...}
实例:
fmt.Println(utf8.RuneLen('a'))
fmt.Println(utf8.RuneLen('界'))
// Output:
// 1
// 3
1.9 EncodeRune将符文的UTF-8编码写进参数p(有必要足够大
假如符文超出了规模,它就写出RuneError的编码,它回来写入的字节数。
func EncodeRune(p []byte, r rune) int {...}
实例: 正常运用
r := '世'
buf := make([]byte, 3)
n := utf8.EncodeRune(buf, r)
fmt.Println(buf)
fmt.Println(n)
// Output:
// [228 184 150]
// 3
实例:索引越界
runes := []rune{
// Less than 0, out of range.
-1,
// Greater than 0x10FFFF, out of range.
0x110000,
// The Unicode replacement character.
utf8.RuneError,
}
for i, c := range runes {
buf := make([]byte, 3)
size := utf8.EncodeRune(buf, c)
fmt.Printf("%d: %d %[2]s %d\n", i, buf, size)
}
// Output:
// 0: [239 191 189] � 3
// 1: [239 191 189] � 3
// 2: [239 191 189] � 3
1.10 AppendRune将r的UTF-8编码附加到p的结尾,而且回来扩展的缓冲区。
假如符文超出了规模。
它将附加RuneError的编码。
func AppendRune(p []byte, r rune) []byte {...}
实例:
buf1 := utf8.AppendRune(nil, 0x10000)
buf2 := utf8.AppendRune([]byte("init"), 0x10000)
fmt.Println(string(buf1))
fmt.Println(string(buf2))
// Output:
//
// init
1.11 RuneCount回来p中符文的数量。
过错的和短小的编码被视为宽度为1字节的单一符文。
func RuneCount(p []byte) int {...}
实例:
buf := []byte("Hello, 国际")
fmt.Println("bytes =", len(buf))
fmt.Println("runes =", utf8.RuneCount(buf))
// Output:
// bytes = 13
// runes = 9
1.12 RuneCountInString与RuneCount相似
其输入是一个字符串, 回来符文数量。
func RuneCountInString(s string) (n int) {...}
实例:
str := "Hello, 国际"
fmt.Println("bytes =", len(str))
fmt.Println("runes =", utf8.RuneCountInString(str))
// Output:
// bytes = 13
// runes = 9
1.13 RuneStart 陈述该字节是否为编码后的第一个字节,可能是无效的符文。
第二个和随后的字节总是将前两个位设置为10。
func RuneStart(b byte) bool { return b&0xC0 != 0x80 }
实例:
buf := []byte("a界")
fmt.Println(utf8.RuneStart(buf[0]))
fmt.Println(utf8.RuneStart(buf[1]))
fmt.Println(utf8.RuneStart(buf[2]))
// Output:
// true
// true
// false
1.14 Valid 陈述参数p是否完全由有用的UTF-8编码的符文组成。
这种优化避免了在生成p[8:]的代码时从头计算容量的需求。
在生成p[8:]的代码时需求从头计算容量,使其与ValidString相媲美。
ValidString,后者在长ASCII字符串上快20%。
将两个32位的负载结合起来,能够使相同的代码用于32位和64位渠道。
编译器可以为first32和second32生成一个32位的负载,在许多渠道上。拜见test/codegen/memcombine.go。
func Valid(p []byte) bool {...}
实例:
valid := []byte("Hello, 国际")
invalid := []byte{0xff, 0xfe, 0xfd}
fmt.Println(utf8.Valid(valid))
fmt.Println(utf8.Valid(invalid))
// Output:
// true
// false
1.15 ValidString陈述s是否完全由有用的UTF-8编码的符文组成。
每次迭代查看并越过8个字节的ASCII字符。
将两个32位的负载结合起来,能够使相同的代码用于用于32位和64位渠道。
编译器可以为first32和second32生成一个32位的负载。
在许多渠道上。拜见test/codegen/memcombine.go。
func ValidString(s string) bool {...}
实例:
valid := "Hello, 国际"
invalid := string([]byte{0xff, 0xfe, 0xfd})
fmt.Println(utf8.ValidString(valid))
fmt.Println(utf8.ValidString(invalid))
// Output:
// true
// false
1.16 ValidRune陈述r是否能够被合法地编码为UTF-8。
超出规模的代码点或代用对折是不合法的。
func ValidRune(r rune) bool {...}
实例:
valid := 'a'
invalid := rune(0xfffffff)
fmt.Println(utf8.ValidRune(valid))
fmt.Println(utf8.ValidRune(invalid))
// Output:
// true
// false
2 再谈类型和新版转化功率
2.1、类型界说
三者都是Go中的内置类型,在 builtin 包中有类型界说
byte是uint8的一个别号,在一切方面都等同于uint8。按照惯例,它被用来差异字节值和8位无符号整数值。
type byte = uint8
rune是int32的一个别号,在一切方面都等同于int32。它在习惯上用于差异字符值和整数值。
type rune = int32
字符串是一切8位字节的字符串的调集,习惯上但不有必要代表UTF-8编码的文本。
一个字符串可所以空的,但不能为零。
字符串类型的值是不行改变的。
type string string
从官方概念来看,string表明的是byte的调集,即八位的一个字节的调集,通常状况下运用UTF-8的编码方法,但不肯定。
而rune表明用四个字节组成的一个字符,rune值为字符的Unicode编码。
2.2、类型转化功能操作再次实践
- 1、类型转化功能优化
Go底层对[]byte和string的转化都需求进行内存复制,因此在部分需求频频转化的场景下,很多的内存复制会导致功能下降。
type stringStruct struct {
str unsafe.Pointer
len int
}
type slice struct {
array unsafe.Pointer
len int
cap int
}
本质上底层数据存储都是依据uintptr,可见string与[]byte的差异在于[]byte额定有一个cap去指定slice的容量。
所以string能够看作[2]uintptr,[]byte看作[3]uintptr,类型转化只需求转化成对应的uintptr数组即可,不需求进行底层数据的频频复制。
以下是依据此思想供给的一个解决方案,用于string与[]byte的高功能转化方案 (fasthttp)。
b2s将字节片转化为字符串,无需分配内存。
请留意,假如字符串和/或片头发生变化,在未来的Go版别中,它可能会中断。
https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
旧版
func byte2str(b []byte) string {
/* #nosec G103 */
return *(*string)(unsafe.Pointer(&b))
}
1.20 新版
func byte2str(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
s2b将字符串转化为字节片,无需分配内存。
留意,假如字符串和/或片头在未来的go版别中发生变化,在未来的go版别中可能会中断,它可能会失效。
旧版:
func str2byte(s string) (b []byte) {
/* #nosec G103 */
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
/* #nosec G103 */
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}
1.20 新版:
func str2byte(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
SliceData 回来一个指向参数的底层数组的指针slice。
// - 假如cap(slice) > 0, SliceData回来&slice[:1][0]。
// - 假如slice == nil, SliceData回来nil。
// - 不然,SliceData回来一个非空的指针,指向一个 未指定的内存地址。
- 功能对比:
旧版字符串转字节切片
BenchmarkString2Bytes
BenchmarkString2Bytes-2 1000000000 0.3060 ns/op
BenchmarkString2Bytes-2 1000000000 0.3096 ns/op
BenchmarkString2Bytes-4 1000000000 0.3011 ns/op
BenchmarkString2Bytes-4 1000000000 0.3093 ns/op
BenchmarkString2Bytes-8 1000000000 0.3106 ns/op
BenchmarkString2Bytes-8 1000000000 0.3050 ns/op
BenchmarkString2Bytes-32 1000000000 0.3191 ns/op
BenchmarkString2Bytes-32 1000000000 0.3100 ns/op
新版1.20的字符串转字节切片
BenchmarkS2B
BenchmarkS2B-2 1000000000 0.6182 ns/op
BenchmarkS2B-2 1000000000 0.6181 ns/op
BenchmarkS2B-4 1000000000 0.6372 ns/op
BenchmarkS2B-4 1000000000 0.6450 ns/op
BenchmarkS2B-8 1000000000 0.6360 ns/op
BenchmarkS2B-8 1000000000 0.6449 ns/op
BenchmarkS2B-32 1000000000 0.6071 ns/op
BenchmarkS2B-32 1000000000 0.6171 ns/op
旧版字节切片转字符串
BenchmarkBytes2String
BenchmarkBytes2String-2 1000000000 0.6216 ns/op
BenchmarkBytes2String-2 1000000000 0.6353 ns/op
BenchmarkBytes2String-4 1000000000 0.6252 ns/op
BenchmarkBytes2String-4 1000000000 0.6620 ns/op
BenchmarkBytes2String-8 1000000000 0.5936 ns/op
BenchmarkBytes2String-8 1000000000 0.6246 ns/op
BenchmarkBytes2String-32 1000000000 0.6032 ns/op
BenchmarkBytes2String-32 1000000000 0.5866 ns/op
新版1.20字节切片转字符串
BenchmarkB2S
BenchmarkB2S-2 1000000000 0.6531 ns/op
BenchmarkB2S-2 1000000000 0.6351 ns/op
BenchmarkB2S-4 1000000000 0.6146 ns/op
BenchmarkB2S-4 1000000000 0.6276 ns/op
BenchmarkB2S-8 1000000000 0.6346 ns/op
BenchmarkB2S-8 1000000000 0.6551 ns/op
BenchmarkB2S-32 1000000000 0.6266 ns/op
BenchmarkB2S-32 1000000000 0.6116 ns/op
-
能够看到从字节切片[]byte转字符串string,新版和旧版功能适当。
-
从字符串string转字节切片[]byte,旧版功能正好两倍于新版功能。
因为[]byte转化到string时直接抛弃cap即可,因此能够直接经过unsafe.Pointer进行操作。
string转化到[]byte时,需求进行指针的复制,并将Cap设置为Len。此处是该方案的一个细节点,因为string是定长的,转化后data后续的数据是否可写是不确定的。
假如Cap大于Len,在进行append的时候不会触发slice的扩容,而且因为后续内存不行写,就会在运行时导致panic。
- 2、UCA不一致
UCA界说在 unicode/tables.go 中,头部即界说了运用的UCA版别。
版别是Unicode的版别,表格是从该版别衍生出来的。
const Version = "13.0.0"
经过追溯,go 1 起的tables.go即运用了6.0.0的版别,方位与现在稍有不同。
依据MySQL官方文档关于UCA的相关内容
MySQL运用不同编码,UCA的版别并不相同,因此很大概率会存在底层数据库运用的UCA与事务层运用的UCA不一致的状况。
在一些大小写不灵敏的场景下,可能会呈现字符的识别问题。
如事务层以为两个字符为一对大小写字符,而因为MySQL运用的UCA版别较低,导致MySQL经过小写进行不灵敏查询无法查询到大写的数据。
因为常用字符集根本不会发生变化,所以关于普通事务,UCA的不一致根本不会形成影响.
小结
字符相关的其他内容。
-
- 关于ASCII(不包括扩展128+)字符,UTF-8编码、Unicode编码、ASCII码均相同(即单字节以0最初)
-
- 关于非ASCII(不包括扩展128+)字符,若字符有n个字节(编码后)。则首字节的最初为n个1和1个0,其余字节均以10最初。
除去这些最初固定位,其余位组合表明Unicode字符。
-
- UCA(Unicode Collation Algorithm)
UCA是Unicode字符的校正算法,现在最新版别15.0.0(2022-05-03 12:36)。
以14.0.0为准,数据文件主要包括两个部分, 即 allkeys 和 decomps,表明字符集的排序、大小写、分解联系等,详细信息可阅览Unicode官方文档。
不同版别之间的UCA是存在差异的,如两个字符,在14.0.0中界说了大小写联系,但在5.0.0中是不具备大小写联系的。
在仅支撑5.0.0的运用中,14.0.0 增加的字符是可能以硬编码的方法存在的,具体状况要看完成细节。
因此关于跨渠道,多语言的事务,各个服务运用的UCA很可能不是同一个版别。
因此关于部分字符,其排序规矩、大小写转化的不同,有可能会发生不一致的问题。