亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文詳解Go語(yǔ)言中切片的底層原理

 更新時(shí)間:2023年06月28日 15:19:49   作者:7small7  
在Go語(yǔ)言中,切片作為一種引用類(lèi)型數(shù)據(jù),相對(duì)數(shù)組而言是一種動(dòng)態(tài)長(zhǎng)度的數(shù)據(jù)類(lèi)型,使用的場(chǎng)景也是非常多,所以本文主要來(lái)和大家聊聊切片的底層原理,需要的可以參考一下

大家好,我是二條,在上一篇我們學(xué)習(xí)了輕松理解Go中的內(nèi)存逃逸問(wèn)題,今天接著我們學(xué)習(xí)Go中切片的相關(guān)知識(shí)。本文不會(huì)單獨(dú)去講解切片的基礎(chǔ)語(yǔ)法,只會(huì)對(duì)切片的底層和在開(kāi)發(fā)中需要注意的事項(xiàng)作分析。

在Go語(yǔ)言中,切片作為一種引用類(lèi)型數(shù)據(jù),相對(duì)數(shù)組而言是一種動(dòng)態(tài)長(zhǎng)度的數(shù)據(jù)類(lèi)型,使用的場(chǎng)景也是非常多。但在使用切片的過(guò)程中,也有許多需要注意的事項(xiàng)。例如切片函數(shù)傳值、切片動(dòng)態(tài)擴(kuò)容、切片對(duì)底層數(shù)組的引用問(wèn)題等等。今天分享的主題,就是圍繞切片進(jìn)行。

切片的函數(shù)傳值

切片作為一種引用數(shù)據(jù)類(lèi)型,在作為函數(shù)傳值時(shí),如果函數(shù)內(nèi)部對(duì)切片做了修改,會(huì)影響到原切片上。

package?main
import?"fmt"
func?main()?{
?sl1?:=?make([]int,?10)
?for?i?:=?0;?i?<?10;?i++?{
?}
?fmt.Println("切片sl1的值是",?sl1)
?change(sl1)
?fmt.Println("切片sl2的值是",?sl1)
}
func?change(sl?[]int)?{
?sl[0]?=?100
?fmt.Println("形參sl切片的值是",?sl)
}

打印上述代碼:

切片sl1的值是 [1 2 3 4 5 6 7 8 9 10]
形參sl切片的值是 [100 2 3 4 5 6 7 8 9 10]
切片sl2的值是 [100 2 3 4 5 6 7 8 9 10]

通過(guò)上面的結(jié)果,不難看出來(lái),在函數(shù)change()中修改了切片,原切片的小標(biāo)0的值也發(fā)生了改變。這是因?yàn)榍衅且环N引用類(lèi)型數(shù)據(jù),在傳遞到函數(shù)change()時(shí),使用的都是相同的底層數(shù)組(切片底層本質(zhì)仍是一個(gè)數(shù)組)。因此,底層數(shù)組的值改變了,就會(huì)影響到其他指向該數(shù)組的切片上。

針對(duì)上述的問(wèn)題,有什么解決方案,使得傳遞切片,不會(huì)影響原切片的值呢?可以采用切片復(fù)制的方式,重新創(chuàng)建一個(gè)新的切片當(dāng)做函數(shù)的參數(shù)進(jìn)行傳遞。

package?main
import?"fmt"
func?main()?{
?sl1?:=?make([]int,?10)
?for?i?:=?0;?i?<?10;?i++?{
??sl1[i]?=?i?+?1
?}
?fmt.Println("切片sl1的值是",?sl1)
?//?創(chuàng)建一個(gè)新的切片,當(dāng)做參數(shù)傳遞。
?sl2?:=?make([]int,?10)
?copy(sl2,?sl1)
?change(sl2)
?fmt.Println("切片sl2的值是",?sl1)
}
func?change(sl?[]int)?{
?sl[0]?=?100
?fmt.Println("形參sl切片的值是",?sl)
}

打印上述代碼:

切片sl1的值是 [1 2 3 4 5 6 7 8 9 10]
形參sl切片的值是 [100 2 3 4 5 6 7 8 9 10]
切片sl2的值是 [1 2 3 4 5 6 7 8 9 10]

通過(guò)上述運(yùn)行結(jié)果,在change函數(shù)中,對(duì)切片下標(biāo)為0做了值修改,對(duì)切片sl1的值沒(méi)有影響。

切片動(dòng)態(tài)擴(kuò)容機(jī)制

在Go中,切片是一種動(dòng)態(tài)長(zhǎng)度引用數(shù)據(jù)類(lèi)型。當(dāng)切片的容量不足以容納新增加的元素時(shí),底層會(huì)實(shí)現(xiàn)自動(dòng)擴(kuò)容用來(lái)存儲(chǔ)新添加的元素。先查看下面的一段實(shí)例代碼,證明切片存在動(dòng)態(tài)擴(kuò)容。

package?main
import?"fmt"
func?main()?{
?var?sl1?[]int
?fmt.Println("切片sl1的長(zhǎng)度是",?len(sl1),?",容量是",?cap(sl1))
?for?i?:=?0;?i?<?10;?i++?{
??sl1?=?append(sl1,?i)
??fmt.Println("切片sl1的長(zhǎng)度是",?len(sl1),?",容量是",?cap(sl1))
?}
}

打印上述代碼:

切片sl1的長(zhǎng)度是 0 ,容量是 0
切片sl1的長(zhǎng)度是 1 ,容量是 1
切片sl1的長(zhǎng)度是 2 ,容量是 2
切片sl1的長(zhǎng)度是 3 ,容量是 4
切片sl1的長(zhǎng)度是 4 ,容量是 4
切片sl1的長(zhǎng)度是 5 ,容量是 8
切片sl1的長(zhǎng)度是 6 ,容量是 8
切片sl1的長(zhǎng)度是 7 ,容量是 8
切片sl1的長(zhǎng)度是 8 ,容量是 8
切片sl1的長(zhǎng)度是 9 ,容量是 16
切片sl1的長(zhǎng)度是 10 ,容量是 16

可以看出,切片的長(zhǎng)度是隨著for操作,依次遞增。但切片的容量就不是依次遞增,從明面上看,有點(diǎn)像以2的倍數(shù)在增加。具體增加的規(guī)律是怎么樣的呢?

要弄明白Go中的切片是如何實(shí)現(xiàn)擴(kuò)容的,這就需要關(guān)注一下Go的版本。

在Go的1.18版本以前,是按照如下的規(guī)則來(lái)進(jìn)行擴(kuò)容:

1、如果原有切片的長(zhǎng)度小于 1024,那么新的切片容量會(huì)直接擴(kuò)展為原來(lái)的 2 倍。

2、如果原有切片的長(zhǎng)度大于等于 1024,那么新的切片容量會(huì)擴(kuò)展為原來(lái)的 1.25 倍,這一過(guò)程可能需要執(zhí)行多次才能達(dá)到期望的容量。

3、如果切片屬于第一種情況(長(zhǎng)度小于 1024)并且需要擴(kuò)容的容量小于 1024 字節(jié),那么新的切片容量會(huì)直接增加到原來(lái)的長(zhǎng)度加上需要擴(kuò)容的容量(新容量=原容量+擴(kuò)容容量)。

從Go的1.18版本開(kāi)始,是按照如下的規(guī)則進(jìn)行擴(kuò)容:

1、當(dāng)原slice容量(oldcap)小于256的時(shí)候,新slice(newcap)容量為原來(lái)的2倍。

2、原slice容量超過(guò)256,新slice容量newcap = oldcap + (oldcap+3*256) / 4。

使用上面的代碼,將循環(huán)的值調(diào)到非常大,例如10w,甚至更大,你會(huì)發(fā)現(xiàn)切片的容量和長(zhǎng)度始終是比較趨近,而不是差距很大。

例如我將循環(huán)設(shè)置到100w,這里就只打印最后幾行結(jié)果,不進(jìn)行全部打印。

package?main
import?"fmt"
func?main()?{
?var?sl1?[]int
?fmt.Println("切片sl1的長(zhǎng)度是",?len(sl1),?",容量是",?cap(sl1))
?for?i?:=?0;?i?<?1000000;?i++?{
??sl1?=?append(sl1,?i)
??fmt.Println("切片sl1的長(zhǎng)度是",?len(sl1),?",容量是",?cap(sl1))
?}
}

打印上述代碼結(jié)果為:

.................
切片sl1的長(zhǎng)度是 999990 ,容量是 1055744
切片sl1的長(zhǎng)度是 999991 ,容量是 1055744
切片sl1的長(zhǎng)度是 999992 ,容量是 1055744
切片sl1的長(zhǎng)度是 999993 ,容量是 1055744
切片sl1的長(zhǎng)度是 999994 ,容量是 1055744
切片sl1的長(zhǎng)度是 999995 ,容量是 1055744
切片sl1的長(zhǎng)度是 999996 ,容量是 1055744
切片sl1的長(zhǎng)度是 999997 ,容量是 1055744
切片sl1的長(zhǎng)度是 999998 ,容量是 1055744
切片sl1的長(zhǎng)度是 999999 ,容量是 1055744
切片sl1的長(zhǎng)度是 1000000 ,容量是 1055744

上面講到的不同版本之間的規(guī)律,這個(gè)規(guī)律是怎么來(lái)的,我們可以直接源代碼。

首先看1.18版本開(kāi)始的底層代碼,你需要找到Go的源碼文件,路徑為runtime/slice.go,該文件中有一個(gè)名為growslice()函數(shù)。這個(gè)函數(shù)的代碼很長(zhǎng),我們重點(diǎn)關(guān)注下述代碼,其他的代碼除了做一些邏輯處理,還處理了內(nèi)存對(duì)齊問(wèn)題,關(guān)于內(nèi)存對(duì)齊就不在本篇提及。

//?type切片期望的類(lèi)型,old舊切片,cap新切片期望最小的容量
func?growslice(et?*_type,?old?slice,?cap?int)?slice?{
??newcap?:=?old.cap//?老切片容量
??doublecap?:=?newcap?+?newcap//?老切片容量的兩倍
??if?cap?>?doublecap?{//?期望最小的容量?>?老切片的兩倍(新切片的容量?=?2?*?老切片的容量)
????newcap?=?cap
??}?else?{
????const?threshold?=?256
????if?old.cap?<?threshold?{
??????newcap?=?doublecap
????}?else?{
??????for?0?<?newcap?&&?newcap?<?cap?{
????????//?在2倍增長(zhǎng)以及1.25倍之間尋找一種相對(duì)平衡的規(guī)則
????????newcap?+=?(newcap?+?3*threshold)?/?4
??????}
??????if?newcap?<=?0?{
????????newcap?=?cap
??????}
????}
??}
}

接著來(lái)看1.18版本之前的源代碼,可以直接通過(guò)GitHub上進(jìn)行查看。1.16GitHub源碼地址。

//?type切片期望的類(lèi)型,old舊切片,cap新切片期望最小的容量
func?growslice(et?*_type,?old?slice,?cap?int)?slice?{
???newcap?:=?old.cap
?doublecap?:=?newcap?+?newcap
?if?cap?>?doublecap?{//?需要兩倍擴(kuò)容時(shí),則直接擴(kuò)容為兩倍
??newcap?=?cap
?}?else?{
??if?old.cap?<?1024?{//?小于1024,直接擴(kuò)容為2倍
???newcap?=?doublecap
??}?else?{
???//?原?slice?容量超過(guò)?1024,新?slice?容量變成原來(lái)的1.25倍
???for?0?<?newcap?&&?newcap?<?cap?{
????newcap?+=?newcap?/?4
???}
???if?newcap?<=?0?{
????newcap?=?cap
???}
??}
?}
}

通過(guò)上述的代碼,已經(jīng)總結(jié)出切片擴(kuò)容的規(guī)律。如果你在實(shí)際的案例中,并非按照總結(jié)的規(guī)律進(jìn)行擴(kuò)容,這是因?yàn)榍衅瑪U(kuò)容之后還考慮了內(nèi)存對(duì)齊問(wèn)題,也就是上述growslic()函數(shù)剩余部分。

切片操作對(duì)數(shù)組的影響

在Go中,切片和數(shù)組有一些共性,也有一些不同之處。

相同之處:

1、切片和數(shù)組在定義時(shí),需要指定內(nèi)部的元素類(lèi)型,一旦定義元素類(lèi)型,就只能存儲(chǔ)該類(lèi)型的元素。

2、切片雖然是單獨(dú)的一種類(lèi)型,底層仍然是一個(gè)數(shù)組,在Go源碼中,有這樣一段定義,通過(guò)閱讀這段代碼,可以總結(jié)出切片底層是一個(gè)struct數(shù)據(jù)結(jié)構(gòu)。

type?slice?struct?{
?array?unsafe.Pointer?#?指向底層數(shù)組的指針
?len???int?#?切片的長(zhǎng)度,也就是說(shuō)當(dāng)前切片中的元素個(gè)數(shù)
?cap???int?#?切片的容量,也就是說(shuō)切片最大能夠存儲(chǔ)多少個(gè)元素
}

不同之處:

1、切片和數(shù)組最大的不同之處,在于切片的長(zhǎng)度和容量是動(dòng)態(tài)的,可以根據(jù)實(shí)際情況實(shí)現(xiàn)動(dòng)態(tài)擴(kuò)容,而數(shù)組是固定長(zhǎng)度,一經(jīng)定義長(zhǎng)度,存儲(chǔ)的元素就不能超過(guò)定義時(shí)的長(zhǎng)度。

下面有這樣一種場(chǎng)景,需要特別注意。

從一個(gè)切片中生產(chǎn)新的切片,使用截取實(shí)現(xiàn)。

func?clipSliceBySlice()?{
?s?:=?make([]int,?1000000)
?start?:=?time.Now()
?_?=?s[0:500000]
?elapsed?:=?time.Since(start)
?fmt.Printf("Time?taken?to?generate?slice?from?slice:?%s\n",?elapsed)
}

從一個(gè)切片中生成新的切片,使用copy()函數(shù)實(shí)現(xiàn)。

func?clipSliceByCopy()?{
?s?:=?make([]int,?1000000)
?start?:=?time.Now()
?s2?:=?make([]int,?500000)
?copy(s2,?s[0:500000])
?elapsed?:=?time.Since(start)
?fmt.Printf("Time?taken?to?copy?slice?using?copy()?function:?%s\n",?elapsed)
}

這兩段代碼,都是從一個(gè)切片中生成一個(gè)新的切片,但誰(shuí)的性能效果更好呢?

1、第一種方式,生成新切片,底層仍然與原切片共用一個(gè)底層數(shù)組。在生成切片時(shí),效率會(huì)更高一些。但存在一個(gè)問(wèn)題,如果原切片和新切片對(duì)自身的元素做了修改,底層數(shù)組也會(huì)隨著改變,這樣會(huì)導(dǎo)致另外一個(gè)切片也跟著受影響。這種方式雖然效率更高,但是共用同一個(gè)底層數(shù)組,會(huì)存在數(shù)據(jù)安全問(wèn)題。

2、第二種方式,生成新切片,使用的是copy()函數(shù)實(shí)現(xiàn),會(huì)發(fā)生一個(gè)內(nèi)存拷貝。這樣新切片就是存儲(chǔ)在新的內(nèi)存中,其底層的數(shù)組和原切片底層的數(shù)組,不在是共享。不管是老切片還是新切片內(nèi)部元素發(fā)生變化,都只會(huì)影響到自身。這種方式雖然消耗的內(nèi)存更大,但數(shù)據(jù)更加安全。

使用歸納

在實(shí)際的開(kāi)發(fā)過(guò)程中,我們一般使用切片的場(chǎng)景要比數(shù)組多,這是為什么呢?

1、動(dòng)態(tài)擴(kuò)展:切片可以動(dòng)態(tài)擴(kuò)展或縮減,而數(shù)組的長(zhǎng)度是固定的。使用切片可以更方便地處理不確定長(zhǎng)度的數(shù)據(jù)集。

2、內(nèi)存效率:切片的底層實(shí)現(xiàn)是數(shù)組,但是通過(guò)切片可以對(duì)底層的數(shù)組進(jìn)行引用,避免了復(fù)制底層數(shù)據(jù)的開(kāi)銷(xiāo)。因此,使用切片可以更高效地處理大量數(shù)據(jù)。

3、零值初始化:切片有一個(gè)默認(rèn)值為0的長(zhǎng)度和容量,這使得初始化切片更加方便。

4、內(nèi)置函數(shù):切片有許多內(nèi)置函數(shù),如append()、copy()等,這些函數(shù)可以更方便地操作切片。

本文總結(jié)

根據(jù)上面的幾個(gè)小問(wèn)題進(jìn)行演示,我們?cè)谌粘i_(kāi)發(fā)中,使用切片重點(diǎn)可以關(guān)注在動(dòng)態(tài)擴(kuò)容引用傳值上面,這也是經(jīng)常出現(xiàn)問(wèn)題的點(diǎn)。下面細(xì)分幾點(diǎn)進(jìn)行歸納:

1、由于切片是引用類(lèi)型,因此容易出現(xiàn)多個(gè)變量引用同一個(gè)底層數(shù)組,導(dǎo)致內(nèi)存泄露和意外修改數(shù)據(jù)的情況。

2、當(dāng)切片長(zhǎng)度超過(guò)底層數(shù)組容量時(shí),可以導(dǎo)致切片重新分配內(nèi)存,這可能會(huì)帶來(lái)性能問(wèn)題。

3、在使用切片時(shí)沒(méi)有正確計(jì)算長(zhǎng)度和容量,也可能導(dǎo)致意料之外的結(jié)果。

4、切片常常被用作函數(shù)參數(shù),由于其引用類(lèi)型的特性,可能會(huì)導(dǎo)致函數(shù)內(nèi)對(duì)切片數(shù)據(jù)的修改影響到外部變量。

5、如果切片的底層數(shù)組被修改,可能會(huì)對(duì)所有引用該底層數(shù)組的切片數(shù)據(jù)造成影響。

到此這篇關(guān)于一文詳解Go語(yǔ)言中切片的底層原理的文章就介紹到這了,更多相關(guān)Go切片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言如何使用分布式鎖解決并發(fā)問(wèn)題

    Go語(yǔ)言如何使用分布式鎖解決并發(fā)問(wèn)題

    這篇文章主要為大家詳細(xì)介紹了Go 語(yǔ)言生態(tài)中基于 Redis 實(shí)現(xiàn)的分布式鎖庫(kù) redsync,并探討其使用方法和實(shí)現(xiàn)原理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-03-03
  • go?defer避坑指南之拆解延遲語(yǔ)句

    go?defer避坑指南之拆解延遲語(yǔ)句

    這篇文章主要為大家詳細(xì)介紹了go?defer避坑指南之如何拆解延遲語(yǔ)句,掌握正確使用方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • go mod tidy拉取依賴(lài)包bug問(wèn)題及解決

    go mod tidy拉取依賴(lài)包bug問(wèn)題及解決

    這篇文章主要介紹了go mod tidy拉取依賴(lài)包bug問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Go語(yǔ)言實(shí)戰(zhàn)學(xué)習(xí)之流程控制詳解

    Go語(yǔ)言實(shí)戰(zhàn)學(xué)習(xí)之流程控制詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的流程控制,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助?,需要的朋友可以參考下
    2022-08-08
  • Go基于雪花算法生成隨機(jī)id

    Go基于雪花算法生成隨機(jī)id

    雪花算法是twitter開(kāi)源的由64位整數(shù)組成的分布式ID,本文主要介紹了Go基于雪花算法生成隨機(jī)id,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • Go語(yǔ)言中的字符串拼接方法詳情

    Go語(yǔ)言中的字符串拼接方法詳情

    本文介紹Go語(yǔ)言中的string類(lèi)型、strings包和bytes.Buffer類(lèi)型,介紹幾種字符串拼接方法的相關(guān)資料,需要的朋友可以參考一下,希望對(duì)你有所幫助
    2021-10-10
  • go get 和 go install 對(duì)比介紹

    go get 和 go install 對(duì)比介紹

    go install和go get都是Go語(yǔ)言的工具命令,但它們之間有一些區(qū)別。go get:用于從遠(yuǎn)程代碼存儲(chǔ)庫(kù)(如 GitHub)中下載或更新Go代碼包。go install:用于編譯并安裝 Go 代碼包,本文go get和go install對(duì)比介紹的非常詳細(xì),需要的朋友可以參考一下
    2023-04-04
  • Golang服務(wù)中context超時(shí)處理的方法詳解

    Golang服務(wù)中context超時(shí)處理的方法詳解

    在Go語(yǔ)言中,Context是一個(gè)非常重要的概念,它存在于一個(gè)完整的業(yè)務(wù)生命周期內(nèi),Context類(lèi)型是一個(gè)接口類(lèi)型,在實(shí)際應(yīng)用中,我們可以使用Context包來(lái)傳遞請(qǐng)求的元數(shù)據(jù),本文將給大家介紹Golang服務(wù)中context超時(shí)處理的方法和超時(shí)原因,需要的朋友可以參考下
    2023-05-05
  • goLang引入自定義包的方法

    goLang引入自定義包的方法

    今天小編就為大家分享一篇goLang引入自定義包的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-06-06
  • 在Go中實(shí)現(xiàn)高效可靠的鏈路追蹤系統(tǒng)

    在Go中實(shí)現(xiàn)高效可靠的鏈路追蹤系統(tǒng)

    在當(dāng)今互聯(lián)網(wǎng)應(yīng)用的架構(gòu)中,分布式系統(tǒng)已經(jīng)成為主流,分布式系統(tǒng)的優(yōu)勢(shì)在于能夠提供高可用性、高并發(fā)性和可擴(kuò)展性,本文將介紹鏈路追蹤的概念和原理,并重點(diǎn)介紹如何在Golang中實(shí)現(xiàn)高效可靠的鏈路追蹤系統(tǒng),需要的朋友可以參考下
    2023-10-10

最新評(píng)論