Go語(yǔ)言常見(jiàn)數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)詳解
channal
channal是go中的管道,主要用于協(xié)程之間的通信,他有點(diǎn)類似于阻塞隊(duì)列,使用管道可以簡(jiǎn)單的實(shí)現(xiàn)生產(chǎn)者消費(fèi)者,他會(huì)幫助我們自動(dòng)的去阻塞或者喚醒groutine
創(chuàng)建寫入和寫出
c := make(chan int, 5) c <- 1 v := <-c
channal中如果是nil的話讀取和寫入都不會(huì)觸發(fā)panic并且阻塞groutine如果是關(guān)閉的channal的話是可以讀取的但是不能寫如果寫的話就會(huì)觸發(fā)panic
channal的源碼在runtime/chan.go中下面是結(jié)構(gòu)體
type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters }
根據(jù)結(jié)構(gòu)體我們也不難發(fā)現(xiàn)它的數(shù)據(jù)結(jié)構(gòu)其實(shí)就是一個(gè)循環(huán)隊(duì)列,同時(shí)又有兩個(gè)recvq和sendq去代表寫操作和讀操作的阻塞隊(duì)列,qcount表示當(dāng)前使用大小也就是len(),dataqsiz表示容積大小也就是cap(),buf表示真實(shí)存儲(chǔ)的地址recvx和sendx分別表示隊(duì)列中的索引
寫入的流程圖
讀的流程圖
常用語(yǔ)法
單向管道
func test1(c chan<- int) {} // 只讀 func test2(c <-chan int) {} // 只寫
可以傳遞chan的讀或者寫這樣在方法中只能進(jìn)行一種操作
select多路監(jiān)聽
使用select可以監(jiān)聽多個(gè)channel的讀或者寫,select如果不寫default的話,會(huì)阻塞groutine有可以讀取到的才會(huì)喚醒,寫了default的話如果都不滿足條件就會(huì)執(zhí)行default中的代碼不會(huì)阻塞
func main() { c1 := make(chan string) c2 := make(chan string) go func() { time.Sleep(1 * time.Second) c1 <- "one" }() go func() { time.Sleep(1 * time.Second) c2 <- "two" }() for i := 0; i < 2; i++ { select { case msg1 := <-c1: fmt.Println(msg1) case msg2 := <-c2: fmt.Println(msg2) } } fmt.Println("執(zhí)行完畢") }
上述代碼的結(jié)果是運(yùn)行之后1秒輸出one和two然后輸出執(zhí)行完畢
for-range
可以使用for-range的方式去channel中不斷的讀取數(shù)據(jù)它會(huì)在沒(méi)有數(shù)據(jù)的時(shí)候阻塞線程
func main() { c1 := make(chan string) go func() { for { time.Sleep(1 * time.Second) c1 <- "one" } }() chanRange(c1) fmt.Println("執(zhí)行完畢") } func chanRange(c chan string) { for e := range c { fmt.Println(e) } }
上述代碼中使用了一個(gè)for-range在主線程去讀取數(shù)據(jù)會(huì)阻塞同時(shí)開啟一個(gè)groutine去每秒鐘寫入一個(gè)one上述代碼的結(jié)果就是每秒輸出one并且"執(zhí)行完畢"永遠(yuǎn)也不會(huì)執(zhí)行
slice
切片是我們平時(shí)最常用的,它又稱為動(dòng)態(tài)數(shù)組,它的底層是類似于java中的arrayList的會(huì)根據(jù)當(dāng)前容量自動(dòng)擴(kuò)容,這樣如果不理解一下它的原理有的問(wèn)題是不好發(fā)現(xiàn)的
func main() { s1 := []int{1, 2} s2 := s1 s2 = append(s2, 3) sliceRise(s1) sliceRise(s2) fmt.Println(s1, s2) } func sliceRise(s []int) { s = append(s, 0) for i := range s { s[i]++ } }
先看看這段代碼輸出結(jié)果是
這是為什么呢? 因?yàn)閟lice底層是使用一塊內(nèi)存地址的,只有當(dāng)容量不夠的時(shí)候才會(huì)創(chuàng)建新的地址,然后將之前的值復(fù)制上去
因?yàn)閟1是array它的空間就是2,s2=s1這樣s2和s1指向一塊地址,s2 = append(s2, 3)這個(gè)語(yǔ)句由于s2中的空間不夠因此需要擴(kuò)容2倍就導(dǎo)致s1和s2不是一塊地址了而是兩塊不同的,進(jìn)行增加操作的時(shí)候s1內(nèi)存不足新創(chuàng)建一塊導(dǎo)致增加的不是原本的數(shù),s2空間是4因此可以再裝一個(gè)數(shù)因此操作的時(shí)候還是操作原來(lái)的數(shù),這就導(dǎo)致s1中的數(shù)沒(méi)增加,s2中的數(shù)增加了
slice的源碼在runtime/slice.go中
type slice struct { array unsafe.Pointer len int cap int }
它的結(jié)構(gòu)體也是非常簡(jiǎn)單,就是一個(gè)數(shù)組和長(zhǎng)度容積大小
切片在使用的時(shí)候就是a[low:hight]這種格式表示前閉后開
a = a[:len(a) - 1] // 表示刪除最后一個(gè) a = a[1:] //表示刪除第一個(gè) a = [1,2,3,4] fmt.Println(a[1:3]) // 輸出結(jié)果為2,3
由于底層使用的是同一塊地址因此會(huì)出現(xiàn)下面的問(wèn)題
func main() { a := []int{1, 2, 3, 4, 5} b := a[1:3] b = append(b, 0) fmt.Println(a) }
我們看到這里并沒(méi)有改變數(shù)組a只是操作切片b就導(dǎo)致a中的數(shù)據(jù)發(fā)生改變,因?yàn)檫@里的b,len大小為2但是cap的大小為4就導(dǎo)致在原來(lái)的地址上面修改了
因此提供了一種設(shè)置大小的方式就是第三個(gè)參數(shù)
b := a[1:3:3]
b的聲明改成這樣就可以讓cap的大小為2保證數(shù)據(jù)安全
數(shù)組的直接比較
同時(shí)這里也聊一聊go中數(shù)組的語(yǔ)法糖:我們可以直接使用==去比較兩個(gè)數(shù)組
a := [2]int{1,2} b := [2]int{1,2} fmt.Printf(a == b) // true
如果數(shù)組中長(zhǎng)度和里面的數(shù)都是相等的話可以使用==去比較兩個(gè)數(shù)組是否相等
map
map是我們最常用的數(shù)據(jù)結(jié)構(gòu)之一,如果學(xué)習(xí)過(guò)別的語(yǔ)言例如java就對(duì)map的數(shù)據(jù)結(jié)構(gòu)比較熟悉,比如擾動(dòng)函數(shù)、hashcode、負(fù)載因子、哈希沖突等名詞都十分熟悉
在go中map的實(shí)現(xiàn)是通過(guò)bucket這種方式實(shí)現(xiàn)的,其實(shí)就是一個(gè)數(shù)組,計(jì)算需要存入的值然后找到數(shù)組下標(biāo),找到bucket中每一個(gè)下標(biāo)代碼的是一個(gè)8長(zhǎng)度的數(shù)組同一個(gè)hashcode可以存8個(gè)值,如果出現(xiàn)哈希沖突就在這8數(shù)組上進(jìn)行拉鏈法追加
可以在runtime/hashmap中去查找grow的代碼
// grow the map func (hmap *hmap) grow() { // ... // compute new size newBucketsCount := oldBucketsCount if !hmap.growing() { newBucketsCount = oldBucketsCount << 1 } // ... newBuckets := makeBucketArray(newBucketsCount) // ... for i := 0; i < oldBucketsCount; i++ { // ... for e := oldBuckets[i].first; e != nil; e = e.next { // ... // rehash the key to find the new bucket bucket := hashKey(newBuckets, e.key) // ... // insert the element into the new bucket newBuckets[bucket].insert(e) } } // ... }
擴(kuò)容過(guò)程就是創(chuàng)建一個(gè)長(zhǎng)度二倍的bucket然后對(duì)舊的每一個(gè)數(shù)進(jìn)行重新hash然后放入新的bucket中
在go中map是線程不安全的如果想要線程安全可以使用sync包中的map去實(shí)現(xiàn)
到此這篇關(guān)于Go語(yǔ)言常見(jiàn)數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Go數(shù)據(jù)結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法
今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08vscode上搭建go開發(fā)環(huán)境詳細(xì)完整過(guò)程
這篇文章主要給大家介紹了關(guān)于vscode上搭建go開發(fā)環(huán)境的詳細(xì)完整過(guò)程,Go語(yǔ)言或?qū)⒊蔀樾碌闹髁﹂_發(fā)語(yǔ)言,Go是google開發(fā)的一種靜態(tài)強(qiáng)類型、編譯型、并發(fā)型,并具有垃圾回收功能的編程語(yǔ)言,所以我們有必要學(xué)習(xí)并掌握它,需要的朋友可以參考下2023-10-10GoLang?channel關(guān)閉狀態(tài)相關(guān)操作詳解
Channel?和?goroutine?的結(jié)合是?Go?并發(fā)編程的大殺器。而?Channel?的實(shí)際應(yīng)用也經(jīng)常讓人眼前一亮,通過(guò)與?select,cancel,timer?等結(jié)合,它能實(shí)現(xiàn)各種各樣的功能。接下來(lái),我們就要介紹GoLang?channel關(guān)閉狀態(tài)相關(guān)操作2022-10-10詳解Go語(yǔ)言RESTful JSON API創(chuàng)建
這篇文章主要介紹了詳解Go語(yǔ)言RESTful JSON API創(chuàng)建,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05golang sync.Pool 指針數(shù)據(jù)覆蓋問(wèn)題解決
本文主要介紹了使用sync.Pool時(shí)遇到指針數(shù)據(jù)覆蓋的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03Golang實(shí)現(xiàn)組合模式和裝飾模式實(shí)例詳解
這篇文章主要介紹了Golang實(shí)現(xiàn)組合模式和裝飾模式,本文介紹組合模式和裝飾模式,golang實(shí)現(xiàn)兩種模式有共同之處,但在具體應(yīng)用場(chǎng)景有差異。通過(guò)對(duì)比兩個(gè)模式,可以加深理解,需要的朋友可以參考下2022-11-11