校园春色亚洲色图_亚洲视频分类_中文字幕精品一区二区精品_麻豆一区区三区四区产品精品蜜桃

主頁 > 知識庫 > 簡單談談Golang中的字符串與字節數組

簡單談談Golang中的字符串與字節數組

熱門標簽:西部云谷一期地圖標注 地圖標注的汽車標 中國地圖標注省會高清 南通如皋申請開通400電話 江西轉化率高的羿智云外呼系統 廣州呼叫中心外呼系統 浙江高速公路地圖標注 學海導航地圖標注 高德地圖標注口訣

前言

字符串是 Go 語言中最常用的基礎數據類型之一,雖然字符串往往都被看做是一個整體,但是實際上字符串是一片連續的內存空間,我們也可以將它理解成一個由字符組成的數組,Go 語言中另外一個與字符串關系非常密切的類型就是字節(Byte)了,相信各位讀者也都非常了解,這里也就不展開介紹。

我們在這一節中就會詳細介紹這兩種基本類型的實現原理以及它們的轉換關系,但是這里還是會將介紹的重點主要放在字符串上,因為這是我們接觸最多的一種基本類型并且后者就是一個簡單的 uint8 類型,所以會給予 string 最大的篇幅,需要注意的是這篇文章不會使用大量的篇幅介紹 UTD-8 以及編碼等知識,主要關注的還是字符串的結構以及常見操作的實現。

字符串雖然在 Go 語言中是基本類型 string ,但是它其實就是字符組成的數組,C 語言中的字符串就可以用 char[] 來表示,作為數組來說它會占用一片連續的內存空間,這片連續的內存空間就存儲了一些 字節 ,這些字節共同組成了字符串, Go 語言中的字符串是一個只讀的字節數組切片 ,下面就是一個只讀的 "hello" 字符串在內存中的結構:

如果是代碼中存在的字符串,會在編譯期間被標記成只讀數據 SRODATA 符號,假設我們有以下的一段代碼,其中包含了一個字符串,當我們將這段代碼編譯成匯編語言時,就能夠看到 hello 字符串有一個 SRODATA 的標記:

$ cat main.go
package main

func main() {
 str := "hello"
 println([]byte(str))
}

$ GOOS=linux GOARCH=amd64 go tool compile -S main.go
...
go.string."hello" SRODATA dupok size=5
 0x0000 68 65 6c 6c 6f     hello
...

不過這只能表明編譯期間存在的字符串會被直接分配到只讀的內存空間并且這段內存不會被更改,但是在運行時我們其實還是可以將這段內存拷貝到其他的堆或者棧上,同時將變量的類型修改成 []byte 在修改之后再通過類型轉換變成 string ,不過如果想要直接修改 string 類型變量的內存空間,Go 語言是不支持這種操作的。

除了今天的主角字符串之外,另外的配角 byte 也還是需要簡單介紹一下的,byte 其實非常好理解,每一個 byte 就是 8 個 bit,相信稍微對編程有所了解的人應該都對這個概念一清二楚,而字節數組也沒什么值得介紹的,所以這里就直接跳過了。

字符串在 Go 語言中的接口其實非常簡單,每一個字符串在運行時都會使用如下的 StringHeader 結構體去表示,在運行時包的內部其實有一個私有的結構 stringHeader ,它有著完全相同的結構只是用于存儲數據的 Data 字段使用了 unsafe.Pointer 類型:

type StringHeader struct {
 Data uintptr
 Len int
}

為什么我們會說字符串其實是一個只讀類型的切片 呢,我們可以看一下切片在 Go 語言中的運行時表示:

type SliceHeader struct {
 Data uintptr
 Len int
 Cap int
}

這個表示切片的結構 SliceHeader 和字符串的結構 StringHeader 非常類似,與切片的結構相比,字符串少了一個表示容量的 Cap 字段,這是因為字符串作為只讀的類型,我們并不會直接向字符串直接追加元素改變其本身的內存空間,所有追加的操作都是通過拷貝來完成的。

字符串的解析一定是解析器在詞法分析 時就完成的,詞法分析階段會對源文件中的字符串進行切片和分組,將原有無意義的字符流轉換成 Token 序列,在 Go 語言中,有兩種字面量的方式可以聲明一個字符串,一種是使用雙引號,另一種是使用反引號:

str1 := "this is a string"
str2 := `this is another 
string`

使用雙引號聲明的字符串其實和其他語言中的字符串沒有太多的區別,它只能用于簡單、單行的字符串并且如果字符串內部出現雙引號時需要使用 \ 符號避免編譯器的解析錯誤,而反引號聲明的字符串就可以擺脫單行的限制,因為雙引號不再標記字符串的開始和結束,我們可以在字符串內部直接使用 " ,在遇到需要寫 JSON 或者其他數據格式的場景下非常方便。

兩種不同的聲明方式其實也意味著 Go 語言的編譯器需要在解析的階段能夠區分并且正確解析這兩種不同的字符串格式,解析字符串使用的 scanner 掃描器,它的主要作用就是將輸入的字符流轉換成 Token 流, stdString 方法就是它用來解析使用雙引號包裹的標準字符串:

func (s *scanner) stdString() {
 s.startLit()

 for {
 r := s.getr()
 if r == '"' {
 break
 }
 if r == '\\' {
 s.escape('"')
 continue
 }
 if r == '\n' {
 s.ungetr()
 s.error("newline in string")
 break
 }
 if r  0 {
 s.errh(s.line, s.col, "string not terminated")
 break
 }
 }

 s.nlsemi = true
 s.lit = string(s.stopLit())
 s.kind = StringLit
 s.tok = _Literal
}

從這個方法中我們其實能夠看出 Go 語言處理標準字符串的邏輯:

1.標準字符串使用雙引號表示開頭和結尾;

2. 標準字符串中需要使用反斜杠 \ 來 escape 雙引號;

3. 標準字符串中不能出現換行符號 \n ;

原始字符串解析的規則就非常簡單了,它會將非反引號的所有字符都劃分到當前字符串的范圍中,所以我們可以使用它來支持復雜的多行字符串字面量,例如 JSON 等數據格式。

func (s *scanner) rawString() {
 s.startLit()

 for {
 r := s.getr()
 if r == '`' {
 break
 }
 if r  0 {
 s.errh(s.line, s.col, "string not terminated")
 break
 }
 }

 s.nlsemi = true
 s.lit = string(s.stopLit())
 s.kind = StringLit
 s.tok = _Literal
}

無論是標準字符串還是原始字符串最終都會被標記成 StringLit 類型的 Token 并傳遞到編譯的下一個階段 —語法分析,在語法分析的階段,與字符串相關的表達式都會使用如下的方法 BasicLit 對字符串進行處理:

func (p *noder) basicLit(lit *syntax.BasicLit) Val {
 switch s := lit.Value; lit.Kind {
 case syntax.StringLit:
 if len(s) > 0  s[0] == '`' {
 s = strings.Replace(s, "\r", "", -1)
 }
 u, _ := strconv.Unquote(s)
 return Val{U: u}
 }
}

無論是 import 語句中包的路徑、結構體中的字段標簽還是表達式中的字符串都會使用這個方法將原生字符串中最后的換行符刪除并對字符串 Token 進行 Unquote,也就是去掉字符串兩遍的引號等無關干擾,還原其本來的面目。

strconv.Unquote 方法處理了很多邊界條件導致整個函數非常復雜,不僅包括各種不同引號的處理,還包括 UTF-8 等編碼的相關問題,所以在這里也就不展開介紹了,感興趣的讀者可以在 Go 語言中找到 strconv.Unquote 方法詳細了解它的執行過程。

介紹完了字符串的的解析過程,這一節就會繼續介紹字符串的常見操作了,我們在這里要介紹的字符串常見操作包括字符串的拼接和類型轉換,字符串相關功能的主要是通過 Go 語言運行時或者 strings 包完成的,我們會重點介紹運行時字符串的操作,想要了解 strings 包的讀者可以閱讀相關的代碼,這里就不多介紹了。

Go 語言中拼接字符串會使用 + 符號,當我們使用這個符號對字符串進行拼接時,編譯器會在類型檢查階段將 OADD 節點轉換成 OADDSTR 類型的節點,隨后在 SSA 中間代碼生成的階段調用 addstr 函數:

func walkexpr(n *Node, init *Nodes) *Node {
 switch n.Op {
 // ...
 case OADDSTR:
 n = addstr(n, init)
 }
}

addstr 函數就是幫助我們在編譯期間選擇合適的函數對字符串進行拼接,如果需要拼接的字符串小于或者等于 5 個,那么就會直接調用 concatstring{2,3,4,5} 等一系列函數,如果超過 5 個就會直接選擇 concatstrings 傳入一個數組切片。

func addstr(n *Node, init *Nodes) *Node {
 c := n.List.Len()

 buf := nodnil()
 args := []*Node{buf}
 for _, n2 := range n.List.Slice() {
 args = append(args, conv(n2, types.Types[TSTRING]))
 }

 var fn string
 if c = 5 {
 fn = fmt.Sprintf("concatstring%d", c)
 } else {
 fn = "concatstrings"

 t := types.NewSlice(types.Types[TSTRING])
 slice := nod(OCOMPLIT, nil, typenod(t))
 slice.List.Set(args[1:])
 args = []*Node{buf, slice}
 }

 cat := syslook(fn)
 r := nod(OCALL, cat, nil)
 r.List.Set(args)
 // ...

 return r
}

其實無論使用 concatstring{2,3,4,5} 中的哪一個,最終都會調用 concatstrings ,在這個函數中我們會先對傳入的切片參數進行遍歷,首先會過濾空字符串并獲取拼接后字符串的長度。

func concatstrings(buf *tmpBuf, a []string) string {
 idx := 0
 l := 0
 count := 0
 for i, x := range a {
 n := len(x)
 if n == 0 {
 continue
 }
 if l+n  l {
 throw("string concatenation too long")
 }
 l += n
 count++
 idx = i
 }
 if count == 0 {
 return ""
 }

 if count == 1  (buf != nil || !stringDataOnStack(a[idx])) {
 return a[idx]
 }
 s, b := rawstringtmp(buf, l)
 for _, x := range a {
 copy(b, x)
 b = b[len(x):]
 }
 return s
}

如果非空字符串的數量為 1 并且當前的字符串不在棧上或者沒有逃逸出調用堆棧,那么就可以直接返回該字符串,不需要進行任何的耗時操作。

但是在正常情況下,原始的多個字符串都會被調用 copy 將所有的字符串拷貝到目標字符串所在的內存空間中,新的字符串其實就是一片新的內存空間,與原來的字符串沒有任何關聯。

類型轉換

當我們使用 Go 語言做一些 JSON 等數據格式的解析和序列化時,可能經常會將這些變量在字符串和字節數組之間來回轉換,類型之間轉換的開銷并沒有想象的這么小,我們經常會看到 slicebytetostring 等函數出現在火焰圖中,這個函數就是將字節數組轉換成字符串所使用的函數,也就是一個類似 string(bytes) 的操作會在編譯期間轉換成 slicebytetostring 的函數調用,這個函數在函數體中首先會處理兩種比較常見的情況,也就是字節長度為 0 或者 1 的情況:

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
 stringStructOf(str).len = len(b)
 memmove(p, (*(*slice)(unsafe.Pointer(b))).array, uintptr(len(b)))
 return
}

處理過后會根據傳入的緩沖區大小決定是否需要為新的字符串分配一片內存空間, stringStructOf 會將傳入的字符串指針轉換成 stringStruct 結構體指針,然后設置結構體持有的指針 str 和字符串長度 len ,最后通過 memmove 將原字節數組中的字節全部復制到新的內存空間中。

從字符串到字節數組的轉換使用的就是 stringtoslicebyte 函數了,這個函數的實現非常簡單:

func stringtoslicebyte(buf *tmpBuf, s string) []byte {
 var b []byte
 if buf != nil  len(s) = len(buf) {
 *buf = tmpBuf{}
 b = buf[:len(s)]
 } else {
 b = rawbyteslice(len(s))
 }
 copy(b, s)
 return b
}

它會使用傳入的緩沖區或者根據字符串的長度調用 rawbyteslice 創建一個新的字節切片, copy 關鍵字就會將字符串中的內容拷貝到新的字節數組中。

字符串和字節數組中的內容雖然一樣,但是字符串的內容是只讀的,我們不能通過下標或者其他形式改變其內存存儲的數據,而字節切片中的內容都是可以讀寫的,所以無論是從哪種類型轉換到另一種都需要對其中的內容進行拷貝,內存拷貝的性能損耗會隨著字符串數組和字節長度的增長而增長,所以在做這種類型轉換時一定要注意性能上的問題。

字符串是 Go 語言中相對來說比較簡單的一種數據結構,作為只讀的數據類型,我們無法改變其本身的結構,但是在做類型轉換的操作時一定要注意性能上的瓶頸,遇到需要極致性能的場景一定要盡量減少不同類型的轉換,避免額外的開銷。

相關文章

本作品采用進行許可。 轉載時請注明原文鏈接,圖片在使用時請保留圖片中的全部內容,可適當縮放并在引用處附上圖片所在的文章鏈接,圖片使用 Sketch 進行繪制。如果對本文的內容有疑問,請在下面的評論系統中留言,謝謝。

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

您可能感興趣的文章:
  • golang 中獲取字符串個數的方法
  • Golang 中整數轉字符串的方法
  • Golang 統計字符串字數的方法示例
  • Golang中文字符串截取函數實現原理
  • Golang實現字符串倒序的幾種解決方案
  • Golang 語言高效使用字符串的方法

標簽:常州 貴州 保定 曲靖 吐魯番 東營 德宏 許昌

巨人網絡通訊聲明:本文標題《簡單談談Golang中的字符串與字節數組》,本文關鍵詞  簡單,談談,Golang,中的,字符串,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《簡單談談Golang中的字符串與字節數組》相關的同類信息!
  • 本頁收集關于簡單談談Golang中的字符串與字節數組的相關信息資訊供網民參考!
  • 推薦文章
    校园春色亚洲色图_亚洲视频分类_中文字幕精品一区二区精品_麻豆一区区三区四区产品精品蜜桃
    欧美军同video69gay| 黄一区二区三区| 99精品国产视频| 国产精品污网站| 成人av网站在线| 亚洲精品国产精华液| 欧美自拍偷拍午夜视频| 日日摸夜夜添夜夜添亚洲女人| 在线观看区一区二| 日韩高清在线一区| 久久久亚洲午夜电影| 成人少妇影院yyyy| 一区二区三区中文免费| 欧美三级电影网| 蜜臀av性久久久久蜜臀aⅴ | 亚洲一区在线看| 欧美高清精品3d| 国产美女一区二区| 自拍av一区二区三区| 欧美日韩高清不卡| 久久 天天综合| 亚洲欧洲韩国日本视频| 欧美日韩精品高清| 狠狠色丁香九九婷婷综合五月| 国产欧美精品一区| 欧美性受xxxx| 韩国欧美一区二区| 亚洲在线视频网站| 欧美成人一区二区三区片免费 | 国产精品私房写真福利视频| 一本久道久久综合中文字幕| 免费看黄色91| 亚洲天堂免费在线观看视频| 91精品国产91久久综合桃花| 国产精品夜夜爽| 三级欧美在线一区| 国产精品视频免费看| 777a∨成人精品桃花网| www.色精品| 免费看欧美女人艹b| 亚洲另类春色校园小说| 26uuu亚洲| 欧美日本乱大交xxxxx| 成人丝袜视频网| 精品一区二区三区在线观看| 一区二区三区在线观看动漫| 精品国产乱码久久久久久老虎| 99国产精品99久久久久久| 国产自产v一区二区三区c| 亚洲午夜一区二区| 欧美韩国日本一区| 精品国产一二三| 精品视频在线免费看| 99re热这里只有精品视频| 国产精品亚洲а∨天堂免在线| 亚州成人在线电影| 一区二区三区四区乱视频| 国产欧美一区二区精品性色超碰| 91精品国产综合久久福利| 一本高清dvd不卡在线观看| 国产精品夜夜嗨| 久久国产精品免费| 青青草97国产精品免费观看 | 狠狠色狠狠色综合系列| 日韩电影一区二区三区| 亚洲综合免费观看高清完整版在线| 久久久久久久久久久99999| 日韩欧美中文字幕制服| 欧美日本视频在线| 日韩午夜在线影院| 国产欧美一区二区精品忘忧草| 国产高清亚洲一区| 国产一区二区视频在线播放| 老司机免费视频一区二区三区| 亚洲成a人在线观看| 亚洲男帅同性gay1069| 国产精品久久久久久久裸模| 国产欧美一区二区在线| 国产日韩欧美综合在线| 中文字幕久久午夜不卡| 国产性做久久久久久| 国产亚洲午夜高清国产拍精品| 精品av久久707| 亚洲精品在线免费观看视频| 日韩欧美在线影院| 日韩欧美中文字幕精品| 久久综合给合久久狠狠狠97色69| 精品日韩一区二区| 国产三级欧美三级日产三级99| 久久综合狠狠综合久久激情| 久久综合狠狠综合| 国产精品久久久久永久免费观看 | 欧洲一区二区三区免费视频| 色一区在线观看| 欧美日韩色一区| 在线综合视频播放| 日韩精品一区二区三区四区| 久久网站最新地址| 国产农村妇女精品| 亚洲理论在线观看| 日韩国产欧美在线播放| 国模一区二区三区白浆| 国产宾馆实践打屁股91| 菠萝蜜视频在线观看一区| 91视频91自| 91精品在线一区二区| 欧美一级高清大全免费观看| 26uuu亚洲综合色| 综合在线观看色| 午夜精品久久久久久久99樱桃| 蜜臀精品久久久久久蜜臀| 国产91在线看| 欧美午夜一区二区| 精品成人一区二区三区四区| 国产精品视频免费| 午夜在线成人av| 国产在线精品免费| 色94色欧美sute亚洲线路一ni| 欧美狂野另类xxxxoooo| 久久久蜜桃精品| 亚洲一区二区精品视频| 国产一区二区三区免费看| 91美女在线视频| 久久午夜免费电影| 亚洲午夜激情网页| 岛国一区二区三区| 欧美一区二区黄色| 亚洲欧美激情一区二区| 久久国产精品99精品国产 | 中文字幕一区av| 日韩精品免费专区| av一二三不卡影片| 日韩欧美一级精品久久| 亚洲久草在线视频| 国产一区二区三区四| 欧美日韩在线不卡| 成人欧美一区二区三区小说| 青青草一区二区三区| 99亚偷拍自图区亚洲| 欧美精品一区二区三区四区 | 91精品婷婷国产综合久久| 国产精品福利一区二区| 久久黄色级2电影| 欧美日韩在线播放一区| 一色屋精品亚洲香蕉网站| 黄色资源网久久资源365| 欧美丰满少妇xxxxx高潮对白| 国产精品欧美一级免费| 婷婷开心激情综合| 欧美在线一区二区三区| 欧美国产精品一区二区| 久草中文综合在线| 欧美日韩一区二区三区四区| 国产精品成人一区二区艾草| 精品一区二区日韩| 欧美mv日韩mv| 日本在线播放一区二区三区| 日本久久电影网| 亚洲视频综合在线| 99视频精品全部免费在线| 久久精品亚洲精品国产欧美kt∨| 麻豆成人免费电影| 欧美一激情一区二区三区| 亚洲第一二三四区| 欧美视频自拍偷拍| 一区二区三区在线观看网站| 97成人超碰视| 《视频一区视频二区| 不卡一卡二卡三乱码免费网站| 国产午夜精品久久久久久免费视| 国产一区二区三区四区五区入口 | 国产精品色眯眯| 国产成人亚洲综合色影视| 精品电影一区二区三区| 玖玖九九国产精品| 久久亚洲精品国产精品紫薇| 国产一区二区三区四区五区美女| 精品国产乱码久久久久久老虎| 韩国精品免费视频| 久久久精品tv| 从欧美一区二区三区| 中文字幕永久在线不卡| 99麻豆久久久国产精品免费优播| 国产欧美日韩激情| 色综合天天做天天爱| 蜜桃一区二区三区在线| 日韩一区二区三区电影在线观看| 首页国产丝袜综合| 欧美成人乱码一区二区三区| 国产一区二区三区免费播放| 国产精品嫩草久久久久| 色婷婷国产精品| 蜜臀av一级做a爰片久久| 久久久久国产一区二区三区四区| 成人一级片在线观看| 一区二区三区在线观看视频| 制服丝袜av成人在线看| 国产一区二区调教| 成人免费小视频| 欧美片网站yy|