日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
面試官:你能聊聊String和[]byte的轉(zhuǎn)換嗎?

本文轉(zhuǎn)載自微信公眾號(hào)「Golang夢(mèng)工廠」,作者Golang夢(mèng)工廠。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang夢(mèng)工廠公眾號(hào)。

成都創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括尼金平網(wǎng)站建設(shè)、尼金平網(wǎng)站制作、尼金平網(wǎng)頁(yè)制作以及尼金平網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,尼金平網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到尼金平省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!

前言

哈嘍,大家好,我是asong。為什么會(huì)有今天這篇文章呢?前天在一個(gè)群里看到了一份Go語(yǔ)言面試的八股文,其中有一道題就是"字符串轉(zhuǎn)成byte數(shù)組,會(huì)發(fā)生內(nèi)存拷貝嗎?";這道題挺有意思的,本質(zhì)就是在問(wèn)你string和[]byte的轉(zhuǎn)換原理,考驗(yàn)?zāi)愕幕竟Φ?。今天我們就?lái)好好的探討一下兩者之間的轉(zhuǎn)換方式。

byte類(lèi)型

我們看一下官方對(duì)byte的定義:

 
 
 
 
  1. // byte is an alias for uint8 and is equivalent to uint8 in all ways. It is 
  2. // used, by convention, to distinguish byte values from 8-bit unsigned 
  3. // integer values. 
  4. type byte = uint8 

我們可以看到byte就是uint8的別名,它是用來(lái)區(qū)分字節(jié)值和8位無(wú)符號(hào)整數(shù)值。

其實(shí)可以把byte當(dāng)作一個(gè)ASCII碼的一個(gè)字符。

示例:

 
 
 
 
  1. var ch byte = 65 
  2. var ch byte = '\x41' 
  3. var ch byte = 'A' 

[]byte類(lèi)型

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

 
 
 
 
  1. // src/runtime/slice.go 
  2. type slice struct { 
  3.     array unsafe.Pointer 
  4.     len   int 
  5.     cap   int 

這里簡(jiǎn)單說(shuō)明一下這幾個(gè)字段,array代表底層數(shù)組的指針,len代表切片長(zhǎng)度,cap代表容量??匆粋€(gè)簡(jiǎn)單示例:

 
 
 
 
  1. func main()  { 
  2.  sl := make([]byte,0,2) 
  3.  sl = append(sl, 'A') 
  4.  sl = append(sl,'B') 
  5.  fmt.Println(sl) 

根據(jù)這個(gè)例子我們可以畫(huà)一個(gè)圖:

string類(lèi)型

先來(lái)看一下string的官方定義:

 
 
 
 
  1. // string is the set of all strings of 8-bit bytes, conventionally but not 
  2. // necessarily representing UTF-8-encoded text. A string may be empty, but 
  3. // not nil. Values of string type are immutable. 
  4. type string string 

string是一個(gè)8位字節(jié)的集合,通常但不一定代表UTF-8編碼的文本。string可以為空,但是不能為nil。string的值是不能改變的。

看一個(gè)簡(jiǎn)單的例子:

 
 
 
 
  1. func main()  { 
  2.  str := "asong" 
  3.  fmt.Println(str) 

string類(lèi)型本質(zhì)也是一個(gè)結(jié)構(gòu)體,定義如下:

 
 
 
 
  1. type stringStruct struct { 
  2.     str unsafe.Pointer 
  3.     len int 

stringStruct和slice還是很相似的,str指針指向的是某個(gè)數(shù)組的首地址,len代表的就是數(shù)組長(zhǎng)度。怎么和slice這么相似,底層指向的也是數(shù)組,是什么數(shù)組呢?我們看看他在實(shí)例化時(shí)調(diào)用的方法:

 
 
 
 
  1. //go:nosplit 
  2. func gostringnocopy(str *byte) string { 
  3.  ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} 
  4.  s := *(*string)(unsafe.Pointer(&ss)) 
  5.  return s 

入?yún)⑹且粋€(gè)byte類(lèi)型的指針,從這我們可以看出string類(lèi)型底層是一個(gè)byte類(lèi)型的數(shù)組,所以我們可以畫(huà)出這樣一個(gè)圖片:

string和[]byte有什么區(qū)別

上面我們一起分析了string類(lèi)型,其實(shí)他底層本質(zhì)就是一個(gè)byte類(lèi)型的數(shù)組,那么問(wèn)題就來(lái)了,string類(lèi)型為什么還要在數(shù)組的基礎(chǔ)上再進(jìn)行一次封裝呢?

這是因?yàn)樵贕o語(yǔ)言中string類(lèi)型被設(shè)計(jì)為不可變的,不僅是在Go語(yǔ)言,其他語(yǔ)言中string類(lèi)型也是被設(shè)計(jì)為不可變的,這樣的好處就是:在并發(fā)場(chǎng)景下,我們可以在不加鎖的控制下,多次使用同一字符串,在保證高效共享的情況下而不用擔(dān)心安全問(wèn)題。

string類(lèi)型雖然是不能更改的,但是可以被替換,因?yàn)閟tringStruct中的str指針是可以改變的,只是指針指向的內(nèi)容是不可以改變的??磦€(gè)例子:

 
 
 
 
  1. func main()  { 
  2.  str := "song" 
  3.  fmt.Printf("%p\n",[]byte(str)) 
  4.  str = "asong" 
  5.  fmt.Printf("%p\n",[]byte(str)) 
  6. // 運(yùn)行結(jié)果 
  7. 0xc00001a090 
  8. 0xc00001a098 

我們可以看出來(lái),指針指向的位置發(fā)生了變化,也就說(shuō)每一個(gè)更改字符串,就需要重新分配一次內(nèi)存,之前分配的空間會(huì)被gc回收。

string和[]byte標(biāo)準(zhǔn)轉(zhuǎn)換

Go語(yǔ)言中提供了標(biāo)準(zhǔn)方式對(duì)string和[]byte進(jìn)行轉(zhuǎn)換,先看一個(gè)例子:

 
 
 
 
  1. func main()  { 
  2.  str := "asong" 
  3.  by := []byte(str) 
  4.  
  5.  str1 := string(by) 
  6.  fmt.Println(str1) 

標(biāo)準(zhǔn)轉(zhuǎn)換用起來(lái)還是比較簡(jiǎn)單的,那你知道他們內(nèi)部是怎樣實(shí)現(xiàn)轉(zhuǎn)換的嗎?我們來(lái)分析一下:

  • string類(lèi)型轉(zhuǎn)換到[]byte類(lèi)型

我們對(duì)上面的代碼執(zhí)行如下指令go tool compile -N -l -S ./string_to_byte/string.go,可以看到調(diào)用的是runtime.stringtoslicebyte:

 
 
 
 
  1. // runtime/string.go go 1.15.7 
  2. const tmpStringBufSize = 32 
  3.  
  4. type tmpBuf [tmpStringBufSize]byte 
  5.  
  6. func stringtoslicebyte(buf *tmpBuf, s string) []byte { 
  7.  var b []byte 
  8.  if buf != nil && len(s) <= len(buf) { 
  9.   *buf = tmpBuf{} 
  10.   b = buf[:len(s)] 
  11.  } else { 
  12.   b = rawbyteslice(len(s)) 
  13.  } 
  14.  copy(b, s) 
  15.  return b 
  16. // rawbyteslice allocates a new byte slice. The byte slice is not zeroed. 
  17. func rawbyteslice(size int) (b []byte) { 
  18.  cap := roundupsize(uintptr(size)) 
  19.  p := mallocgc(cap, nil, false) 
  20.  if cap != uintptr(size) { 
  21.   memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size)) 
  22.  } 
  23.  
  24.  *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)} 
  25.  return 

這里分了兩種狀況,通過(guò)字符串長(zhǎng)度來(lái)決定是否需要重新分配一塊內(nèi)存。也就是說(shuō)預(yù)先定義了一個(gè)長(zhǎng)度為32的數(shù)組,字符串的長(zhǎng)度超過(guò)了這個(gè)數(shù)組的長(zhǎng)度,就說(shuō)明[]byte不夠用了,需要重新分配一塊內(nèi)存了。這也算是一種優(yōu)化吧,32是閾值,只有超過(guò)32才會(huì)進(jìn)行內(nèi)存分配。

最后我們會(huì)通過(guò)調(diào)用copy方法實(shí)現(xiàn)string到[]byte的拷貝,具體實(shí)現(xiàn)在src/runtime/slice.go中的slicestringcopy方法,這里就不貼這段代碼了,這段代碼的核心思路就是:將string的底層數(shù)組從頭部復(fù)制n個(gè)到[]byte對(duì)應(yīng)的底層數(shù)組中去

  • []byte類(lèi)型轉(zhuǎn)換到string類(lèi)型

[]byte類(lèi)型轉(zhuǎn)換到string類(lèi)型本質(zhì)調(diào)用的就是runtime.slicebytetostring:

 
 
 
 
  1. // 以下無(wú)關(guān)的代碼片段 
  2. func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) { 
  3.  if n == 0 { 
  4.   return "" 
  5.  } 
  6.  if n == 1 { 
  7.   p := unsafe.Pointer(&staticuint64s[*ptr]) 
  8.   if sys.BigEndian { 
  9.    p = add(p, 7) 
  10.   } 
  11.   stringStructOf(&str).str = p 
  12.   stringStructOf(&str).len = 1 
  13.   return 
  14.  } 
  15.  
  16.  var p unsafe.Pointer 
  17.  if buf != nil && n <= len(buf) { 
  18.   p = unsafe.Pointer(buf) 
  19.  } else { 
  20.   p = mallocgc(uintptr(n), nil, false) 
  21.  } 
  22.  stringStructOf(&str).str = p 
  23.  stringStructOf(&str).len = n 
  24.  memmove(p, unsafe.Pointer(ptr), uintptr(n)) 
  25.  return 

這段代碼我們可以看出會(huì)根據(jù)[]byte的長(zhǎng)度來(lái)決定是否重新分配內(nèi)存,最后通過(guò)memove可以拷貝數(shù)組到字符串。

string和[]byte強(qiáng)轉(zhuǎn)換

標(biāo)準(zhǔn)的轉(zhuǎn)換方法都會(huì)發(fā)生內(nèi)存拷貝,所以為了減少內(nèi)存拷貝和內(nèi)存申請(qǐng)我們可以使用強(qiáng)轉(zhuǎn)換的方式對(duì)兩者進(jìn)行轉(zhuǎn)換。在標(biāo)準(zhǔn)庫(kù)中有對(duì)這兩種方法實(shí)現(xiàn):

 
 
 
 
  1. // runtime/string.go 
  2. func slicebytetostringtmp(ptr *byte, n int) (str string) { 
  3.  stringStructOf(&str).str = unsafe.Pointer(ptr) 
  4.  stringStructOf(&str).len = n 
  5.  return 
  6.  
  7. func stringtoslicebytetmp(s string) []byte { 
  8.     str := (*stringStruct)(unsafe.Pointer(&s)) 
  9.     ret := slice{array: unsafe.Pointer(str.str), len: str.len, cap: str.len} 
  10.     return *(*[]byte)(unsafe.Pointer(&ret)) 

通過(guò)這兩個(gè)方法我們可知道,主要使用的就是unsafe.Pointer進(jìn)行指針替換,為什么這樣可以呢?因?yàn)閟tring和slice的結(jié)構(gòu)字段是相似的:

 
 
 
 
  1. type stringStruct struct { 
  2.     str unsafe.Pointer 
  3.     len int 
  4. type slice struct { 
  5.     array unsafe.Pointer 
  6.     len   int 
  7.     cap   int 

唯一不同的就是cap字段,array和str是一致的,len是一致的,所以他們的內(nèi)存布局上是對(duì)齊的,這樣我們就可以直接通過(guò)unsafe.Pointer進(jìn)行指針替換。

兩種轉(zhuǎn)換如何取舍

當(dāng)然是推薦大家使用標(biāo)準(zhǔn)轉(zhuǎn)換方式了,畢竟標(biāo)準(zhǔn)轉(zhuǎn)換方式是更安全的!但是如果你是在高性能場(chǎng)景下使用,是可以考慮使用強(qiáng)轉(zhuǎn)換的方式的,但是要注意強(qiáng)轉(zhuǎn)換的使用方式,他不是安全的,這里舉個(gè)例子:

 
 
 
 
  1. func stringtoslicebytetmp(s string) []byte { 
  2.  str := (*reflect.StringHeader)(unsafe.Pointer(&s)) 
  3.  ret := reflect.SliceHeader{Data: str.Data, Len: str.Len, Cap: str.Len} 
  4.  return *(*[]byte)(unsafe.Pointer(&ret)) 
  5.  
  6. func main()  { 
  7.  str := "hello" 
  8.  by := stringtoslicebytetmp(str) 
  9.  by[0] = 'H' 

運(yùn)行結(jié)果:

 
 
 
 
  1. unexpected fault address 0x109d65f 
  2. fatal error: fault 
  3. [signal SIGBUS: bus error code=0x2 addr=0x109d65f pc=0x107eabc] 

我們可以看到程序直接發(fā)生嚴(yán)重錯(cuò)誤了,即使使用defer+recover也無(wú)法捕獲。原因是什么呢?

我們前面介紹過(guò),string類(lèi)型是不能改變的,也就是底層數(shù)據(jù)是不能更改的,這里因?yàn)槲覀兪褂玫氖菑?qiáng)轉(zhuǎn)換的方式,那么by指向了str的底層數(shù)組,現(xiàn)在對(duì)這個(gè)數(shù)組中的元素進(jìn)行更改,就會(huì)出現(xiàn)這個(gè)問(wèn)題,導(dǎo)致整個(gè)程序down掉!

總結(jié)

本文我們一起分析byte和string類(lèi)型的基本定義,也分析了[]byte和string的兩種轉(zhuǎn)換方式,應(yīng)該還差最后一環(huán),也就是大家最關(guān)心的性能測(cè)試,這個(gè)我沒(méi)有做,我覺(jué)得沒(méi)有很大意義,通過(guò)前面的分析就可以得出結(jié)論,強(qiáng)轉(zhuǎn)換的方式性能肯定要比標(biāo)準(zhǔn)轉(zhuǎn)換要好。對(duì)于這兩種方式的使用,大家還是根據(jù)實(shí)際場(chǎng)景來(lái)選擇,脫離場(chǎng)景的談性能就是耍流氓!!!


本文名稱(chēng):面試官:你能聊聊String和[]byte的轉(zhuǎn)換嗎?
本文URL:http://m.5511xx.com/article/djcsosh.html