亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解GO語(yǔ)言中[]byte與string的兩種轉(zhuǎn)換方式和底層實(shí)現(xiàn)

 更新時(shí)間:2024年03月25日 11:24:29   作者:小許code  
這篇文章主要為大家詳細(xì)介紹了GO語(yǔ)言中[]byte與string的兩種轉(zhuǎn)換方式和底層實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考下

看過(guò)小許之前的文章《fasthttp是如何做到比net/http快十倍的》,相信你們還對(duì)極致的優(yōu)化方式意猶未盡。

不過(guò)你發(fā)現(xiàn)沒(méi)fasthttp關(guān)于string和[]byte的轉(zhuǎn)換方式和大家平常普遍使用的方式不一樣,fasthttp轉(zhuǎn)換實(shí)現(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
}

為什么不用我們常見(jiàn)的string和[]byte的轉(zhuǎn)換方式呢?這樣做是怎么提高性能的呢?...

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

兩種轉(zhuǎn)換方式

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

反正在寫(xiě)這篇文章之前小許估計(jì)是答不出來(lái)的,哈哈!

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

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

string和[]byte

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

關(guān)于string類(lèi)型,在go標(biāo)準(zhǔn)庫(kù)中官方說(shuō)明如下:

// 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類(lèi)型沒(méi)有cap字段

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

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

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

標(biāo)準(zhǔn)方式

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

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

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

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

強(qiáng)轉(zhuǎn)換方式

強(qiáng)轉(zhuǎn)換方式是通過(guò)unsafe和reflect包來(lái)實(shí)現(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(代表一個(gè)運(yùn)行時(shí)的切片) 和 unsafe.Pointer進(jìn)行指針替換。

為什么可以這么做呢?

前面我們?cè)谥vstring和[]byte類(lèi)型的時(shí)候就提了,因?yàn)閮烧叩牡讓咏Y(jié)構(gòu)的字段相似!

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

分析

我們看下這兩種轉(zhuǎn)換方式底層是如何實(shí)現(xiàn)的,這些實(shí)現(xiàn)代碼在標(biāo)準(zhǔn)庫(kù)中都是有的,下面底層實(shí)現(xiàn)的代碼來(lái)自Go 1.18.6版本。

標(biāo)準(zhǔn)方式底層實(shí)現(xiàn)

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

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

const tmpStringBufSize = 32

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

//時(shí)間函數(shù)
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
    var b []byte
    //判斷字符串長(zhǎng)度是否小于等于32
    if buf != nil && len(s) <= len(buf) {
        *buf = tmpBuf{}
        b = buf[:len(s)]
    } else {
        //預(yù)定義數(shù)組長(zhǎng)度不夠,重新分配內(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ù) 分配一個(gè)新的字節(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是,會(huì)根據(jù)字符串長(zhǎng)度來(lái)決定是否需要重新分配一塊內(nèi)存。

• 預(yù)先定義了一個(gè)長(zhǎng)度為32的數(shù)組

• 若字符串的長(zhǎng)度不超過(guò)這個(gè)長(zhǎng)度32的數(shù)組,copy函數(shù)實(shí)現(xiàn)string到[]byte的拷貝

• 若字符串的長(zhǎng)度超過(guò)了這個(gè)長(zhǎng)度32的數(shù)組,重新分配一塊內(nèi)存了,再進(jìn)行copy

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

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

const tmpStringBufSize = 32

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

//實(shí)現(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
    //判斷字符串長(zhǎng)度是否小于等于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ù)組長(zhǎng)度超過(guò)32時(shí),同樣需要調(diào)用mallocgc分配一塊新內(nèi)存

強(qiáng)轉(zhuǎn)換底層實(shí)現(xiàn)

從標(biāo)準(zhǔn)的轉(zhuǎn)換方式中,我們知道如果字符串長(zhǎng)度超過(guò)32的話,會(huì)重新分配一塊新內(nèi)存,進(jìn)行內(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
}

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

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

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

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

reflect.SliceHeader:slice類(lèi)型的運(yùn)行時(shí)表示形式

reflect.StringHeader:string類(lèi)型的運(yùn)行時(shí)表示形式

//slice在運(yùn)行時(shí)的描述符
type SliceHeader struct {      
     Data uintptr
     Len  int
    Cap  int
}

//string在運(yùn)行時(shí)的描述符
type StringHeader struct {
    Data uintptr
    Len  int
}

*(reflect.SliceHeader)(unsafe.Pointer(&b)) 的目的就是通過(guò)unsafe.Pointer 把它們轉(zhuǎn)換為 *reflect.SliceHeader 指針。

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

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

那么我們就可以理解相對(duì)于標(biāo)準(zhǔn)轉(zhuǎn)換方式,強(qiáng)轉(zhuǎn)換方式的優(yōu)點(diǎn)在哪了!

直接替換指針的指向,避免了申請(qǐng)新內(nèi)存(零拷貝),因?yàn)閮烧咧赶虻牡讓幼侄蜠ata地址相同

總結(jié)

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

不過(guò)Go語(yǔ)言提供給我們使用的還是標(biāo)準(zhǔn)轉(zhuǎn)換方式,主要是因?yàn)樵谀悴淮_定安全隱患的情況下,使用強(qiáng)轉(zhuǎn)化方式可能不必要的問(wèn)題。

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

到此這篇關(guān)于詳解GO語(yǔ)言中[]byte與string的兩種轉(zhuǎn)換方式和底層實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)GO byte與string轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言編程中字符串切割方法小結(jié)

    Go語(yǔ)言編程中字符串切割方法小結(jié)

    這篇文章主要介紹了Go語(yǔ)言編程中字符串切割方法小結(jié),所整理的方法都來(lái)自字符串相關(guān)的strings包,需要的朋友可以參考下
    2015-10-10
  • Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解

    Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解

    這篇文章主要為大家介紹了Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 使用go求冪的幾種方法小結(jié)

    使用go求冪的幾種方法小結(jié)

    這篇文章主要介紹了使用go求冪的幾種方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 一文帶你了解Golang中的泛型

    一文帶你了解Golang中的泛型

    泛型是一種可以編寫(xiě)?yīng)毩⒂谑褂玫奶囟?lèi)型的代碼的方法,可以通過(guò)編寫(xiě)函數(shù)或類(lèi)型來(lái)使用一組類(lèi)型中的任何一個(gè),下面就來(lái)和大家聊聊Golang中泛型的使用吧
    2023-07-07
  • golang抓取tcp包的實(shí)現(xiàn)方式

    golang抓取tcp包的實(shí)現(xiàn)方式

    使用`golang`的`packet`和`pcap`庫(kù)可以抓取TCP數(shù)據(jù)包,首先,確保安裝了`pcap`庫(kù),然后使用以下代碼打開(kāi)網(wǎng)絡(luò)接口,設(shè)置過(guò)濾規(guī)則為“tcp”,開(kāi)始捕獲并解析TCP數(shù)據(jù)包,運(yùn)行代碼時(shí)需要管理員權(quán)限
    2024-12-12
  • Go語(yǔ)言學(xué)習(xí)筆記之反射用法詳解

    Go語(yǔ)言學(xué)習(xí)筆記之反射用法詳解

    這篇文章主要介紹了Go語(yǔ)言學(xué)習(xí)筆記之反射用法,詳細(xì)分析了Go語(yǔ)言中反射的概念、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-05-05
  • Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器

    Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器

    本文主要介紹了Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Go語(yǔ)言時(shí)間相關(guān)操作合集(超詳細(xì))

    Go語(yǔ)言時(shí)間相關(guān)操作合集(超詳細(xì))

    在開(kāi)發(fā)應(yīng)用程序的過(guò)程中,經(jīng)常需要記錄某些操作的時(shí)間或者格式化時(shí)間戳,因此大部分編程語(yǔ)言都會(huì)有操作時(shí)間的庫(kù),Go語(yǔ)言當(dāng)然也不例外,本文我們就一起來(lái)學(xué)習(xí)一下time包的使用
    2023-08-08
  • 快速解決Golang Map 并發(fā)讀寫(xiě)安全的問(wèn)題

    快速解決Golang Map 并發(fā)讀寫(xiě)安全的問(wèn)題

    這篇文章主要介紹了快速解決Golang Map 并發(fā)讀寫(xiě)安全的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Golang運(yùn)行報(bào)錯(cuò)找不到包:package?xxx?is?not?in?GOROOT的解決過(guò)程

    Golang運(yùn)行報(bào)錯(cuò)找不到包:package?xxx?is?not?in?GOROOT的解決過(guò)程

    這篇文章主要給大家介紹了關(guān)于Golang運(yùn)行報(bào)錯(cuò)找不到包:package?xxx?is?not?in?GOROOT的解決過(guò)程,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-07-07

最新評(píng)論