深入理解golang string

golang string

string的定义
// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

string里存储的是字符按照utf8编码后的“8-bit bytes”二进制数据 。也就是byte类型

string的长度:

​ utf8表示一个中文需要2个字节以上的空间,表示一个英文字符需要1个字节的空间,所以当使用len()返回字符串的长度时,实际上返回的是字节的数量。

​ 关于字符串的长度参考全面认识golang string

strings库常用函数
// 转换大小写
strings.ToUpper()
strings.ToLower()

// 字符串清理
strings.Trim()
strings.TrimPrefix()
strings.TrimSuffix()

// 字符串拆合
strings.Split()
strings.Fields()
strings.Join()

// 子串
strings.HasPrefix()
strings.Contains()
strings.Index()
strings.Count()

// Reader
strings.NewReader()
数据结构

【stringStruct】

type stringStruct struct {
	str unsafe.Pointer
	len int
}
  • string的底层实际上是一个stringStruct结构体,结构体的定义位于src/runtime/string.go文件中
  • str表示字符串的首地址
  • len表示字符串的长度

####字符串类型是只读的,不可修改

字符串一旦创建就不能修改,意味着可以通过下标来获取字符串的byte,但是不能通过下标来进行赋值。但是可以用过转换成[]byte再转换成string来实现修改。

字符串拼接

字符串拼接由多种方式,分别是:

  • 使用操作符 +/+= ,特点是易用
  • fmt.Sprintf,性能最差,但可以格式化
  • bytes.Buffer,跟strings.Join性能差不多
  • strings.Join
  • strings.Builder,性能最高

【runtime.concatstrings】

使用+操作符拼接字符串,会调用runtime.concatstrings方法,

func concatstrings(buf *tmpBuf, a []string) string {
   idx := 0
   l := 0
   count := 0
   for i, x := range a {
      n := len(x)
      if n == 0 {
         continue
      }
      if l+n < l {
         throw("string concatenation too long")
      }
      l += n
      count++
      idx = i
   }
   if count == 0 {
      return ""
   }

   // If there is just one string and either it is not on the stack
   // or our result does not escape the calling frame (buf != nil),
   // then we can return that string directly.
   if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
      return a[idx]
   }
   s, b := rawstringtmp(buf, l) 	//返回string和[]byte,他们的数据地址都指向同一个新申请的地址
   for _, x := range a {	//遍历要拼接的字符串,将其拷贝到内存空间
      copy(b, x)
      b = b[len(x):]
   }
   return s
}

concatstrings方法在运行时,会调用copy将输入的多个字符串拷贝到目标字符串所在的内存空间。如果需要拼接的字符串非常大,需要注意拷贝所带来的性能损失。

字符串与字节切片相互转换

【runtime.slicebytetostring】将字节切片转换成字符串

// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, b []byte) (str string) {
	l := len(b)
	if l == 0 {
		// Turns out to be a relatively common case.
		// Consider that you want to parse out data between parens in "foo()bar",
		// you find the indices and convert the subslice to string.
		return ""
	}
	if raceenabled {
		racereadrangepc(unsafe.Pointer(&b[0]),
			uintptr(l),
			getcallerpc(),
			funcPC(slicebytetostring))
	}
	if msanenabled {
		msanread(unsafe.Pointer(&b[0]), uintptr(l))
	}
	if l == 1 {
		stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])
		stringStructOf(&str).len = 1
		return
	}

	// 为str申请一个内存空间用来存储byte数组
	var p unsafe.Pointer
	if buf != nil && len(b) <= len(buf) {
		p = unsafe.Pointer(buf)
	} else {
		p = mallocgc(uintptr(len(b)), nil, false)
	}
	stringStructOf(&str).str = p
	stringStructOf(&str).len = len(b)
	
	// 将切片的底层数组复制到str的内存空间
	memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
	return
}

【runtime.stringtoslicebyte】将字符串转换成byte切片

func stringtoslicebyte(buf *tmpBuf, s string) []byte {
   var b []byte
   if buf != nil && len(s) <= len(buf) {
      *buf = tmpBuf{}
      b = buf[:len(s)]
   } else {
      b = rawbyteslice(len(s))
   }
   copy(b, s)
   return b
}

// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
func rawbyteslice(size int) (b []byte) {
	cap := roundupsize(uintptr(size))
	p := mallocgc(cap, nil, false)
	if cap != uintptr(size) {
		memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
	}

	*(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
	return
}

分配一个新的切片空间,将string拷贝给新切片

高效使用字符串
  • 字符串拼接使用strings.Builder
  • 字符串与字节切片相互转换,使用unsafe.Pointer转换成字节切片实现零拷贝
总结

字符串在go里面是一片连续的内存空间,是由一个个字符组成是一个集合。

string类型的值是只读的,不可以被修改。

reference
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值