详解GO语言中[]byte与string的两种转换方式和底层实现

 更新时间:2024年03月25日 11:24:29   作者:小许code  
这篇文章主要为大家详细介绍了GO语言中[]byte与string的两种转换方式和底层实现的相关知识,文中的示例代码讲解详细,有需要的小伙伴可以参考下

脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用

看过小许之前的文章《fasthttp是如何做到比net/http快十倍的》,相信你们还对极致的优化方式意犹未尽。

不过你发现没fasthttp关于string和[]byte的转换方式和大家平常普遍使用的方式不一样,fasthttp转换实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//[]byte转string
func b2s(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}
  
//string转[]byte
func s2b(s string) (b []byte) {
    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh.Data = sh.Data
    bh.Cap = sh.Len
    bh.Len = sh.Len
    return b
}

为什么不用我们常见的string和[]byte的转换方式呢?这样做是怎么提高性能的呢?...

带着这些疑问,今天将分享下并总结string和[]byte的转换方式,不同的转换方式之间的实现和区别!

两种转换方式

如果此时此刻你刚好遇到面试官问你string和[]byte如何进行转换,有几种方式?你能答上来吗

反正在写这篇文章之前小许估计是答不出来的,哈哈!

毕竟知道的越多,不知道的也越多嘛

那今天我们就来聊聊,继续往下读之前,我们先了解下这两种数据类型:

string和[]byte

上图中可以看出 stringStruct和slice还是有一些相似之处,str和array指针指向底层数组的地址,len代表的就是数组长度。

关于string类型,在go标准库中官方说明如下:

1
2
3
4
5
// 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是8位字节的集合,string的定义在上图中左侧,通常但不一定代表UTF-8编码的文本。string可以为空,但是不能为nil,并且string的值是不能改变的。

为什么string类型没有cap字段

string的不可变性,也就不能直接向底层数组追加元素,所以不需要Cap。

而[]byte就是一个byte类型的切片,切片本质也是一个结构体。

这里我们先记住下这两种数据类型的特点,对后面的了解两者的转换有帮助!

标准方式

Golang中string与[]byte的互换,这是我们常用的,也是立马能想到的转换方式,这种方式称为标准方式。

1
2
3
4
5
6
// string 转 []byte
s1 := "xiaoxu"
b := []byte(s1)
 
// []byte 转 string
s2 := string(b)

那还有其他方式吗?当然有的,那就是强转换

强转换方式

强转换方式是通过unsafe和reflect包来实现的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//[]byte转string
func b2s(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}
  
//string转[]byte
func s2b(s string) (b []byte) {
    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh.Data = sh.Data
    bh.Cap = sh.Len
    bh.Len = sh.Len
    return b
}

可以看出利用reflect.SliceHeader(代表一个运行时的切片) 和 unsafe.Pointer进行指针替换。

为什么可以这么做呢?

前面我们在讲string和[]byte类型的时候就提了,因为两者的底层结构的字段相似!

array和str的len是一致的,而唯一不同的就是cap字段,所以他们的内存布局上是对齐的。

分析

我们看下这两种转换方式底层是如何实现的,这些实现代码在标准库中都是有的,下面底层实现的代码来自Go 1.18.6版本。

标准方式底层实现

string转[]byte底层实现

先看string转[]byte的实现,(实现源码在 src/runtime/string.go 中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const tmpStringBufSize = 32
 
//长度32的数组
type tmpBuf [tmpStringBufSize]byte
 
//时间函数
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
    var b []byte
    //判断字符串长度是否小于等于32
    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.
//rawbyteslice函数 分配一个新的字节片。字节片未归零
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转[]byte是,会根据字符串长度来决定是否需要重新分配一块内存。

• 预先定义了一个长度为32的数组

• 若字符串的长度不超过这个长度32的数组,copy函数实现string到[]byte的拷贝

• 若字符串的长度超过了这个长度32的数组,重新分配一块内存了,再进行copy

[]byte转string底层实现

再看[]byte转string的实现,(实现源码在 src/runtime/string.go 中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const tmpStringBufSize = 32
 
//长度32的数组
type tmpBuf [tmpStringBufSize]byte
 
//实现函数
func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
    ...
    if n == 1 {
        p := unsafe.Pointer(&staticuint64s[*ptr])
        if goarch.BigEndian {
            p = add(p, 7)
        }
        stringStructOf(&str).str = p
        stringStructOf(&str).len = 1
        return
    }
 
    var p unsafe.Pointer
    //判断字符串长度是否小于等于32
    if buf != nil && n <= len(buf) {
        p = unsafe.Pointer(buf)
    } else {
        p = mallocgc(uintptr(n), nil, false)
    }
    stringStructOf(&str).str = p
    stringStructOf(&str).len = n
    //拷贝byte数组至字符串
    memmove(p, unsafe.Pointer(ptr), uintptr(n))
    return
}

跟string转[]byte一样,当数组长度超过32时,同样需要调用mallocgc分配一块新内存

强转换底层实现

从标准的转换方式中,我们知道如果字符串长度超过32的话,会重新分配一块新内存,进行内存拷贝。

1
2
3
4
5
6
7
8
9
//string转[]byte
func s2b(s string) (b []byte) {
    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh.Data = sh.Data
    bh.Cap = sh.Len
    bh.Len = sh.Len
    return b
}

强转换过程中,通过 神奇的unsafe.Pointer指针

• 任何类型的指针 *T 都可以转换为unsafe.Pointer类型的指针,可以存储任何变量的地址

• unsafe.Pointer 类型的指针也可以转换回普通指针,并且可以和类型*T不相同

refletc包的 reflect.SliceHeader 和 reflect.StringHeader分别代表什么意思?

reflect.SliceHeader:slice类型的运行时表示形式

reflect.StringHeader:string类型的运行时表示形式

1
2
3
4
5
6
7
8
9
10
11
12
//slice在运行时的描述符
type SliceHeader struct {     
     Data uintptr
     Len  int
    Cap  int
}
 
//string在运行时的描述符
type StringHeader struct {
    Data uintptr
    Len  int
}

*(reflect.SliceHeader)(unsafe.Pointer(&b)) 的目的就是通过unsafe.Pointer 把它们转换为 *reflect.SliceHeader 指针。

而运行时表现形式 SliceHeader 和 StringHeader,而这两个结构体都有一个 Data 字段,用于存放指向真实内容的指针。

[]byte 和 string之间的转换,就可以理解为是通过 unsafe.Pointer 把 *SliceHeader 转为 *StringHeader,也就是 *[]byte 和 *string之间的转换。

那么我们就可以理解相对于标准转换方式,强转换方式的优点在哪了!

直接替换指针的指向,避免了申请新内存(零拷贝),因为两者指向的底层字段Data地址相同

总结

今天和大家一起了解了[]byte和string类型,以及[]byte和string的两种转换方式。

不过Go语言提供给我们使用的还是标准转换方式,主要是因为在你不确定安全隐患的情况下,使用强转化方式可能不必要的问题。

不过像fasthttp那样,对程序对运行性能有高要求,那就可以考虑使用强转换方式!

到此这篇关于详解GO语言中[]byte与string的两种转换方式和底层实现的文章就介绍到这了,更多相关GO byte与string转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://juejin.cn/post/7349752708386258998

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • GO将mysql 中 decimal 数据类型映射到 protobuf的操作方法

    GO将mysql 中 decimal 数据类型映射到 protobuf的操作方法

    这篇文章主要介绍了go如何优雅地将 mysql 中 decimal 数据类型映射到 protobuf,本文主要展示一下在 protobuf中 float与double的一个区别,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • 初探GO中unsafe包的使用

    初探GO中unsafe包的使用

    unsafe是Go语言标准库中的一个包,提供了一些不安全的编程操作,本文将深入探讨Go语言中的unsafe包,介绍它的使用方法和注意事项,感兴趣的可以了解下
    2023-08-08
  • 用Go+WebSocket快速实现一个chat服务

    用Go+WebSocket快速实现一个chat服务

    这篇文章主要介绍了用Go+WebSocket快速实现一个chat服务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 使用Singleflight实现Golang代码优化

    使用Singleflight实现Golang代码优化

    有许多方法可以优化代码以提高效率,减少运行进程就是其中之一,本文我们就来学习一下如何通过使用一个Go包Singleflight来减少重复进程,从而优化Go代码吧
    2023-09-09
  • Golang 流水线设计模式实践示例详解

    Golang 流水线设计模式实践示例详解

    这篇文章主要为大家介绍了Golang 流水线设计模式实践示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Golang在图像中绘制矩形框的示例代码

    Golang在图像中绘制矩形框的示例代码

    这篇文章主要介绍了Golang在图像中绘制矩形框的示例代码,文中有详细的代码示例供大家参考,具有一定的参考价值,需要的朋友可以参考下
    2008-08-08
  • Go项目怎么使用枚举

    Go项目怎么使用枚举

    枚举是一种很重要的数据类型,本文主要介绍了Go项目怎么使用枚举,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Go+Kafka实现延迟消息的实现示例

    Go+Kafka实现延迟消息的实现示例

    本文主要介绍了Go+Kafka实现延迟消息的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • go 对象池化组件 bytebufferpool使用详解

    go 对象池化组件 bytebufferpool使用详解

    这篇文章主要为大家介绍了go 对象池化组件 bytebufferpool使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • golang包快速生成base64验证码的方法

    golang包快速生成base64验证码的方法

    这篇文章主要介绍了golang包快速生成base64验证码的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论