最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)資料,文中通過示例代碼介紹的非常詳細(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類型,需要將其轉(zhuǎn)換為字符串類型。當(dāng)數(shù)據(jù)量小時(shí),類型間轉(zhuǎn)換的開銷可以忽略不計(jì),但當(dāng)數(shù)據(jù)量增大后,可能成為性能瓶頸,使用高效的轉(zhuǎn)換方法能減少這方面的開銷

數(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ì)為不可變類型(當(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ù)的線程安全

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

字符串不可變有很多好處,為了維持其不可變特性,字符串和字節(jié)數(shù)組互轉(zhuǎn)一般是通過數(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)單,但是通過底層數(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還是通過mallocgc新申請(qǐng)一段內(nèi)存,但無(wú)論哪種方式,最后都要拷貝數(shù)據(jù)
這里設(shè)置了轉(zhuǎn)換后字符串的len屬性

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

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

unsafe.Pointer,int,uintpt這三種類型占用的內(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切片類型可以看做 [3]uintptr

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

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

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

[]byte轉(zhuǎn)string更簡(jiǎn)單,直接轉(zhuǎn)換指針類型即可,忽略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)換不同類型的指針,沒有底層數(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)文章

  • GoLand 中設(shè)置默認(rèn)項(xiàng)目文件夾的實(shí)現(xiàn)

    GoLand 中設(shè)置默認(rèn)項(xiàng)目文件夾的實(shí)現(xiàn)

    本文主要介紹了GoLand 中設(shè)置默認(rèn)項(xiàng)目文件夾的實(shí)現(xiàn),默認(rèn)項(xiàng)目文件夾會(huì)在你打開或新建項(xiàng)目時(shí)自動(dòng)預(yù)選,避免每次都需要手動(dòng)導(dǎo)航到目標(biāo)目錄,感興趣的可以了解一下
    2025-03-03
  • golang 跳出for循環(huán)操作

    golang 跳出for循環(huán)操作

    這篇文章主要介紹了golang 跳出for循環(huán)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2020-12-12
  • Go基于struct?tag實(shí)現(xiàn)結(jié)構(gòu)體字段級(jí)別的訪問控制

    Go基于struct?tag實(shí)現(xiàn)結(jié)構(gòu)體字段級(jí)別的訪問控制

    本文將會(huì)基于這個(gè)主題展開,討論Go中的結(jié)構(gòu)體tag究竟是什么,我們?cè)撊绾卫盟?另外,文末還提供了一個(gè)實(shí)際案例,實(shí)現(xiàn)結(jié)構(gòu)體字段級(jí)別的訪問,幫助我們進(jìn)一步提升對(duì)struct tag的理解
    2024-02-02
  • GoLang?socket網(wǎng)絡(luò)編程傳輸數(shù)據(jù)包時(shí)進(jìn)行長(zhǎng)度校驗(yàn)的方法

    GoLang?socket網(wǎng)絡(luò)編程傳輸數(shù)據(jù)包時(shí)進(jìn)行長(zhǎng)度校驗(yàn)的方法

    在GoLang?socket網(wǎng)絡(luò)編程中,為了確保數(shù)據(jù)交互的穩(wěn)定性和安全性,通常會(huì)通過傳輸數(shù)據(jù)的長(zhǎng)度進(jìn)行校驗(yàn),發(fā)送端首先發(fā)送數(shù)據(jù)長(zhǎng)度,然后發(fā)送數(shù)據(jù)本體,接收端則根據(jù)接收到的數(shù)據(jù)長(zhǎng)度和數(shù)據(jù)本體進(jìn)行比較,以此來(lái)確認(rèn)數(shù)據(jù)是否傳輸成功
    2024-11-11
  • go-micro微服務(wù)JWT跨域認(rèn)證問題

    go-micro微服務(wù)JWT跨域認(rèn)證問題

    JWT 以 JSON 對(duì)象的形式安全傳遞信息。因?yàn)榇嬖跀?shù)字簽名,因此所傳遞的信息是安全的,這篇文章主要介紹了go-micro微服務(wù)JWT跨域認(rèn)證,需要的朋友可以參考下
    2023-01-01
  • Go語(yǔ)言實(shí)現(xiàn)RSA加解密算法詳解

    Go語(yǔ)言實(shí)現(xiàn)RSA加解密算法詳解

    隨著互聯(lián)網(wǎng)的高速發(fā)展,人們對(duì)安全的要求也越來(lái)越高,加解密也變得越來(lái)越重要,本文主要為大家介紹了Go語(yǔ)言中實(shí)現(xiàn)RSA加解密與簽名驗(yàn)證算法,希望對(duì)大家有所幫助
    2023-06-06
  • Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn)

    Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn)

    本文主要介紹了Go 互斥鎖和讀寫互斥鎖的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Go語(yǔ)言實(shí)現(xiàn)常見限流算法的示例代碼

    Go語(yǔ)言實(shí)現(xiàn)常見限流算法的示例代碼

    計(jì)數(shù)器、滑動(dòng)窗口、漏斗算法、令牌桶算法是我們常見的幾個(gè)限流算法,本文將依次用Go語(yǔ)言實(shí)現(xiàn)這幾個(gè)限流算法,感興趣的可以了解一下
    2023-05-05
  • Go語(yǔ)言中一些不常見的命令參數(shù)詳解

    Go語(yǔ)言中一些不常見的命令參數(shù)詳解

    這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中一些不常見的命令參數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • Go語(yǔ)言使用Timeout Context取消任務(wù)的實(shí)現(xiàn)

    Go語(yǔ)言使用Timeout Context取消任務(wù)的實(shí)現(xiàn)

    本文主要介紹了Go語(yǔ)言使用Timeout Context取消任務(wù)的實(shí)現(xiàn),包括基本的任務(wù)取消和控制HTTP客戶端請(qǐng)求的超時(shí),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01

最新評(píng)論

大姚县| 天镇县| 曲阜市| 元江| 习水县| 东城区| 台山市| 绵阳市| 林芝县| 凉城县| 和静县| 辽中县| 五华县| 万宁市| 黄平县| 开远市| 司法| 遂溪县| 乌审旗| 南漳县| 柯坪县| 陵川县| 永昌县| 无为县| 海宁市| 高阳县| 靖远县| 息烽县| 祁东县| 昆山市| 平乐县| 大城县| 林周县| 收藏| 微山县| 海丰县| 青岛市| 犍为县| 务川| 梨树县| 浠水县|