http://blog.csdn.net/vipally/article/details/52940119
相對于C語言,golang是類型安全的語言。但是安全的代價就是性能的妥協。
下面我們通過Golang中的“黑科技”來一窺Golang不想讓我們看到的“秘密”——string的底層數據。
通過reflect包,我們可以知道,在Golang底層,string和slice其實都是struct:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
type StringHeader struct {
Data uintptr
Len int
}
其中Data是一個指針,指向實際的數據地址,Len表示數據長度。
但是,在string和[]byte轉換過程中,Golang究竟悄悄幫我們做了什么,來達到安全的目的?
在Golang語言規范里面,string數據是禁止修改的,試圖通過&s[0], &b[0]取得string和slice數據指針地址也是不能通過編譯的。
下面,我們就通過Golang的“黑科技”來一窺Golang背后的“秘密”。
func StringBytes(s string) Bytes {
return *(*Bytes)(unsafe.Pointer(&s))
}
func BytesString(b []byte) String {
return *(*String)(unsafe.Pointer(&b))
}
func StringPointer(s string) unsafe.Pointer {
p := (*reflect.StringHeader)(unsafe.Pointer(&s))
return unsafe.Pointer(p.Data)
}
func BytesPointer(b []byte) unsafe.Pointer {
p := (*reflect.SliceHeader)(unsafe.Pointer(&b))
return unsafe.Pointer(p.Data)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
以上4個函數的神奇之處在于,通過unsafe.Pointer和reflect.XXXHeader取到了數據首地址,并實現了string和[]byte的直接轉換(這些操作在語言層面是禁止的)。
下面我們就通過這幾個“黑科技”來測試一下語言底層的秘密:
func TestPointer(t *testing.T) {
s := []string{
"",
"",
"hello",
"hello",
fmt.Sprintf(""),
fmt.Sprintf(""),
fmt.Sprintf("hello"),
fmt.Sprintf("hello"),
}
fmt.Println("String to bytes:")
for i, v := range s {
b := unsafe.StringBytes(v)
b2 := []byte(v)
if b.Writeable() {
b[0] = 'x'
}
fmt.Printf("%d\ts=%5s\tptr(v)=%-12v\tptr(StringBytes(v)=%-12v\tptr([]byte(v)=%-12v\n",
i, v, unsafe.StringPointer(v), b.Pointer(), unsafe.BytesPointer(b2))
}
b := [][]byte{
[]byte{},
[]byte{'h', 'e', 'l', 'l', 'o'},
}
fmt.Println("Bytes to string:")
for i, v := range b {
s1 := unsafe.BytesString(v)
s2 := string(v)
fmt.Printf("%d\ts=%5s\tptr(v)=%-12v\tptr(StringBytes(v)=%-12v\tptr(string(v)=%-12v\n",
i, s1, unsafe.BytesPointer(v), s1.Pointer(), unsafe.StringPointer(s2))
}
}
const N = 3000000
func Benchmark_Normal(b *testing.B) {
for i := 1; i < N; i++ {
s := fmt.Sprintf("12345678901234567890123456789012345678901234567890")
bb := []byte(s)
bb[0] = 'x'
s = string(bb)
s = s
}
}
func Benchmark_Direct(b *testing.B) {
for i := 1; i < N; i++ {
s := fmt.Sprintf("12345678901234567890123456789012345678901234567890")
bb := unsafe.StringBytes(s)
bb[0] = 'x'
s = s
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
結論如下:
1.string常量會在編譯期分配到只讀段,對應數據地址不可寫入,并且相同的string常量不會重復存儲。
2.fmt.Sprintf生成的字符串分配在堆上,對應數據地址可修改。
3.常量空字符串有數據地址,動態生成的字符串沒有設置數據地址
4.Golang string和[]byte轉換,會將數據復制到堆上,返回數據指向復制的數據
5.動態生成的字符串,即使內容一樣,數據也是在不同的空間
6.只有動態生成的string,數據可以被黑科技修改
8.string和[]byte通過復制轉換,性能損失接近4倍
我將測試代碼放在這里,歡迎參考:
https://github.com/vipally/gx/blob/master/unsafe/string_test.go
參考資料:
[1] Go語言黑魔法 http://studygolang.com/articles/2909
posted on 2017-05-04 10:32
思月行云 閱讀(578)
評論(0) 編輯 收藏 引用 所屬分類:
Golang