go专家编程系列(1)常见数据结构 string

字符串



从编码说起

  • 一个字节由8个比特组成,当比特位全为0时代表数字0,全为1时代表数字255,一个字节可以表示256个数字,2个字节可以表示65536个数字。
  • 而字符的表示方法与之不同,是通过将字符进行编号,比如将A编号为65,对应二进制的01000001是这个字符的编码,通过这种映射关系可以将字符以比特的形式存起来,而这种映射关系就是字符
    集。
  • 常见的字符集有: ASCII GB18030 GBK Unicode等等
    字符集促成了字符与二进制的合作,但如何表示字符串呢?

hello世界

字符编号二进制
h1040110 1000
e1010110 0101
l1080110 1100
l1080110 1100
o1110110 0111
1999001001110 00010110
3002801110101 01001100

如果不加处理直接存放就是01101000|01100101|01101100|01101100|01100111|0100111000010110|0111010101001100,可以发现如果将分隔符去掉我们完全不知道这些比特串将要表达什么意思。

解决方案
  • 定长编码
    无论字符的二进制表示原本有多长,全部按字符集最长的编码来比如 e:00000000 00000000 00000000 01100101
    但这样明显是在浪费内存
  • 变长编码
    小编号字符少占用字节,大编号字符多占用字节
编号编码模板
[0,127]0???????
[128,2047]110????? 10??????
[2048,65536]1110???? 10?????? 10??????

比如对于 编号19990 二进制表示01001110 00010110 变长编码 1110 0100 10111000 10010110

以上编码方式就是所谓的UTF-8编码,也就是GO语言默认的编码方式

string in golang

var str string = "hello"
在c中字符串变量存放着一块以\0结尾的连续内存的起始地址,在go中字符串变量不光存放这个地址,还存放着这块连续内存用多少个字节(源码包 src/runtime/string/string.go:stringStruct)

type stringStruct struct {
	str unsafe.Pointer
	len int
}

string的数据结构跟切片有些类似,只不过切片还有一个表示容量的成员,事实上string和切片准确来说是[]byte经常发生交换

var str string
str = "Hello 世界"

字符串生成时,会先构建stringStruct对象,在转换成string,源码:

func gostringnocopy(str *byte) string {
	ss := stringStruct{
		str: unsafe.Pointer(str),
		len: findnull(str),
		}
	s := *(*string)(unsafe.Pointer(&ss))
	return s
}
字符串拼接

字符串可以方便地拼接str := "Str1" + "Str2
即便有非常多的字符串需要拼接,性能上也有比较好的保证,因为新字符串的内存空间是一次分配完成的,所以性能消耗主要在拷贝数据上
runtime包中,使用concatstrings()函数来拼接字符串。在一个拼接语句中,所有待拼接的字符串都被编译器组织到一个切片中传入concatstrings函数,拼接的过程需要遍历两次切片 ,第一次获取长度来申请内存,第二次将字符逐个拷贝过去(以下是伪代码)

func concatstrings(a []string) string{
	length := 0
	for _,str := range a {
		length += len(str)
	}
	//分配内存 返回一个string 和 slice 它们共享内存
	s,b := rawstring(length)
	//string无法修改 可以通过切片修改
	for _,str := range a{
		copy(b,str)
		b = b[len(str):]
	}
	return s
}

rawstring() 的源码,初始化一个指定大小string同时返回一个切片二者共享同一块内存空间,后面向切片中拷贝数据,就间接地修改了string

func rawstring(size int) (s string, b []byte) {
	p := mallocgc(uintptr(size), nil, false)

	stringStructOf(&s).str = p
	stringStructOf(&s).len = size

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

	return
}
类型转换
[]byte 转 string
func slicebytetostring(buf *tmpBuf, b []byte) (str string) {
	
	//非主要代码
	
	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)
	//将切片底层数组拷贝到字符串
	memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
	return
}

string 转 []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 {
		//申请未经初始化的切片(string的内容会完全覆盖切片)
		b = rawbyteslice(len(s))
	}
	copy(b, s)
	return b
}

总结

golang的string不包含内存空间,只有一个内存的指针,这样的好处是String变得非常轻量,可以方便地进行传递而不用担心内存拷贝
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值