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

Go中string與[]byte高效互轉(zhuǎn)的方法實(shí)例

 更新時(shí)間:2021年09月20日 12:16:42   作者:亞洲第一中鋒_哈達(dá)迪  
string與[]byte經(jīng)常需要互相轉(zhuǎn)化,普通轉(zhuǎn)化會(huì)發(fā)生底層數(shù)據(jù)的復(fù)制,下面這篇文章主要給大家介紹了關(guān)于Go中string與[]byte高效互轉(zhuǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

當(dāng)我們使用go進(jìn)行數(shù)據(jù)序列化或反序列化操作時(shí),可能經(jīng)常涉及到字符串和字節(jié)數(shù)組的轉(zhuǎn)換。例如:

if str, err := json.Marshal(from); err != nil {

    panic(err)

} else {

    return string(str)

}

json序列化后為[]byte類(lèi)型,需要將其轉(zhuǎn)換為字符串類(lèi)型。當(dāng)數(shù)據(jù)量小時(shí),類(lèi)型間轉(zhuǎn)換的開(kāi)銷(xiāo)可以忽略不計(jì),但當(dāng)數(shù)據(jù)量增大后,可能成為性能瓶頸,使用高效的轉(zhuǎn)換方法能減少這方面的開(kāi)銷(xiāo)

數(shù)據(jù)結(jié)構(gòu)

在了解其如何轉(zhuǎn)換前,需要了解其底層數(shù)據(jù)結(jié)構(gòu)

本文基于go 1.13.12

string:

type stringStruct struct {

   str unsafe.Pointer

   len int

}

slice:

type slice struct {

   array unsafe.Pointer

   len   int

   cap   int

}

與slice的結(jié)構(gòu)相比,string缺少一個(gè)表示容量的cap字段,因此不能對(duì)string遍歷使用內(nèi)置的cap()函數(shù)那為什么string不需要cap字段呢?因?yàn)間o中string被設(shè)計(jì)為不可變類(lèi)型(當(dāng)然在很多其他語(yǔ)言中也是),由于其不可像slice一樣追加元素,也就不需要cap字段判斷是否超出底層數(shù)組的容量,來(lái)決定是否擴(kuò)容

只有l(wèi)en屬性不影響for-range等讀取操作,因?yàn)閒or-range操作只根據(jù)len決定是否跳出循環(huán)

那為什么字符串要設(shè)定為不可變呢?因?yàn)檫@樣能保證字符串的底層數(shù)組不發(fā)生改變

舉個(gè)例子,map中以string為鍵,如果底層字符數(shù)組改變,則計(jì)算出的哈希值也會(huì)發(fā)生變化,這樣再?gòu)膍ap中定位時(shí)就找不到之前的value,因此其不可變特性能避免這種情況發(fā)生,string也適合作為map的鍵。除此之外,不可變特性也能保障數(shù)據(jù)的線(xiàn)程安全

常規(guī)實(shí)現(xiàn)

字符串不可變有很多好處,為了維持其不可變特性,字符串和字節(jié)數(shù)組互轉(zhuǎn)一般是通過(guò)數(shù)據(jù)拷貝的方式實(shí)現(xiàn):

var a string = "hello world"

var b []byte = []byte(a)  // string轉(zhuǎn)[]byte

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

這種方式實(shí)現(xiàn)簡(jiǎn)單,但是通過(guò)底層數(shù)據(jù)復(fù)制實(shí)現(xiàn)的,在編譯期間分別轉(zhuǎn)換成對(duì)slicebytetostring和stringtoslicebyte的函數(shù)調(diào)用

string轉(zhuǎn)[]byte

func stringtoslicebyte(buf *tmpBuf, s string) []byte {

   var b []byte

   if buf != nil && len(s) <= len(buf) {

      *buf = tmpBuf{}

      b = buf[:len(s)]

   } else {

      // 申請(qǐng)內(nèi)存

      b = rawbyteslice(len(s))

   }

   // 復(fù)制數(shù)據(jù)

   copy(b, s)

   return b

}

其根據(jù)返回值是否逃逸到堆上,以及buf的長(zhǎng)度是否足夠,判斷選擇使用buf還是調(diào)用rawbyteslice申請(qǐng)一個(gè)slice。但不管是哪種,都會(huì)執(zhí)行一次copy拷貝底層數(shù)據(jù)

[]byte轉(zhuǎn)string

func slicebytetostring(buf *tmpBuf, b []byte) (str string) {

   l := len(b)

   if l == 0 {

 return ""

   }

   if l == 1 {

      stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])

      stringStructOf(&str).len = 1

      return

   }



   var p unsafe.Pointer

   if buf != nil && len(b) <= len(buf) {

      p = unsafe.Pointer(buf)

   } else {

      p = mallocgc(uintptr(len(b)), nil, false)

   }

   // 賦值底層指針

   stringStructOf(&str).str = p

   // 賦值長(zhǎng)度

   stringStructOf(&str).len = len(b)

   // 拷貝數(shù)據(jù)

   memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))

   return

}

首先處理長(zhǎng)度為0或1的情況,再判斷使用buf還是通過(guò)mallocgc新申請(qǐng)一段內(nèi)存,但無(wú)論哪種方式,最后都要拷貝數(shù)據(jù)
這里設(shè)置了轉(zhuǎn)換后字符串的len屬性

高效實(shí)現(xiàn)

如果程序保證不對(duì)底層數(shù)據(jù)進(jìn)行修改,那么只轉(zhuǎn)換類(lèi)型,不拷貝數(shù)據(jù),是否可以提高性能?

unsafe.Pointer,int,uintpt這三種類(lèi)型占用的內(nèi)存大小相同

var v1 unsafe.Pointer

var v2 int

var v3 uintptr

fmt.Println(unsafe.Sizeof(v1)) // 8

fmt.Println(unsafe.Sizeof(v2)) // 8

fmt.Println(unsafe.Sizeof(v3)) // 8

因此從底層結(jié)構(gòu)上來(lái)看string可以看做[2]uintptr,[]byte切片類(lèi)型可以看做 [3]uintptr

那么從string轉(zhuǎn)[]byte只需構(gòu)建出 [3]uintptr{ptr,len,len}

這里我們?yōu)閟lice結(jié)構(gòu)生成了cap字段,其實(shí)這里不生成cap字段對(duì)讀取操作沒(méi)有影響,但如果要往轉(zhuǎn)換后的slice append元素可能有問(wèn)題,原因如下:

這樣做slice的cap屬性是隨機(jī)的,可能是大于len的值,那么append時(shí)就不會(huì)新開(kāi)辟一段內(nèi)存存放元素,而是在原數(shù)組后面追加,如果后面的內(nèi)存不可寫(xiě)就會(huì)panic

[]byte轉(zhuǎn)string更簡(jiǎn)單,直接轉(zhuǎn)換指針類(lèi)型即可,忽略cap字段

實(shí)現(xiàn)如下:

func stringTobyteSlice(s string) []byte {

   tmp1 := (*[2]uintptr)(unsafe.Pointer(&s))

   tmp2 := [3]uintptr{tmp1[0], tmp1[1], tmp1[1]}

   return *(*[]byte)(unsafe.Pointer(&tmp2))

}



func byteSliceToString(bytes []byte) string {

   return *(*string)(unsafe.Pointer(&bytes))

}

這里使用unsafe.Pointer來(lái)轉(zhuǎn)換不同類(lèi)型的指針,沒(méi)有底層數(shù)據(jù)的拷貝

性能測(cè)試

接下來(lái)對(duì)高效實(shí)現(xiàn)進(jìn)行性能測(cè)試,這里選用長(zhǎng)度為100的字符串或字節(jié)數(shù)組進(jìn)行轉(zhuǎn)換

分別測(cè)試以下4個(gè)方法:

func stringTobyteSlice(s string) []byte {

   tmp1 := (*[2]uintptr)(unsafe.Pointer(&s))

   tmp2 := [3]uintptr{tmp1[0], tmp1[1], tmp1[1]}

   return *(*[]byte)(unsafe.Pointer(&tmp2))

}



func stringTobyteSliceOld(s string) []byte {

   return []byte(s)

}



func byteSliceToString(bytes []byte) string {

   return *(*string)(unsafe.Pointer(&bytes))

}



func byteSliceToStringOld(bytes []byte) string {

   return string(bytes)

}

測(cè)試結(jié)果如下:

BenchmarkStringToByteSliceOld-12            28637332                42.0 ns/op

BenchmarkStringToByteSliceNew-12            1000000000                 0.496 ns/op

BenchmarkByteSliceToStringOld-12            32595271                36.0 ns/op

BenchmarkByteSliceToStringNew-12            1000000000                 0.256 ns/op

可以看出性能差距比較大,如果需要轉(zhuǎn)換的字符串或字節(jié)數(shù)組長(zhǎng)度更長(zhǎng),性能提升更加明顯

總結(jié)

本文介紹了字符串和數(shù)組的底層數(shù)據(jù)結(jié)構(gòu),以及高效的互轉(zhuǎn)方法,需要注意的是,其適用于程序能保證不對(duì)底層數(shù)據(jù)進(jìn)行修改的場(chǎng)景。若不能保證,且底層數(shù)據(jù)被修改可能引發(fā)異常,則還是使用拷貝的方式

到此這篇關(guān)于Go中string與[]byte高效互轉(zhuǎn)的文章就介紹到這了,更多相關(guān)Go中string與[]byte互轉(zhuǎn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • windows下安裝make及使用makefile文件

    windows下安裝make及使用makefile文件

    這篇文章主要為大家介紹了windows下安裝make及使用makefile文件方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Go語(yǔ)言context上下文管理的使用

    Go語(yǔ)言context上下文管理的使用

    本文主要介紹了Go語(yǔ)言context上下文管理的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Go語(yǔ)言同步與異步執(zhí)行多個(gè)任務(wù)封裝詳解(Runner和RunnerAsync)

    Go語(yǔ)言同步與異步執(zhí)行多個(gè)任務(wù)封裝詳解(Runner和RunnerAsync)

    這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言同步與異步執(zhí)行多個(gè)任務(wù)封裝(Runner和RunnerAsync)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • Golang實(shí)現(xiàn)http server提供壓縮文件下載功能

    Golang實(shí)現(xiàn)http server提供壓縮文件下載功能

    這篇文章主要介紹了Golang實(shí)現(xiàn)http server提供壓縮文件下載功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Go?iota關(guān)鍵字與枚舉類(lèi)型實(shí)現(xiàn)原理

    Go?iota關(guān)鍵字與枚舉類(lèi)型實(shí)現(xiàn)原理

    這篇文章主要介紹了Go?iota關(guān)鍵字與枚舉類(lèi)型實(shí)現(xiàn)原理,iota是go語(yǔ)言的常量計(jì)數(shù)器,只能在常量的表達(dá)式中使用,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-07-07
  • golang中protobuf的使用詳解

    golang中protobuf的使用詳解

    protobuf是Google公司提出的一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,常用于結(jié)構(gòu)化數(shù)據(jù)的序列化,具有語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、可擴(kuò)展性特性,常用于通訊協(xié)議、服務(wù)端數(shù)據(jù)交換場(chǎng)景,下面我們就來(lái)看看golang中protobuf的具體使用吧
    2023-10-10
  • Go??import _ 下劃線(xiàn)使用

    Go??import _ 下劃線(xiàn)使用

    這篇文章主要為大家介紹了Go??import下劃線(xiàn)_使用小技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go語(yǔ)言基礎(chǔ)map用法及示例詳解

    Go語(yǔ)言基礎(chǔ)map用法及示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)map的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • GoLang中panic與recover函數(shù)以及defer語(yǔ)句超詳細(xì)講解

    GoLang中panic與recover函數(shù)以及defer語(yǔ)句超詳細(xì)講解

    這篇文章主要介紹了GoLang的panic、recover函數(shù),以及defer語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-01-01
  • Golang?手寫(xiě)一個(gè)簡(jiǎn)單的并發(fā)任務(wù)?manager

    Golang?手寫(xiě)一個(gè)簡(jiǎn)單的并發(fā)任務(wù)?manager

    這篇文章主要介紹了Golang?手寫(xiě)一個(gè)簡(jiǎn)單的并發(fā)任務(wù)?manager,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-08-08

最新評(píng)論