[go]go中的字符串详解

这几天发现go的字符串还是有点意思的,go的这几个基本数据结构的底层看来还是要好好地学习一波

:ku:

零拷贝?但是得拷贝

这个要从string的底层说起,日常我们在使用string的时候,一旦涉及对内容修改,无一例外地就会想到想转成[]byte类型


// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
    Data uintptr
    Len  int
}

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

其中,第一个是字符串的底层结构,本质就是一个结构体,结构体中的成员是一个指向底层[]byte的指针以及长度,通过这个两个元素即可完成一个字符串的定位

第二个是所有切片类型的底层结构,多了一个底层数组的容量

从结构和功能上看,其实这两个底层十分相似,实际上,确实可以通过unsafe包对指针的操作来完成直接[]byte到string的“去封装和重封装”


func main() {

    a := "aaa"
    // unsafe.Pointer(&a) : 获得变量的地址
    // (*reflect.StringHeader)(unsafe.Pointer(&a)): 把字符串a转成底层结构的形式
    ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
    // 把ssh底层结构体转成byte的切片的指针
    // 通过*指向实际的内容
    b := *(*[]byte)(unsafe.Pointer(&ssh))


    fmt.Printf("%v", b)

    fmt.Println("---------------------------------------------------")

}

是不是很便利?

然而,别高兴得太早,一旦你试图改动得到的字节切片,会直接panic

显然,这就要说到go语言永远都是值传递的事了

由底层结构可见,复制一个字符串本质上复制的是结构体,即指针值和长度

所以无论复制几份,得到结果都一样,其保障是底层的那个字节数组永远不会变动

假如一旦可以改动,安全性就无法保障,所有字符串由于引用的同一个底层数组,一瞬间所有复制出来的字符串都会跟着变 :jingku:

其实也和mutex不能复制有异曲同工之妙,复制会同时复制过去状态,从而导致锁复用出现问题

:huaji22:

那话说回来,这个零拷贝既然不能修改,那有个卵用啊!

其实还是有场景的

即便不能修改,至少还能转回去嘛还可以截取

go也有字符常量池?

这个其实发现的十分偶然,参考Java中对字符串的实现

我发现在go中,如果你前后赋值两个完全(运行前而非运行时通过strconv.ItoA(123)这种)一样的字符串,其底层指向的其实同一个字节数组,即便是不同函数也一样!

当然,也有可能是编译器的优化

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注