字符串
从编码说起
- 一个字节由8个比特组成,当比特位全为0时代表数字0,全为1时代表数字255,一个字节可以表示256个数字,2个字节可以表示65536个数字。
- 而字符的表示方法与之不同,是通过将字符进行编号,比如将
A
编号为65
,对应二进制的01000001
是这个字符的编码,通过这种映射关系可以将字符以比特的形式存起来,而这种映射关系就是字符
集。 - 常见的字符集有: ASCII GB18030 GBK Unicode等等
字符集促成了字符与二进制的合作,但如何表示字符串呢?
hello世界
字符 | 编号 | 二进制 |
---|---|---|
h | 104 | 0110 1000 |
e | 101 | 0110 0101 |
l | 108 | 0110 1100 |
l | 108 | 0110 1100 |
o | 111 | 0110 0111 |
世 | 19990 | 01001110 00010110 |
界 | 30028 | 01110101 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
}