GO 中 string 的實現原理

這是我參與更文挑戰的第 16 天,活動詳情查看: 更文挑戰mysql

GO 中 string 的實現原理

上次咱們分享的內容咱回顧一下c++

  • 分享了ETCD的簡單單點部署,ETCD 使用到的包安裝,以及會遇到的問題
  • ETCD 的設置 和 獲取KEY
  • ETCD 的WATCH 監控 KEY的簡化
  • ETCD 的租約 和保活機制
  • ETCD 的分佈式鎖的簡單實現

要是對GO 對 ETCD 的編碼還有點興趣的話, 歡迎查看文章 GO 中 ETCD 的編碼案例分享sql

字符串是什麼?

他是一種基本類型(string 類型),而且是一個不可改變的UTF-8字符序列shell

在衆多編程語言裏面,相信都少不了字符串類型編程

字符串,顧名思義就是一串字符,咱們要明白,字符也是分爲中文字符和英文字符的數組

例如咱們在 C/C++ 中 , 一個英文字符佔 1 個字節,一箇中文字符有的佔 2 個字節,有的佔3個字節markdown

用到 mysql 的中文字符,有的佔 4 個字節數據結構

回過來看 GO 裏面的字符串,字符也是根據英文和中文不同,一個字符所佔用的字節數也是不同的,大致分爲以下 2app

  • 英文的字符,按照ASCII 碼來算,佔用 1 個字節
  • 其餘的字符,包括中文字符在內的,根據不一樣字符,佔用字節數是 2 -- 4個字節

字符串的數據結構是啥樣的?

說到字符串的數據結構,咱們先來看看 GO 裏面的字符串,是在哪一個包裏面編程語言

不難發現,咱們隨便在 GOLANG 裏面 定義個string 變量,就可以知道 string 類型是在哪一個包裏面,例如

var name  string
複製代碼

GO 裏面的字符串對應的包是 builtin

// 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
複製代碼
  • 字符串這個類型,是全部8-bits 字符串的集合,一般但不必定表示utf -8編碼的文本

  • 字符串能夠爲空,但不能爲 nil ,此處的字符串爲空是 ""

  • 字符串類型的值是不可變的

另外,找到 string 在 GO 裏面對應的源碼文件中src/runtime/string.go , 有這麼一個結構體,只提供給包內使用,咱們能夠看到string的數據結構 stringStruct 是這個樣子的

type stringStruct struct {
    str unsafe.Pointer
    len int
}
複製代碼

整個結構體,就 2 個成員,**string **類型是否是很簡單呢

  • str

是對應到字符串的首地址

  • len

這個就是不難理解,是字符串的長度

那麼,在建立一個字符串變量的時候,stringStruct 是在哪裏使用到的呢?

咱們看看 GO string.go 文件中的源碼

//go:nosplit
func gostringnocopy(str *byte) string {
   ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}  // 構建成 stringStruct
   s := *(*string)(unsafe.Pointer(&ss))  // 強轉成 string
   return s
}
複製代碼
//go:nosplit
func findnull(s *byte) int {
   if s == nil {
      return 0
   }

   // Avoid IndexByteString on Plan 9 because it uses SSE instructions
   // on x86 machines, and those are classified as floating point instructions,
   // which are illegal in a note handler.
   if GOOS == "plan9" {
      p := (*[maxAlloc/2 - 1]byte)(unsafe.Pointer(s))
      l := 0
      for p[l] != 0 {
         l++
      }
      return l
   }

   // pageSize is the unit we scan at a time looking for NULL.
   // It must be the minimum page size for any architecture Go
   // runs on. It's okay (just a minor performance loss) if the
   // actual system page size is larger than this value.
   const pageSize = 4096

   offset := 0
   ptr := unsafe.Pointer(s)
   // IndexByteString uses wide reads, so we need to be careful
   // with page boundaries. Call IndexByteString on
   // [ptr, endOfPage) interval.
   safeLen := int(pageSize - uintptr(ptr)%pageSize)

   for {
      t := *(*string)(unsafe.Pointer(&stringStruct{ptr, safeLen}))
      // Check one page at a time.
      if i := bytealg.IndexByteString(t, 0); i != -1 {
         return offset + i
      }
      // Move to next page
      ptr = unsafe.Pointer(uintptr(ptr) + uintptr(safeLen))
      offset += safeLen
      safeLen = pageSize
   }
}
複製代碼

簡單分爲 2 步:

  • 先將字符數據構建程 stringStruct
  • 再經過 gostringnocopy 函數 轉換成 string

字符串中的數據爲何不能被修改呢?

從上述官方說明中,咱們能夠看到,字符串類型的值是不可變的

但是這是爲啥呢?

咱們之前在寫C/C++的時候,爲啥能夠開闢空間存放多個字符,而且還能夠修改其中的某些字符呢?

但是在 C/C++裏面的字面量也是不能夠改變的

GO 裏面的 string 類型,是否是也和 字面量同樣的呢?咱們來看看吧

字符串類型,自己也是擁有對應的內存空間的,那麼修改string類型的值應該是要支持的。

但是,XDM 在 Go 的實現中,string 類型是不包含內存空間,只有一個內存的指針,這裏就有點想C/C++裏面的案例:

char * str = "XMTONG"
複製代碼

上述的 str是絕對不能作修改的,str只是做爲可讀,不能寫的

在GO 裏面的字符串,就與上述相似

這樣作的好處是 string 變得很是輕量,能夠很方便的進行傳遞而不用擔憂內存拷貝(這也避免了內存帶來的諸多問題)

GO 中的 string類型通常是指向字符串字面量

字符串字面量存儲位置是在虛擬內存分區的只讀段上面,而不是堆或棧上

所以,GO 的 string 類型不可修改的

但是咱們想想,要是在GO 裏面字符串全都是隻讀的,那麼咱們如何動態修改一些咱們須要改變的字符呢,這豈不是缺陷了

別慌

GO 裏面還有byte數組,[]byte

這裏順帶說一下

上述 char * str = "XMTONG"

  • 字符串長度,就是字符的個數,爲 6
  • 計算str所佔字節數(C/C++中是經過 sizeof() 來計算的)的話,那就是 7 ,由於尾巴後面還有一個'\0'

計算機中有這樣的對應關係,簡單提一下:

1 Bytes = 8 bit

1 K = 1024 Bytes

1 M = 1024 K

1 G = 1024 M

爲何有了字符串 還要 []byte?

緣由正如上述咱們說到的,若是全是一些只讀的字面量,那麼咱們編碼的時候就沒得玩了

另外,也是根據使用字符串的場景緣由,單是string沒法知足全部的場景,所以得有一個咱們能夠修改裏面值的 []byte 來彌補一下

說到這裏,咱們應該就知道了,string[]byte都是能夠表示字符串,沒毛病 ,

不過,他們畢竟對應不一樣的數據結構,使用方式也有必定的區別,GO 提供的對應方法也是不盡相同

咱們來看看什麼場景用 string 類型, 啥場景 使用 []byte 類型

使用到 string 類型的 地方:

  • 須要對字符串進行比較的時候,使用string 類型很是方便,直接使用操做符進行比較便可
  • string 類型 類型,爲空的時候是 "",他不能和nil作比較,所以,不用到nil的時候,也可使用 string 類型

使用到 []byte 類型的 地方:

  • 須要修改字符串中字符的應用場景,使用**[]byte 類型**就至關靈活了,用起來很香
  • []byte 類型 爲空的話,會是返回 nil ,須要使用到 nil 的時候,就可使用他
  • []byte 類型 自己就能夠按照切片的方式來玩,所以須要操做切片的時候,也能夠用他

就上述場景來看,好像使用 []byte 更加實在和靈活,爲啥還要用 string

緣由以下:

  • string 類型看起來直觀,用起來簡單
  • []byte,byte 數組,咱們能夠知道,裏面都是一個字節一個字節的,這個會比較多的用在底層,對操做字節比較關注的時候

字符串 和 []byte 如何互相轉換?

看到這裏,分別瞭解了 string 類型, 和 []byte 類型的應用場景

毋庸置疑,咱們編碼過程當中,確定少不了對他們作相互轉換,咱們來看看在 GO ,裏面如何使用

字符串轉 []byte

package main

import (
   "fmt"
)


func main(){
    var str string

    str = "XMTONG"

    strByte := []byte(str)
    for _,v :=range strByte{
        fmt.Printf("%x ",v)
    }
}
複製代碼

代碼輸出爲:

58 4d 54 4f 4e 47
複製代碼

上述代碼轉成 []byte 以後是一個字節,一個字節的

將每個字節的值用十六進制打印出來,咱們能夠看到,XMTONG 對應 584d544f4e47

[]byte 轉字符串

[]byte轉字符串在GO 裏面那就更簡單了

func main(){
   name := []byte("XMTONG")
   fmt.Println(string(name))
}
複製代碼

GO 中 字符串都會涉及到哪些函數?

不管什麼語言,對於字符串大概涉及以下幾種操做,如有誤差,還請指正:

  • 計算字符串長度
  • 拼接
  • 切割
  • 找到字串進行替換,找到字符串的具體位置和出現的次數
  • 統計字符串
  • 字符串進制轉換

具體的函數使用方法也比較簡單,推薦你們感興趣的能夠直接看go 的開發文檔,須要的時候去查一下便可。

GO 的標準開發文檔,在搜索引擎裏面仍是比較容易搜索到的

img

總結

  • 分享了字符串具體是啥
  • GO 中字符串的特性,爲何不能被修改
  • 字符串 GO 源碼是如何構建的
  • 字符串 和 []byte 的由來和應用場景
  • 字符串與 []byte 相互轉換
  • 順帶提了GO 的標準開發文檔,你們能夠用起來哦

歡迎點贊,關注,收藏

朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力

好了,本次就到這裏,下一次 GO 中 slice 的實現原理分享

技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

相關文章
相關標籤/搜索