上次我們分享的內容我們回顧一下
- 分享了ETCD的簡單單點部署,ETCD 使用到的包安裝,以及會遇到的問題
- ETCD 的設定 和 獲取KEY
- ETCD 的WATCH 監控 KEY的簡化
- ETCD 的租約 和保活機制
- ETCD 的分散式鎖的簡單實現
要是對GO 對 ETCD 的編碼還有點興趣的話, 歡迎檢視文章 GO 中 ETCD 的編碼案例分享
字串是什麼?
他是一種基本型別(string 型別
),並且是一個不可改變的UTF-8字元序列
在眾多程式語言裡面,相信都少不了字串型別
字串,顧名思義就是一串字元,我們要明白,字元也是分為中文字元和英文字元的
例如我們在 C/C++
中 , 一個英文字元佔 1 個位元組,一箇中文字元有的佔 2 個位元組,有的佔3個位元組
用到 mysql
的中文字元,有的佔 4 個位元組
回過來看 GO 裡面的字串,字元也是根據英文和中文不一樣,一個字元所佔用的位元組數也是不一樣的,大體分為如下 2 種
- 英文的字元,按照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 的標準開發文件,在搜尋引擎裡面還是比較容易搜尋到的
總結
- 分享了字串具體是啥
- GO 中字串的特性,為什麼不能被修改
- 字串 GO 原始碼是如何構建的
- 字串 和
[]byte
的由來和應用場景 - 字串與
[]byte
相互轉換 - 順帶提了GO 的標準開發文件,大家可以用起來哦
歡迎點贊,關注,收藏
朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力
好了,本次就到這裡,下一次 GO 中 slice 的實現原理分享
技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~
本作品採用《CC 協議》,轉載必須註明作者和本文連結