Go中的binary包實(shí)現(xiàn)了簡(jiǎn)單的數(shù)字與字節(jié)序列的轉(zhuǎn)換以及變長(zhǎng)值的編解碼
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、成都微信小程序、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了資陽(yáng)免費(fèi)建站歡迎大家使用!
package main
import ( "fmt" "bytes" "encoding/binary" ) func main(){ n := 0x12345678 bytesBuffer := bytes.NewBuffer([]byte{}) //BigEndian 大端順序存儲(chǔ) LittleEndian小端順序存儲(chǔ) binary.Write(bytesBuffer, binary.BigEndian, int32(n)) data:=bytesBuffer.Bytes() fmt.Printf("[0]: %#x addr:%#x\n",data[0],data[0]) fmt.Printf("[0]: %#x addr:%#x\n",data[1],data[1]) fmt.Printf("[0]: %#x addr:%#x\n",data[2],data[2]) fmt.Printf("[0]: %#x addr:%#x\n",data[3],data[3]) }
輸出
[0]: 0x12 addr:0xc042010248 [1]: 0x34 addr:0xc042010249 [2]: 0x56 addr:0xc04201024a [3]: 0x78 addr:0xc04201024b
也可以使用下面的方式
n := 0x12345678 var data []byte = make([]byte,4) //操作的都是無(wú)符號(hào)整型 binary.BigEndian.PutUint32(data,uint32(n))
可以使用下面的方式判斷當(dāng)前系統(tǒng)的字節(jié)序類型
const INT_SIZE int = int(unsafe.Sizeof(0))
//判斷我們系統(tǒng)中的字節(jié)序類型 func systemEdian() { var i int = 0x1 bs := (*[INT_SIZE]byte)(unsafe.Pointer(i)) if bs[0] == 0 { fmt.Println("system edian is little endian") } else { fmt.Println("system edian is big endian") } }
在開(kāi)始之前,希望你計(jì)算一下 Part1 共占用的大小是多少呢?
輸出結(jié)果:
這么一算, Part1 這一個(gè)結(jié)構(gòu)體的占用內(nèi)存大小為 1+4+1+8+1 = 15 個(gè)字節(jié)。相信有的小伙伴是這么算的,看上去也沒(méi)什么毛病
真實(shí)情況是怎么樣的呢?我們實(shí)際調(diào)用看看,如下:
輸出結(jié)果:
最終輸出為占用 32 個(gè)字節(jié)。這與前面所預(yù)期的結(jié)果完全不一樣。這充分地說(shuō)明了先前的計(jì)算方式是錯(cuò)誤的。為什么呢?
在這里要提到 “內(nèi)存對(duì)齊” 這一概念,才能夠用正確的姿勢(shì)去計(jì)算,接下來(lái)我們?cè)敿?xì)的講講它是什么
有的小伙伴可能會(huì)認(rèn)為內(nèi)存讀取,就是一個(gè)簡(jiǎn)單的字節(jié)數(shù)組擺放
上圖表示一個(gè)坑一個(gè)蘿卜的內(nèi)存讀取方式。但實(shí)際上 CPU 并不會(huì)以一個(gè)一個(gè)字節(jié)去讀取和寫(xiě)入內(nèi)存。相反 CPU 讀取內(nèi)存是 一塊一塊讀取 的,塊的大小可以為 2、4、6、8、16 字節(jié)等大小。塊大小我們稱其為 內(nèi)存訪問(wèn)粒度 。如下圖:
在樣例中,假設(shè)訪問(wèn)粒度為 4。 CPU 是以每 4 個(gè)字節(jié)大小的訪問(wèn)粒度去讀取和寫(xiě)入內(nèi)存的。這才是正確的姿勢(shì)
另外作為一個(gè)工程師,你也很有必要學(xué)習(xí)這塊知識(shí)點(diǎn)哦 :)
在上圖中,假設(shè)從 Index 1 開(kāi)始讀取,將會(huì)出現(xiàn)很崩潰的問(wèn)題。因?yàn)樗膬?nèi)存訪問(wèn)邊界是不對(duì)齊的。因此 CPU 會(huì)做一些額外的處理工作。如下:
從上述流程可得出,不做 “內(nèi)存對(duì)齊” 是一件有點(diǎn) "麻煩" 的事。因?yàn)樗鼤?huì)增加許多耗費(fèi)時(shí)間的動(dòng)作
而假設(shè)做了內(nèi)存對(duì)齊,從 Index 0 開(kāi)始讀取 4 個(gè)字節(jié),只需要讀取一次,也不需要額外的運(yùn)算。這顯然高效很多,是標(biāo)準(zhǔn)的 空間換時(shí)間 做法
在不同平臺(tái)上的編譯器都有自己默認(rèn)的 “對(duì)齊系數(shù)”,可通過(guò)預(yù)編譯命令 #pragma pack(n) 進(jìn)行變更,n 就是代指 “對(duì)齊系數(shù)”。一般來(lái)講,我們常用的平臺(tái)的系數(shù)如下:
另外要注意,不同硬件平臺(tái)占用的大小和對(duì)齊值都可能是不一樣的。因此本文的值不是唯一的,調(diào)試的時(shí)候需按本機(jī)的實(shí)際情況考慮
輸出結(jié)果:
在 Go 中可以調(diào)用 unsafe.Alignof 來(lái)返回相應(yīng)類型的對(duì)齊系數(shù)。通過(guò)觀察輸出結(jié)果,可得知基本都是 2^n ,最大也不會(huì)超過(guò) 8。這是因?yàn)槲沂痔幔?4 位)編譯器默認(rèn)對(duì)齊系數(shù)是 8,因此最大值不會(huì)超過(guò)這個(gè)數(shù)
在上小節(jié)中,提到了結(jié)構(gòu)體中的成員變量要做字節(jié)對(duì)齊。那么想當(dāng)然身為最終結(jié)果的結(jié)構(gòu)體,也是需要做字節(jié)對(duì)齊的
接下來(lái)我們一起分析一下,“它” 到底經(jīng)歷了些什么,影響了 “預(yù)期” 結(jié)果
在每個(gè)成員變量進(jìn)行對(duì)齊后,根據(jù)規(guī)則 2,整個(gè)結(jié)構(gòu)體本身也要進(jìn)行字節(jié)對(duì)齊,因?yàn)榭砂l(fā)現(xiàn)它可能并不是 2^n ,不是偶數(shù)倍。顯然不符合對(duì)齊的規(guī)則
根據(jù)規(guī)則 2,可得出對(duì)齊值為 8?,F(xiàn)在的偏移量為 25,不是 8 的整倍數(shù)。因此確定偏移量為 32。對(duì)結(jié)構(gòu)體進(jìn)行對(duì)齊
Part1 內(nèi)存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
通過(guò)本節(jié)的分析,可得知先前的 “推算” 為什么錯(cuò)誤?
是因?yàn)閷?shí)際內(nèi)存管理并非 “一個(gè)蘿卜一個(gè)坑” 的思想。而是一塊一塊。通過(guò)空間換時(shí)間(效率)的思想來(lái)完成這塊讀取、寫(xiě)入。另外也需要兼顧不同平臺(tái)的內(nèi)存操作情況
在上一小節(jié),可得知根據(jù)成員變量的類型不同,其結(jié)構(gòu)體的內(nèi)存會(huì)產(chǎn)生對(duì)齊等動(dòng)作。那假設(shè)字段順序不同,會(huì)不會(huì)有什么變化呢?我們一起來(lái)試試吧 :-)
輸出結(jié)果:
通過(guò)結(jié)果可以驚喜的發(fā)現(xiàn),只是 “簡(jiǎn)單” 對(duì)成員變量的字段順序進(jìn)行改變,就改變了結(jié)構(gòu)體占用大小
接下來(lái)我們一起剖析一下 Part2 ,看看它的內(nèi)部到底和上一位之間有什么區(qū)別,才導(dǎo)致了這樣的結(jié)果?
符合規(guī)則 2,不需要額外對(duì)齊
Part2 內(nèi)存布局:ecax|bbbb|dddd|dddd
通過(guò)對(duì)比 Part1 和 Part2 的內(nèi)存布局,你會(huì)發(fā)現(xiàn)兩者有很大的不同。如下:
仔細(xì)一看, Part1 存在許多 Padding。顯然它占據(jù)了不少空間,那么 Padding 是怎么出現(xiàn)的呢?
通過(guò)本文的介紹,可得知是由于不同類型導(dǎo)致需要進(jìn)行字節(jié)對(duì)齊,以此保證內(nèi)存的訪問(wèn)邊界
那么也不難理解,為什么 調(diào)整結(jié)構(gòu)體內(nèi)成員變量的字段順序 就能達(dá)到縮小結(jié)構(gòu)體占用大小的疑問(wèn)了,是因?yàn)榍擅畹販p少了 Padding 的存在。讓它們更 “緊湊” 了。這一點(diǎn)對(duì)于加深 Go 的內(nèi)存布局印象和大對(duì)象的優(yōu)化非常有幫
熟悉C語(yǔ)言的同學(xué)都知道,查看一個(gè)變量的地址在處理指針的相關(guān)問(wèn)題的時(shí)候直觀重要,在C中直接取地址符 即可。那么在Go語(yǔ)言中如何查看一個(gè)變量的地址,我們使用unsafe.Pointer() 函數(shù)來(lái)查看一個(gè)變量的內(nèi)存地址。
舉例:
type Vertex struct {
X, Y float64
}
func (v Vertex) sqrt() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
func (v Vertex) scale(f float64) { //帶 號(hào) 和不帶*號(hào)的區(qū)別 可以從內(nèi)存地址來(lái)看出
fmt.printf("=======", unsafe.Pointer(v))//v 本身就是指針 存儲(chǔ)的就是地址 不用取地址
v.X = x.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
fmt.printf("=======", unsafe.Pointer(v))
v.scale(10)
fmt.Println(v.sqrt())
}
//帶 號(hào) 打印的結(jié)果 ====== -%!(EXTRA unsafe.Pointer=0xc00006e070)======%!(EXTRA unsafe.Pointer=0xc00006e070) 相同
//不帶 號(hào) 打印的結(jié)果 ======%!(EXTRA unsafe.Pointer=0xc000094060)======%!(EXTRA unsafe.Pointer=0xc000094090) 不同
去掉*號(hào) 在scale()方法中要對(duì) v 進(jìn)行取地址操作
本文題目:Go語(yǔ)言中的unsafe go語(yǔ)言中的引用類型
網(wǎng)站網(wǎng)址:http://jinyejixie.com/article48/ddisdep.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、服務(wù)器托管、標(biāo)簽優(yōu)化、響應(yīng)式網(wǎng)站、定制網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)