談談Go語言的字串設計

2021-09-30 13:31:31 字數 1704 閱讀 6391

『問題描述』

那天有使用者向我反饋在使用 gojieba 的過程中發現記憶體洩露的bug。 具體現象就是這個測試** test.go 跑著跑著記憶體一直增長。 剛開始以為是**裡面的c語言部分記憶體沒有正確釋放導致的, 查了很久一直沒有找到問題所在。

最後發現這個bug非常白痴,是因為 c.cstring 使用不當導致的。 在呼叫了 c.cstring 之後需要手動釋放記憶體。 這個bug非常白痴,但是卻反映了我之前對go語言string理解不徹底的隱患。 才導致在我第一眼看到 c.cstring 的時候, 就下意識的認為這個函式肯定沒有動態申請新的記憶體, 和 c++ string::c_str() 一樣,復用了記憶體。 所以也就肯定不需要手動釋放。 當然這些只是『我以為』。

『問題深扒』

c語言和go語言本是同根生嘛, 所以go語言在設計的時候就通過cgo對c語言呼叫支援得很好。 而go語言和c語言之間的資料轉換就是通過 c.cstring (go->c), c.gostring(c->go) 來進行的。

先談談 c.gostring ,很顯然當使用 c.gostring 的時候, 會複製c語言的*char指標指向的字串的內容拷貝到go語言的string管理的記憶體空間。 go語言的string管理的記憶體空間有gc管理,不需要使用者主動釋放記憶體。 也就是不需要管它。

而 c.cstring 將 go語言 string 轉換成 c語言字串的時候。 我們就要談談為什麼它不會像 c++ 的 string::c_str() 一樣只是單純的共用記憶體了。

本質原因在於對於 go 來說, string 和 c語言最大的不同是: 在c語言中,字串是以 』\0』 結尾。 其實我認為這個本身是一種歷史遺留問題。

『c語言的字串主要有兩種儲存方式可選』

比如乙個 「hello」 的字串。 我們在記憶體中表示可以有兩種選擇:

第一種:

第二種: 1

2 3 4

typedefstructstring;

c語言預設的字串選擇了第一種方式, 我認為主要原因在於當年c語言發明的時候是記憶體和稀缺的時代。 第一種方式比第二種方式顯然更省記憶體。

但是隨著時代的發展,記憶體越來越便宜。記憶體已經越來越不是程式開發的瓶頸。 第二種方式越來越成為字串設計的首選。 比如在nginx之類的著名開源專案中,也是採用了第二種方式對字串進行儲存。

而第二種方式更受青睞的主要原因我認為有兩點:

『1. 更好的記憶體共享』

比如有乙個字串s1 = 「hello world」 , 而有兩種字串s2 s3 分別是 s1 的子串:"hello", 「world」 . 當我們使用第二種方式儲存字串的時候, 我們對於s2 s3就直接復用 s1的記憶體即可。 無需動態分配和釋放,這樣的場景在協議解析,比如http包頭的場景下特別常用。

而假設我們使用第一種方式儲存字串的話, 那麼 s1 = 「hello world\0」, s2 = 「hello\0」, 雖然 s2 是 s1 的子串,但是因為 「\0」 結尾符的存在, s2 就無法復用 s1 的記憶體,而是需要新申請一段新的記憶體。 這也是為什麼在go語言中, c.cstring 函式返回的記憶體肯定是一段新的記憶體, 也就不得不要求呼叫者手動釋放。

『2. 效能更高,獲取長度不再是strlen這種o(n)時間複雜度的函式』

這點就比較顯而易見了。

Go語言遍歷字串

位元組陣列方式遍歷 func main 執行 104101 108108 11144 228184 150231 149140 可以看出,這個字串的長度為13,直觀來看它的長度不應該有那麼長,這是因為每個中文字元在 utf 8 中佔三個位元組,而不是乙個位元組。另一種方式是以 unicode 字元遍...

Go語言中的字元和字串

在go語言中,沒有字元型別,字元型別是rune型別,rune是int32的別稱。下面用乙個簡單的程式來演示字元型別 package main import fmt reflect func main 程式輸出 我 的型別為 t int32 25105 二進位制為 110001000010001 r的...

go語言解析json字串

go語言解析json資料主要使用go語言自帶的json轉換庫encoding json 1.json.marshal函式 該函式的作用是主要物件轉換為json字串資料,函式原型如下 func marsha v inte ce byte,error 也就是說,這個函式接收任意型別的資料v,並將其轉換為...