对于golang中的字符串,我们需要关注的点是,
- 字符如何表示?
- 字符串如何编码?
- 字符串在运行时的结构?
- 字符串遍历方式的区别?
- 字符串的拼接?
string与[]byte之间如何转换?
字符如何表示?
在golang在采用的符文类型表示字符,也就是rune,比如在C++中,字符a,可能只占用一个字节,但是在golang中占用4个字节。
func TestCharacter(t *testing.T) {
c := 'a'
fmt.Println(reflect.TypeOf(c)) // int32
}
字符串如何编码?
字符串的编码的方式采用utf8编码,这是一种变长的编码方式,字节长度范围为2~4字节,像ASCII中的字符只占用一个字节;
func TestLen(t *testing.T) {
s := "这o"
// output4: 这:3个字节表示,o:一个字节表示
fmt.Println(len(s)) // 返回的是字节数,goang使用UTF-8的变长编码
// 将字符串转换为[]rune
fmt.Println(len([]rune(s)))
}
对于字符串理解有问题,可以看下面两个资料:
字符串在运行时的结构?
我们想一个问题,字符串在内存里面如何存呢?可以看下golang源码go/src/reflect/value.go
type StringHeader struct {
Data uintptr
Len int
}
这个结构包含两部分,一个部分是字符串的长度,一部分是指针域,存的是对应的字符串底层数组;
字符串遍历方式的区别?
了解完字符的表示,字符串的编码以及底层结构,那么我们如何访问字符串呢?有这么几种方式:
- 通过下标,或者截面(类似于slice),不过这里截取获得还是字符串;
- 通过
for循环获取;
func TestAccess(t *testing.T) {
str := "xiao范"
s := str[1:2]
fmt.Println(reflect.TypeOf(s)) // string类型
fmt.Println(reflect.TypeOf(str[1])) // unit8
// 这里i是单个字节递增
for i := 0; i < len(str); i++ {
fmt.Printf("编号:i:[%d], [%#U] \n", i, str[i])
}
// 这里是按照unit8编码递增
// r:是rune类型
for i, r := range str {
fmt.Printf("编号:i:[%d], [%#U] \n", i, r)
}
}
字符串的拼接?
了解完单个字符串的访问,再看看如何将多个字符串拼接在一起的几种方式?
- 通过
+来拼接字符串; - 通过
fmt.Sprintf; - 通过
strings.Join
下面代码,是这三个函数的benchmark性能对比。
func concatStringByOperator(sl []string) string {
var s string
for _, v := range sl {
s += v
}
return s
}
func concatStringBySprintf(sl []string) string {
var s string
for _, v := range sl {
s = fmt.Sprintf("%s%s", s, v)
}
return s
}
func concatStringByJoin(sl []string) string {
return strings.Join(sl, "")
}
func BenchmarkConcatStringByOperator(b *testing.B) {
for i := 0; i < b.N; i++ {
concatStringByOperator(sl)
}
}
func BenchmarkConcatStringBySprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
concatStringBySprintf(sl)
}
}
func BenchmarkStringByJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
concatStringByJoin(sl)
}
}
benchMark的性能如下:
BenchmarkConcatStringByOperator-12 15912842 72.92 ns/op
BenchmarkConcatStringBySprintf-12 2891470 420.7 ns/op
BenchmarkStringByJoin-12 26036792 46.25 ns/op
从上面的benchmark来看,Join > + > fmt.Sprintf
string与[]byte之间如何转换?
最后,我么你来看string与[]byte之间的转化,在golang中,string是不能够被修改的,意味着字符串分配到只读的内存,如果需要修改,我么那需要将string转为[]byte,修改完后,在转为string,具体流程如下:
- 将字符串的只读内存复制到堆或栈中;
- 将变量类型转为
[]byte,再修改数据; - 将修改后的
[]byte在转为string;
参考
- Go语言设计与实现
- Go语言底层原理剖析