golang string 和 []byte的关系

首先,string内部就是一个byte数组
结构如下

type stringStruct struct {
    str unsafe.Pointer
    len int
}

可以看到str其实是个指针,指向某个数组的首地址,另一个字段是len长度。那到这个数组是什么呢? 在实例化这个stringStruct的时候:

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

[]byte 是个slice数据,byte是uint8,而slice结构在go的源码中src/runtime/slice.go定义:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

看着和上面的string结构很像,但其实差别很大

差别在哪?

首先,字符串的值可以被替换但不能被更改。 以string的结构体来解释,所有的string在底层都是这样的一个结构体

stringStruct{
    str: str_point, 
    len: str_len
}

str指针指向的是一个字符常量的地址, 这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址。
如下:123 是字符常量,而常量是不允许修改的,str 指向 字符常量的内存地址。456也是字符常量,456赋给str时,是重新指向了 456的内存地址。

str := "123"
str = "456"

byte数据则不同,[]byte{1}是slice,其内容是可以被更改的。

str := []byte{1}
str = []byte{2}

这就是string 和 []byte 的区别。

那二者进行转换时,会产生额外的内存空间占用吗?
我们看下转换的底层实现

将string转为[]byte,语法[]byte(string)源码如下:

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
}

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
}

这里新申请了内存空间 var b []byte 。是逻辑是先判断buf是否为空,以及长度是否够用,不够用通过rawstring 扩容
然后通过copy将 s 复制给了 b
这里就有个新的内存空间使用。

将[]byte转为string,语法string([]byte)源码如下:

func slicebytetostring(buf *tmpBuf, b []byte) 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 && l > 0 {
        racereadrangepc(unsafe.Pointer(&b[0]),
            uintptr(l),
            getcallerpc(unsafe.Pointer(&buf)),
            funcPC(slicebytetostring))
    }
    if msanenabled && l > 0 {
        msanread(unsafe.Pointer(&b[0]), uintptr(l))
    }
    s, c := rawstringtmp(buf, l)
    copy(c, b)
    return s
}

func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {
    if buf != nil && l <= len(buf) {
        b = buf[:l]
        s = slicebytetostringtmp(b)
    } else {
        s, b = rawstring(l)
    }
    return
}

依然可以看到s是新分配的,然后再将b复制给s。
正因为string和[]byte相互转换都会有新的内存分配,才导致其代价不小,但读者千万不要误会,对于现在的机器来说这些代价其实不值一提。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • string类型和[]byte类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间...
    机器铃砍菜刀s阅读 4,007评论 0 0
  • slice 切片的原理 切⽚ ( slice ) 是Go中⼀种⽐较特殊的数据结构,这种数据结构更便于使⽤和管理数据...
    强某某阅读 1,114评论 0 1
  • 1.安装 https://studygolang.com/dl 2.使用vscode编辑器安装go插件 3.go语...
    go含羞草阅读 1,571评论 0 6
  • 主函数:程序有且只有一个主函数package main //导入主函数的包func main() {} 打印的模...
    hellomyshadow阅读 180评论 0 0
  • 在讲解String之前,我们先了解一下Java的内存结构。 一、Java内存模型 按照官方的说法:Java 虚拟机...
    哦00阅读 257评论 0 0