偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

[]byte與string的兩種轉(zhuǎn)換方式和底層實現(xiàn)

開發(fā) 前端
Go語言提供給我們使用的還是標準轉(zhuǎn)換方式,主要是因為在你不確定安全隱患的情況下,使用強轉(zhuǎn)化方式可能不必要的問題。

不過你發(fā)現(xiàn)沒fasthttp關(guān)于string和[]byte的轉(zhuǎn)換方式和大家平常普遍使用的方式不一樣,fasthttp轉(zhuǎn)換實現(xiàn)如下:

//[]byte轉(zhuǎn)string
func b2s(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}
 
//string轉(zhuǎn)[]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的轉(zhuǎn)換方式呢?這樣做是怎么提高性能的呢?...

帶著這些疑問,今天將分享下并總結(jié)string和[]byte的轉(zhuǎn)換方式,不同的轉(zhuǎn)換方式之間的實現(xiàn)和區(qū)別!


圖片圖片

兩種轉(zhuǎn)換方式

如果此時此刻你剛好遇到面試官問你string和[]byte如何進行轉(zhuǎn)換,有幾種方式?你能答上來嗎

反正在寫這篇文章之前小許估計是答不出來的,哈哈!

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

那今天我們就來聊聊,繼續(xù)往下讀之前,我們先了解下這兩種數(shù)據(jù)類型:

string和[]byte

圖片圖片

??上圖中可以看出 stringStruct和slice還是有一些相似之處,str和array指針指向底層數(shù)組的地址,len代表的就是數(shù)組長度。

關(guān)于string類型,在go標準庫中官方說明如下:

// 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位字節(jié)的集合,string的定義在上圖中左側(cè),通常但不一定代表UTF-8編碼的文本。string可以為空,但是不能為nil,并且string的值是不能改變的。

??為什么string類型沒有cap字段

string的不可變性,也就不能直接向底層數(shù)組追加元素,所以不需要Cap。

而[]byte就是一個byte類型的切片,切片本質(zhì)也是一個結(jié)構(gòu)體。

?? 這里我們先記住下這兩種數(shù)據(jù)類型的特點,對后面的了解兩者的轉(zhuǎn)換有幫助!

標準方式

Golang中string與[]byte的互換,這是我們常用的,也是立馬能想到的轉(zhuǎn)換方式,這種方式稱為標準方式。

// string 轉(zhuǎn) []byte
s1 := "xiaoxu"
b := []byte(s1)

// []byte 轉(zhuǎn) string
s2 := string(b)

那還有其他方式嗎?當(dāng)然有的,那就是強轉(zhuǎn)換

強轉(zhuǎn)換方式

強轉(zhuǎn)換方式是通過unsafe和reflect包來實現(xiàn)的,代碼如下:

//[]byte轉(zhuǎn)string
func b2s(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}
 
//string轉(zhuǎn)[]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類型的時候就提了,因為兩者的底層結(jié)構(gòu)的字段相似!

array和str的len是一致的,而唯一不同的就是cap字段,所以他們的內(nèi)存布局上是對齊的。

分析

我們看下這兩種轉(zhuǎn)換方式底層是如何實現(xiàn)的,這些實現(xiàn)代碼在標準庫中都是有的,下面底層實現(xiàn)的代碼來自Go 1.18.6版本。

標準方式底層實現(xiàn)

string轉(zhuǎn)[]byte底層實現(xiàn)

先看string轉(zhuǎn)[]byte的實現(xiàn),(實現(xiàn)源碼在 src/runtime/string.go 中)

const tmpStringBufSize = 32

//長度32的數(shù)組
type tmpBuf [tmpStringBufSize]byte

//時間函數(shù)
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 {
        //預(yù)定義數(shù)組長度不夠,重新分配內(nèi)存
        b = rawbyteslice(len(s))
    }
    copy(b, s)
    return b
}

// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
//rawbyteslice函數(shù) 分配一個新的字節(jié)片。字節(jié)片未歸零
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轉(zhuǎn)[]byte是,會根據(jù)字符串長度來決定是否需要重新分配一塊內(nèi)存。

  • ? 預(yù)先定義了一個長度為32的數(shù)組
  • ? 若字符串的長度不超過這個長度32的數(shù)組,copy函數(shù)實現(xiàn)string到[]byte的拷貝
  • ? 若字符串的長度超過了這個長度32的數(shù)組,重新分配一塊內(nèi)存了,再進行copy

[]byte轉(zhuǎn)string底層實現(xiàn)

再看[]byte轉(zhuǎn)string的實現(xiàn),(實現(xiàn)源碼在 src/runtime/string.go 中)

const tmpStringBufSize = 32

//長度32的數(shù)組
type tmpBuf [tmpStringBufSize]byte

//實現(xiàn)函數(shù)
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數(shù)組至字符串
    memmove(p, unsafe.Pointer(ptr), uintptr(n))
    return
}

跟string轉(zhuǎn)[]byte一樣,當(dāng)數(shù)組長度超過32時,同樣需要調(diào)用mallocgc分配一塊新內(nèi)存

強轉(zhuǎn)換底層實現(xiàn)

從標準的轉(zhuǎn)換方式中,我們知道如果字符串長度超過32的話,會重新分配一塊新內(nèi)存,進行內(nèi)存拷貝。

//string轉(zhuǎn)[]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
}

強轉(zhuǎn)換過程中,通過 神奇的unsafe.Pointer指針

? 任何類型的指針 *T 都可以轉(zhuǎn)換為unsafe.Pointer類型的指針,可以存儲任何變量的地址

? unsafe.Pointer 類型的指針也可以轉(zhuǎn)換回普通指針,并且可以和類型*T不相同

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

reflect.SliceHeader:slice類型的運行時表示形式

reflect.StringHeader:string類型的運行時表示形式

//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 把它們轉(zhuǎn)換為 *reflect.SliceHeader 指針。

而運行時表現(xiàn)形式 SliceHeader 和 StringHeader,而這兩個結(jié)構(gòu)體都有一個 Data 字段,用于存放指向真實內(nèi)容的指針。

??[]byte 和 string之間的轉(zhuǎn)換,就可以理解為是通過 unsafe.Pointer 把 *SliceHeader 轉(zhuǎn)為 *StringHeader,也就是 *[]byte 和 *string之間的轉(zhuǎn)換。

那么我們就可以理解相對于標準轉(zhuǎn)換方式,強轉(zhuǎn)換方式的優(yōu)點在哪了!

直接替換指針的指向,避免了申請新內(nèi)存(零拷貝),因為兩者指向的底層字段Data地址相同

總結(jié)

今天小許和大家一起了解了[]byte和string類型,以及[]byte和string的兩種轉(zhuǎn)換方式。

不過Go語言提供給我們使用的還是標準轉(zhuǎn)換方式,主要是因為在你不確定安全隱患的情況下,使用強轉(zhuǎn)化方式可能不必要的問題。

不過像fasthttp那樣,對程序?qū)\行性能有高要求,那就可以考慮使用強轉(zhuǎn)換方式!

責(zé)任編輯:武曉燕 來源: 小許code
相關(guān)推薦

2010-07-14 10:30:26

Perl多線程

2021-12-08 10:47:35

RabbitMQ 實現(xiàn)延遲

2009-06-23 18:18:13

SpringHibernate

2011-03-03 10:26:04

Pureftpd

2009-06-15 15:02:48

Spring定時器

2021-05-27 10:57:01

TCP定時器網(wǎng)絡(luò)協(xié)議

2020-02-21 17:33:17

SparkKafka數(shù)據(jù)

2022-06-08 15:12:34

前端前端截圖

2023-05-31 19:10:31

2020-05-11 13:03:03

SR-TEIP路由器

2009-06-25 13:43:00

Buffalo AJA

2010-10-21 16:24:18

sql server升

2010-07-13 14:54:15

Perl面向?qū)ο缶幊?/a>

2012-12-24 13:30:34

iOS

2010-09-28 15:12:27

Javascript

2010-08-06 09:38:11

Flex讀取XML

2023-03-29 13:06:36

2015-10-09 09:51:29

Web API認證

2010-09-07 11:09:59

2010-07-27 15:03:37

Flex ArrayC
點贊
收藏

51CTO技術(shù)棧公眾號