如何理解golang中的字符串呢?

102 阅读2分钟

对于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语言底层原理剖析