網站首頁 編程語言 正文
1、 string的定義
Golang中的string的定義在reflect包下的value.go中,定義如下:
StringHeader 是字符串的運行時表示,其中包含了兩個字段,分別是指向數據數組的指針和數組的長度。
// StringHeader is the runtime representation of a string. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type StringHeader struct { Data uintptr Len int }
2、string不可變
Golang中的字符串是不可變的,不能通過索引下標的方式修改字符串中的數據:
運行代碼,可以看到編譯器報錯,string是不可變的
但是能不能進行一些騷操作來改變元素的值呢?
package main import ( "fmt" "reflect" "unsafe" ) func main() { a := "hello,world" b := a[6:] bptr := (*reflect.StringHeader) (unsafe.Pointer(&b)) fmt.Println(a) fmt.Println(b) *(*byte)(unsafe.Pointer(bptr.Data)) = '.' fmt.Println(a) fmt.Println(b) } // 運行結果 hello,world world unexpected fault address 0x49d7e3 fatal error: fault [signal 0xc0000005 code=0x1 addr=0x49d7e3 pc=0x4779fa] goroutine 1 [running]: runtime.throw(0x49c948, 0x5) C:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc0000dbe90 sp=0xc0000dbe60 pc=0x405fd9 runtime.sigpanic() C:/Program Files/Go/src/runtime/signal_windows.go:245 +0x2d6 fp=0xc0000dbee8 sp=0xc0000dbe90 pc=0x4189f6 main.main() F:/go_workspace/src/code/string_test/main.go:20 +0x13a fp=0xc0000dbf88 sp=0xc0000dbee8 pc=0x4779fa runtime.main() C:/Program Files/Go/src/runtime/proc.go:225 +0x256 fp=0xc0000dbfe0 sp=0xc0000dbf88 pc=0x4087f6 runtime.goexit() C:/Program Files/Go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc0000dbfe8 sp=0xc0000dbfe0 pc=0x435da1 Process finished with the exit code 2
在上面的代碼中,因為在go語言中不能進行指針的加減運算,因此取切片,讓b的Data指針指向’,'所在的位置。然后把"hello,world"中的逗號改為點,但是發現還是不行,程序直接崩潰了。看來go語言中的指針得到了大大的限制,設計者并不想讓程序員過度使用指針來寫出一些不安全的代碼。
3、使用string給另一個string賦值
Golang中的字符串的賦值并不是拷貝底層的字符串數組,而是數組指針和長度字段的拷貝。例如:當我們定義了一個字符串 a := “hello,world” 然后定義了 b := a 底層所做的操作只是創建了兩個StringHeader的結構體,它們的Data字段都指向同一段數據,如下圖:
我們可以利用代碼來證實這一點:
package main import ( "fmt" "reflect" "unsafe" ) func main() { a := "hello,world" b := a fmt.Println(a) fmt.Println(b) aptr := (*reflect.StringHeader) (unsafe.Pointer(&a)) bptr := (*reflect.StringHeader) (unsafe.Pointer(&b)) fmt.Println("a ptr:", unsafe.Pointer(aptr.Data)) fmt.Println("b ptr:", unsafe.Pointer(bptr.Data)) } // 運行結果 hello, world hello, world a ptr: 0x6bdb76 b ptr: 0x6bdb76
在上面的代碼中,將a和b轉換為StringHeader類型的指針,然后分別打印出,a和b的Data指針的值,發現是相同的
那么如果對a做切片賦值給b呢?
func main() { a := "hello,world" b := a[6:] fmt.Println(a) fmt.Println(b) aptr := (*reflect.StringHeader) (unsafe.Pointer(&a)) bptr := (*reflect.StringHeader) (unsafe.Pointer(&b)) fmt.Println("a ptr:", unsafe.Pointer(aptr.Data)) fmt.Println("b ptr:", unsafe.Pointer(bptr.Data)) } // 運行結果 hello,world world a ptr: 0xd4d849 b ptr: 0xd4d84f
0xd4d849 - 0xd4d84f = 0x000006
顯然,也沒有分配新的數組并拷貝數據,而是將原字符數組的指針的偏移賦給了b的StringHeader的Data
4、string重新賦值
如果對一個已經賦值的字符串重新賦值,也不會修改原內存空間,而是申請了新的內存空間,對其賦值,并指向新的內存空間。如下圖:
也可以使用代碼來證實一下:
package main import ( "fmt" "reflect" "unsafe" ) func main() { a := "hello,world" aptr := (*reflect.StringHeader) (unsafe.Pointer(&a)) fmt.Println("a ptr:", unsafe.Pointer(aptr.Data)) fmt.Println("a len", aptr.Len) a = "hello,golang" newAPtr := (*reflect.StringHeader) (unsafe.Pointer(&a)) fmt.Println("b ptr:", unsafe.Pointer(newAPtr.Data)) fmt.Println("b len:", newAPtr.Len) } // 運行結果 a ptr: 0x3ed7f4 a len 11 b ptr: 0x3edb2c b len: 12
補充:字符串拼接
字符串可以很方便的拼接,像下面這樣:
str := "Str1" + "Str2" + "Str3"
即便有非常多的字符串需要拼接,性能上也有比較好的保證,因為新字符串的內存空間是一次分配完成的,所以性能消耗主要在拷貝數據上。
一個拼接語句的字符串編譯時都會被存放到一個切片中,拼接過程需要遍歷兩次切片,第一次遍歷獲取總的字符串長度,據此申請內存,第二次遍歷會把字符串逐個拷貝過去。
字符串拼接偽代碼如下:
func concatstrings(a []string) string { // 字符串拼接 length := 0 // 拼接后總的字符串長度 for _, str := range a { length += length(str) } s, b := rawstring(length) // 生成指定大小的字符串,返回一個string和切片,二者共享內存空間 for _, str := range a { copy(b, str) // string無法修改,只能通過切片修改 b = b[len(str):] } return s }
因為string是無法直接修改的,所以這里使用rawstring()方法初始化一個指定大小的string,同時返回一個切片,二者共享同一塊內存空間,后面向切片中拷貝數據,也就間接修改了string。
rawstring()源代碼如下:
func rawstring(size int) (s string, b []byte) { // 生成一個新的string,返回的string和切片共享相同的空間 p := mallocgc(uintptr(size), nil, false) stringStructOf(&s).str = p stringStructOf(&s).len = size *(*slice)(unsafe.Pointer(&b)) = slice{p, size, size} return }
總結
原文鏈接:https://blog.csdn.net/Peerless__/article/details/121209732
相關推薦
- 2022-09-19 C/C++最短路徑算法之迪杰斯特拉Dijkstra的實現詳解_C 語言
- 2022-11-15 Python+?Flask實現Mock?Server詳情_python
- 2022-10-30 系統應用根據Uri授予權限方法詳解_Android
- 2023-03-12 Pandas中根據條件替換列中的值的四種方式_python
- 2022-08-08 基于Redis緩存數據常見的三種問題及解決_Redis
- 2022-07-30 jQuery?UI組件介紹_jquery
- 2022-05-23 Redis監控工具RedisInsight安裝與使用_Redis
- 2022-09-08 python?字典生成樹狀圖的實例_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支