欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang中slice擴容的具體實現

 更新時間:2025年05月23日 10:42:30   作者:xsh219  
Go 語言中的切片擴容機制是 Go 運行時的一個關鍵部分,它確保切片在動態(tài)增加元素時能夠高效地管理內存,本文主要介紹了golang中slice擴容的具體實現,感興趣的可以了解一下

Go 語言中的切片擴容機制是 Go 運行時的一個關鍵部分,它確保切片在動態(tài)增加元素時能夠高效地管理內存。這個機制是在 Go 運行時內部實現的,涉及了內存分配、數據拷貝和容量調整。擴容的實現主要體現在 runtime.growslice 函數中。下面我們將深入分析 Go 切片的擴容機制,結合源碼來進行詳細解答。

1. 切片擴容的觸發(fā)

切片是 Go 中的一種動態(tài)數組,它有 長度(len) 和 容量(cap)。當我們向切片添加元素時,切片的長度會增加。如果長度超出了當前的容量,Go 會自動擴容。切片擴容的過程是通過 append 函數來觸發(fā)的。

append 函數的實現

append 函數是 Go 內置的函數,用于向切片追加元素。它的實現會首先檢查當前切片的容量是否足夠容納新的元素,如果不夠,它會調用 runtime.growslice 函數來擴容。

// append() 源碼簡化版
func append(slice []T, elems ...T) []T {
    // 1. 檢查當前切片是否有足夠的容量
    if len(slice)+len(elems) <= cap(slice) {
        // 如果容量足夠,直接追加元素
        slice = slice[:len(slice)+len(elems)]
        copy(slice[len(slice)-len(elems):], elems)
        return slice
    }
    // 2. 如果容量不足,進行擴容
    return growslice(reflect.TypeOf(slice).Elem(), slice, len(slice)+len(elems))
}

2. runtime.growslice 函數

當切片容量不足時,append 會調用 growslice 函數來擴容。growslice 函數是 Go 運行時內部函數,負責實際的內存分配和切片擴容操作。

growslice 函數源碼

// growslice 擴容的實現
func growslice(et *_type, old slice, cap int) slice {
    if cap < old.cap {
        panic("growslice: cap out of range")
    }

    // 如果元素大小為 0,直接返回新的切片
    if et.size == 0 {
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    // 擴容策略:根據當前容量和預期的新容量來決定新容量
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        const threshold = 256
        if old.cap < threshold {
            newcap = doublecap
        } else {
            for 0 < newcap && newcap < cap {
                newcap += (newcap + 3*threshold) / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    // 根據擴容后的容量計算所需的內存空間
    var lenmem, newlenmem, capmem uintptr
    switch {
    case et.size == 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        newcap = int(capmem)
    case et.size == goarch.PtrSize:
        lenmem = uintptr(old.len) * goarch.PtrSize
        newlenmem = uintptr(cap) * goarch.PtrSize
        capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
        newcap = int(capmem / goarch.PtrSize)
    case isPowerOfTwo(et.size):
        var shift uintptr
        if goarch.PtrSize == 8 {
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
        }
        lenmem = uintptr(old.len) << shift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        newcap = int(capmem >> shift)
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem, _ = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }

    // 分配新的內存,并拷貝舊的數據
    var p unsafe.Pointer
    if et.ptrdata == 0 {
        p = mallocgc(capmem, nil, false)
    } else {
        p = mallocgc(capmem, et, true)
    }

    memmove(p, old.array, lenmem)
    return slice{p, old.len, newcap}
}

3. 擴容的具體過程

  • 檢查容量是否足夠: growslice 首先會檢查請求的擴容是否超過了當前切片的容量。如果新容量大于原容量的兩倍,它會直接使用新容量作為目標容量。

  • 根據擴容策略計算新容量:

    • 如果切片的容量小于 256,它會直接翻倍容量(newcap = cap)。

    •  如果切片容量較大,擴容時會采取逐步增加的策略:按原容量的 1.25 倍+192 (也就是3*threshold / 4) 增長,直到滿足目標容量。

  • 計算內存所需大?。?nbsp;growslice 會計算新切片所需的內存大小,并分配新的內存空間。對于不同類型的元素(如基本類型、指針類型等),內存分配的方式會有所不同。

  • 內存分配和數據拷貝:

    • 通過 mallocgc 分配新的內存區(qū)域,在分配空間的時候因根據標簽的大小等級會向上取整,go在擴容的時候寧愿犧牲一部分內部碎片也要保證外部盡可能少的外部碎片,并使用 memmove 將原切片的數據拷貝到新的內存中。

    • 擴容后的切片會更新 指針(指向新的內存)、長度 和 容量,然后返回新的切片。

4. 擴容的性能考慮

切片擴容的過程需要進行 內存分配 和 數據拷貝,這會影響性能,尤其是在頻繁擴容的場景中。

  • 頻繁擴容的開銷:每次擴容時,都會重新分配內存并將數據拷貝到新的內存區(qū)域,這會帶來額外的時間和空間開銷。

  • 容量預估:為了避免頻繁擴容,最好在創(chuàng)建切片時預估切片需要的容量,尤其是在元素數量已知的情況下??梢允褂?nbsp;make([]T, 0, n) 來預分配一個足夠的容量,以減少擴容操作。

5. 擴容策略的總結

  • 小容量時(比如容量小于 256),切片會按 2 倍擴容。

  • 大容量時,Go 會使用 1.25 倍擴容 + 192 的策略,確保不會過度浪費內存。

  • 對于 nil 或零容量切片,growslice 會初始化并分配合適的容量。

6. 示例:切片擴容過程

下面是一個示例,展示了切片擴容的過程:

package main

import "fmt"

func main() {
    var slice []int
    for i := 0; i < 10; i++ {
        slice = append(slice, i)
        fmt.Printf("Length: %d, Capacity: %d, Slice: %v\n", len(slice), cap(slice), slice)
    }
}

輸出示例:

Length: 1, Capacity: 1, Slice: [0]
Length: 2, Capacity: 2, Slice: [0 1]
Length: 3, Capacity: 4, Slice: [0 1 2]
Length: 4, Capacity: 4, Slice: [0 1 2 3]
Length: 5, Capacity: 8, Slice: [0 1 2 3 4]
Length: 6, Capacity: 8, Slice: [0 1 2 3 4 5]
Length: 7, Capacity: 8, Slice: [0 1 2 3 4 5 6]
Length: 8, Capacity: 8, Slice: [0 1 2 3 4 5 6 7]
Length: 9, Capacity: 16, Slice: [0 1 2 3 4 5 6 7 8]
Length: 10, Capacity: 16, Slice: [0 1 2 3 4 5 6 7 8 9]

如上所示,切片的容量在每次添加元素時會根據擴容策略進行增長。初始容量為 1,當容量不足時,擴容后容量翻倍。最終,容量達到 16。

總結

Go 的切片擴容機制是通過 append 函數和 growslice 函數實現的。在擴容時,Go 會根據當前切片的容量和預期的容量進行計算,并分配新的內存空間,拷貝原有數據。擴容的策略通常是小容量時翻倍,大容量時逐步增加,保證了內存的高效使用。

到此這篇關于golang中slice擴容的具體實現的文章就介紹到這了,更多相關golang slice擴容內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go 簡單實現多租戶數據庫隔離

    Go 簡單實現多租戶數據庫隔離

    本文主要介紹了Go 簡單實現多租戶數據庫隔離,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-05-05
  • Makefile構建Golang項目示例詳解

    Makefile構建Golang項目示例詳解

    這篇文章主要為大家介紹了Makefile構建Golang項目的過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • gorm golang 并發(fā)連接數據庫報錯的解決方法

    gorm golang 并發(fā)連接數據庫報錯的解決方法

    今天小編就為大家分享一篇gorm golang 并發(fā)連接數據庫報錯的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-07-07
  • go內存隊列l(wèi)ist VS slice實現方式對比分析

    go內存隊列l(wèi)ist VS slice實現方式對比分析

    這篇文章主要為大家介紹了go內存隊列l(wèi)ist VS slice實現方式對比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Go語言的匿名字段實現組合復用實例探究

    Go語言的匿名字段實現組合復用實例探究

    這篇文章主要為大家介紹了Go語言的匿名字段實現組合復用實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • golang調用shell命令(實時輸出,終止)

    golang調用shell命令(實時輸出,終止)

    本文主要介紹了golang調用shell命令(實時輸出,終止),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • Go語言學習教程之goroutine和通道的示例詳解

    Go語言學習教程之goroutine和通道的示例詳解

    這篇文章主要通過A?Tour?of?Go中的例子進行學習,以此了解Go語言中的goroutine和通道,文中的示例代碼講解詳細,感興趣的可以了解一下
    2022-09-09
  • go語言實現依賴注入的示例代碼

    go語言實現依賴注入的示例代碼

    依賴注入和控制反轉恰恰相反,它是一種具體的編碼技巧,我們不通過 new 的方式在類內部創(chuàng)建依賴類的對象,而是將依賴的類對象在外部創(chuàng)建好之后,通過構造函數、函數參數等方式傳遞給類來使用,本文將給大家介紹go語言實現依賴注入,需要的朋友可以參考下
    2024-01-01
  • Go?的入口函數和包初始化的使用

    Go?的入口函數和包初始化的使用

    本文主要介紹了Go?的入口函數和包初始化的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • Go?panic的三種產生方式細節(jié)探究

    Go?panic的三種產生方式細節(jié)探究

    這篇文章主要介紹了Go?panic的三種產生方式細節(jié)探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12

最新評論