最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入了解Golang中的Slice底層實(shí)現(xiàn)

 更新時(shí)間:2023年02月26日 16:39:39   作者:nil  
本文主要為大家詳細(xì)介紹了Golang中slice的底層實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

1 Go數(shù)組

Go數(shù)組是值類型,數(shù)組定義的時(shí)候就需要指定大小,不同大小的數(shù)組是不同的類型,數(shù)組大小固定之后不可改變。數(shù)組的賦值和傳參都會(huì)復(fù)制一份。

func main() {
    arrayA := [2]int{100, 200}
    var arrayB [2]int

    arrayB = arrayA

    fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
    fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)

    testArray(arrayA)
}

func testArray(x [2]int) {
    fmt.Printf("func Array : %p , %v\n", &x, x)
}

結(jié)果:

arrayA : 0xc4200bebf0 , [100 200]
arrayB : 0xc4200bec00 , [100 200]
func Array : 0xc4200bec30 , [100 200]

可以看到,三個(gè)內(nèi)存地址都不同,這也就驗(yàn)證了 Go 中數(shù)組賦值和函數(shù)傳參都是值復(fù)制的。尤其是傳參的時(shí)候把數(shù)組復(fù)制一遍,當(dāng)數(shù)組非常大的時(shí)候會(huì)非常消耗內(nèi)存??梢钥紤]使用指針傳遞。

指針傳遞有個(gè)不好的地方,當(dāng)函數(shù)內(nèi)部改變了數(shù)組的內(nèi)容,則原數(shù)組的內(nèi)容也改變了。

因此一般參數(shù)傳遞的時(shí)候使用slice

2 切片的數(shù)據(jù)結(jié)構(gòu)

切片本身并不是動(dòng)態(tài)數(shù)組或者數(shù)組指針。它內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通過指針引用底層數(shù)組,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定的區(qū)域內(nèi)。切片本身是一個(gè)只讀對(duì)象,其工作機(jī)制類似數(shù)組指針的一種封裝。

切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類型。這個(gè)片段可以是整個(gè)數(shù)組,或者是由起始和終止索引標(biāo)識(shí)的一些項(xiàng)的子集。需要注意的是,終止索引標(biāo)識(shí)的項(xiàng)不包括在切片內(nèi)。切片提供了一個(gè)與指向數(shù)組的動(dòng)態(tài)窗口。

給定項(xiàng)的切片索引可能比相關(guān)數(shù)組的相同元素的索引小。和數(shù)組不同的是,切片的長(zhǎng)度可以在運(yùn)行時(shí)修改,最小為 0 最大為相關(guān)數(shù)組的長(zhǎng)度:切片是一個(gè)長(zhǎng)度可變的數(shù)組。

切片數(shù)據(jù)結(jié)構(gòu)定義

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

切片的結(jié)構(gòu)體由3部分構(gòu)成,Pointer 是指向一個(gè)數(shù)組的指針,len 代表當(dāng)前切片的長(zhǎng)度,cap 是當(dāng)前切片的容量。cap 總是大于等于 len 的。

如果想從 slice 中得到一塊內(nèi)存地址,可以這樣做:

s := make([]byte, 200)
ptr := unsafe.Pointer(&s[0])

3 創(chuàng)建切片

3.1 方法一:make

使用make函數(shù)創(chuàng)建slice

// 創(chuàng)建一個(gè)初始大小是3,容量是10的切片
s1 := make([]int64,3,10)

底層方法實(shí)現(xiàn):

func makeslice(et *_type, len, cap int) slice {
    // 根據(jù)切片的數(shù)據(jù)類型,獲取切片的最大容量
    maxElements := maxSliceCap(et.size)
    // 比較切片的長(zhǎng)度,長(zhǎng)度值域應(yīng)該在[0,maxElements]之間
    if len < 0 || uintptr(len) > maxElements {
        panic(errorString("makeslice: len out of range"))
    }
    // 比較切片的容量,容量值域應(yīng)該在[len,maxElements]之間
    if cap < len || uintptr(cap) > maxElements {
        panic(errorString("makeslice: cap out of range"))
    }
    // 根據(jù)切片的容量申請(qǐng)內(nèi)存
    p := mallocgc(et.size*uintptr(cap), et, true)
    // 返回申請(qǐng)好內(nèi)存的切片的首地址
    return slice{p, len, cap}
}

3.2 方法二:字面量

利用數(shù)組創(chuàng)建切片

arr := [10]int64{1,2,3,4,5,6,7,8,9,10}
s1 := arr[2:4:6] // 以arr[2:4]創(chuàng)建一個(gè)切片,且容量到達(dá)arr[6]的位置,即cap=6-2=4,如果不寫容量則默認(rèn)為數(shù)組最后一個(gè)元素

4 nil和空切片

nil切片的指針指向的是nil

空切片指向的是一個(gè)空數(shù)組

空切片和 nil 切片的區(qū)別在于,空切片指向的地址不是nil,指向的是一個(gè)內(nèi)存地址,但是它沒有分配任何內(nèi)存空間,即底層元素包含0個(gè)元素。

最后需要說明的一點(diǎn)是。不管是使用 nil 切片還是空切片,對(duì)其調(diào)用內(nèi)置函數(shù) append,len 和 cap 的效果都是一樣的

5 切片擴(kuò)容

5.1 擴(kuò)容策略

func main() {
    slice := []int{10, 20, 30, 40}
    newSlice := append(slice, 50)
    fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
    fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
    newSlice[1] += 10
    fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
    fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
}

輸出結(jié)果:

Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8

Go 中切片擴(kuò)容的策略是這樣的:

如果切片的容量小于 1024 個(gè)元素,于是擴(kuò)容的時(shí)候就翻倍增加容量。上面那個(gè)例子也驗(yàn)證了這一情況,總?cè)萘繌脑瓉淼?個(gè)翻倍到現(xiàn)在的8個(gè)。

一旦元素個(gè)數(shù)超過 1024 個(gè)元素,那么增長(zhǎng)因子就變成 1.25 ,即每次增加原來容量的四分之一。

5.2 底層數(shù)組是不是新地址

不一定。當(dāng)發(fā)生了擴(kuò)容就肯定是新數(shù)組,沒有發(fā)生擴(kuò)容則是舊地址

不管切片是通過make創(chuàng)建還是字面量創(chuàng)建,底層都是一樣的,指向的是一個(gè)數(shù)組。當(dāng)使用字面量創(chuàng)建時(shí),切片底層使用的數(shù)組就是創(chuàng)建時(shí)候的數(shù)組。修改切片中的元素或者往切片中添加元素,如果沒有擴(kuò)容,則會(huì)影響原數(shù)組的內(nèi)容,切片底層和原數(shù)組是同一個(gè)數(shù)組;當(dāng)切片擴(kuò)容了之后,則修改切片的元素或者往切片中添加元素,不會(huì)修改數(shù)組內(nèi)容,因?yàn)榍衅瑪U(kuò)容之后,底層數(shù)組不再是原數(shù)組,而是一個(gè)新數(shù)組。

所以盡量避免切片底層數(shù)組與原始數(shù)組相同,盡量使用make創(chuàng)建切片

range遍歷數(shù)組或者切片需要注意

func main() {
	// slice := []int{10, 20, 30, 40}
	slice := [4]int{10, 20, 30, 40}
	for index, value := range slice {
		fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index])
	}
}

結(jié)果:

value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378

從上面結(jié)果我們可以看到,如果用 range 的方式去遍歷一個(gè)數(shù)組或者切片,拿到的 Value 其實(shí)是切片里面的值拷貝。所以每次打印 Value 的地址都不變。

由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達(dá)不到更改原切片值的目的的,需要通過 &slice[index] 獲取真實(shí)的地址

尤其是在for循環(huán)中使用協(xié)程,一定不能直接把index,value傳入?yún)f(xié)程,而應(yīng)該通過參數(shù)傳進(jìn)去

錯(cuò)誤示例:

func main() {
	s := []int{10,20,30}
	for index, value := range s {
		go func() {
			time.Sleep(time.Second)
			fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value))
		}()
	}
	time.Sleep(time.Second*2)
}

結(jié)果:

index:2,value:30
index:2,value:30
index:2,value:30

正確示例:

func main() {
	s := []int{10,20,30}
	for index, value := range s {
		go func(i,v int) {
			time.Sleep(time.Second)
			fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v))
		}(index,value)
	}
	time.Sleep(time.Second*2)
}

結(jié)果:

index:0,value:10
index:2,value:30
index:1,value:20

到此這篇關(guān)于深入了解Golang中的Slice底層實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang Slice內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang中web參數(shù)校驗(yàn)的實(shí)現(xiàn)

    Golang中web參數(shù)校驗(yàn)的實(shí)現(xiàn)

    本文介紹了使用Gin框架進(jìn)行參數(shù)校驗(yàn)的幾種方法,包括JSON、URL查詢、表單數(shù)據(jù)的校驗(yàn),常用校驗(yàn)規(guī)則,自定義錯(cuò)誤信息和自定義校驗(yàn)規(guī)則,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-11-11
  • golang中select語句的簡(jiǎn)單實(shí)例

    golang中select語句的簡(jiǎn)單實(shí)例

    Go的select語句是一種僅能用于channl發(fā)送和接收消息的專用語句,此語句運(yùn)行期間是阻塞的,下面這篇文章主要給大家介紹了關(guān)于golang中select語句的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Go語言中的內(nèi)存布局詳解

    Go語言中的內(nèi)存布局詳解

    這篇文章主要給大家介紹了Go語言中的內(nèi)存布局,那么本文中將嘗試解釋Go如何在內(nèi)存中構(gòu)建結(jié)構(gòu)體,以及結(jié)構(gòu)體在字節(jié)和比特位方面是什么樣子。 有需要的朋友們可以參考借鑒,感興趣的朋友們下面來跟著小編一起學(xué)習(xí)學(xué)習(xí)吧。
    2016-11-11
  • golang fmt占位符的使用詳解

    golang fmt占位符的使用詳解

    這篇文章主要介紹了golang fmt占位符的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go中sync.Once源碼的深度講解

    Go中sync.Once源碼的深度講解

    sync.Once是Go語言標(biāo)準(zhǔn)庫中的一個(gè)同步原語,用于確保某個(gè)操作只執(zhí)行一次,本文將從源碼出發(fā)為大家詳細(xì)介紹一下sync.Once的具體使用,x希望對(duì)大家有所幫助
    2025-01-01
  • GoFrame?ORM原生方法操作示例

    GoFrame?ORM原生方法操作示例

    這篇文章主要為大家介紹了GoFrame?ORM原生方法操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Golang生成Excel文檔的方法步驟

    Golang生成Excel文檔的方法步驟

    生成Excel是一個(gè)很常見的需求,本文將介紹如何使用Go的 Excelize庫去生成Excel文檔,以及一些具體場(chǎng)景下的代碼實(shí)現(xiàn),感興趣的可以參考一下
    2021-06-06
  • 使用Golang打印特定的日期時(shí)間的操作

    使用Golang打印特定的日期時(shí)間的操作

    這篇文章主要給大家詳細(xì)介紹了如何使用Golang打印特定的日期時(shí)間的操作,文中有詳細(xì)的代碼示例,具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-07-07
  • Go語言LeetCode題解1046最后一塊石頭的重量

    Go語言LeetCode題解1046最后一塊石頭的重量

    這篇文章主要為大家介紹了Go語言LeetCode題解1046最后一塊石頭的重量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • go內(nèi)存緩存BigCache實(shí)現(xiàn)BytesQueue源碼解讀

    go內(nèi)存緩存BigCache實(shí)現(xiàn)BytesQueue源碼解讀

    這篇文章主要為大家介紹了go內(nèi)存緩存BigCache實(shí)現(xiàn)BytesQueue源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09

最新評(píng)論

罗甸县| 安吉县| 长泰县| 青龙| 黄骅市| 和林格尔县| 汉川市| 安陆市| 连江县| 宁城县| 武邑县| 彭水| 谢通门县| 健康| 库尔勒市| 阿克| 德令哈市| 泰和县| 广平县| 江永县| 仙居县| 浪卡子县| 修水县| 澄江县| 岐山县| 石台县| 铜川市| 吴江市| 琼海市| 满洲里市| 宜宾市| 丹东市| 古丈县| 泽普县| 农安县| 淮阳县| 杂多县| 黄大仙区| 沙坪坝区| 古丈县| 扶绥县|