「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
背景
由于字符串的不可变性,针对字符串,更多是尝试对其进行读取,或者将它作为一个组成单元去构建其他字符串,又或是转换为其他类型
下标操作
- 在字符串的实现中,真正存储数据的是底层的数组
- 字符串的下标操作本质上等价于底层数组的下标操作
package main
import (
"fmt"
)
func main() {
var s = "中国人"
fmt.Printf("0x%x\n", s[0]) // 0xe4:字符“中” utf-8编码的第一个字节
}
运行结果
0xe4
通过下标操作,获取的特定下标上的字节,而不是字符
字符迭代
有两种迭代形式
- 常规 for 迭代
- for range 迭代
通过这两种形式的迭代对字符串进行操作得到的结果是不同的\
常规 for 迭代
是一种字节视角的迭代,每轮迭代得到的结果都是组成字符串内容的一个字节,以及字节所在的下标值,这也等价于对字符串底层数组的迭代
package main
import (
"fmt"
)
func main() {
var s = "中国人"
for i := 0; i < len(s); i++ {
fmt.Printf("index: %d, value: 0x%x\n", i, s[i])
}
}
运行结果
index: 0, value: 0xe4
index: 1, value: 0xb8
index: 2, value: 0xad
index: 3, value: 0xe5
index: 4, value: 0x9b
index: 5, value: 0xbd
index: 6, value: 0xe4
index: 7, value: 0xba
index: 8, value: 0xba
获取到的是字符串里字符的 UTF-8 编码中的一个字节
for range 迭代
是一种字符视角
package main
import (
"fmt"
)
func main() {
var s = "中国人"
for i, v := range s {
fmt.Printf("index: %d, value: 0x%x\n", i, v)
}
}
运行结果
index: 0, value: 0x4e2d
index: 3, value: 0x56fd
index: 6, value: 0x4eba
得到的是字符串中 Unicode 字符的码点值,以及该字符在字符串中的偏移值
len(字符串)
获取的是字符串内容的字节个数
如果想获取字符串中的字符个数
应该使用标准库 UTF-8 包中的 RuneCountInString 函数
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
var s = "中国人"
fmt.Println("the char count in s is: ", utf8.RuneCountInString(s))
}
运行结果
the char count in s is: 3
3 个中文,就是 3 个字符啦
字符串连接
Go 原生支持通过 +/+= 操作符进行字符串连接
package main
import (
"fmt"
)
func main() {
s := "i am "
s = s + "poloyy "
s += "tester"
fmt.Println(s)
}
运行结果
i am poloyy tester
- +/+= 进行字符串连接的开发体验是最好的,但却不是性能最好的方式
- Go 还提供了 strings.Builder、strings.Join、fmt.Sprintf 等函数来进行字符串连接操作
字符串比较
- Go 字符串类型支持各种比较关系操作符,包括
==、!= 、>=、<=、>、< - 从字符串的起始处,开始逐个字节地对两个字符串变量进行比较
- 当两个字符串之间出现了第一个不相同的元素,比较就结束了,这两个元素的比较结果就会做为串最终的比较结果
- 如果出现两个字符串长度不同的情况,长度比较小的字符串会用空元素补齐,空元素比其他非空元素都小
package main
import (
"fmt"
)
func main() {
// ==
s1 := "世界和平"
s2 := "世界" + "和平"
fmt.Println(s1 == s2)
// !=
s1 = "Go"
s2 = "C"
fmt.Println(s1 != s2)
// > and >=
s1 = "12345"
s2 = "23456"
fmt.Println(s1 < s2)
fmt.Println(s1 <= s2)
// > and >=
s1 = "12345"
s2 = "123"
fmt.Println(s1 > s2)
fmt.Println(s1 >= s2)
}
运行结果
true
true
true
true
true
true
- 如果两个字符串长度相同,就要进一步判断,数据指针是否指向同一块底层存储数据
- 如果还相同,那么我们可以说两个字符串是等价的,如果不同,那就还需要进一步去比对实际的数据内容
字符串转换
Go 支持字符串与字节切片、字符串与 rune 切片的双向转换,并且这种转换无需调用任何函数,只需使用显式类型转换就可以了
package main
import (
"fmt"
)
func main() {
var s = "小菠萝测试笔记"
// string -> []rune
rs := []rune(s)
fmt.Printf("%x\n", rs)
// string -> []byte
bs := []byte(s)
fmt.Printf("%x\n", bs)
// []rune -> string
s1 := string(rs)
fmt.Println(s1)
// []byte -> string
s2 := string(bs)
fmt.Println(s2)
}
运行结果
[5c0f 83e0 841d 6d4b 8bd5 7b14 8bb0]
e5b08fe88fa0e8909de6b58be8af95e7ac94e8aeb0
小菠萝测试笔记
小菠萝测试笔记
- 无论是 string 转切片,还是切片转 string,这类转型背后也是有着一定开销的
- 这些开销的根源就在于 string 是不可变的,运行时要为转换后的类型分配新内存