go語言中切片 slice

參考文章:
https://studygolang.com/articles/18194?fr=sidebar
https://github.com/cch123/golang-notes/blob/master/slice.md
https://blog.haohtml.com/archives/18094

slice:

先看一下slcie結構:

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

slice三要素: type、len、cap

slice 的底層結構定義非常直觀,指向底層數組的指針,當前長度 len 和當前 slice 的 cap。

   []int{1,3,4,5}                                              
                                                               
   struct {                                                    
     array unsafe.Pointer --------------+                      
     len int                            |                      
     cap int                            |                      
   }                                    |                      
                                        |                      
                                        v                      
                                                               
                           +------|-------|------|------+-----+
                           |      |  1    |  3   | 4    |  5  |
                           |      |       |      |      |     |
                           +------|-------|------|------+-----+
                                             [5]int            
func makeslice(et *_type, len, cap int) slice

slice擴容機制:

擴容時會判斷 slice 的 cap 是不是已經大於 1024,如果在 1024 之內,會按二倍擴容。超過的話就是 1.25 倍擴容了。

slice 擴容必然會導致內存拷貝,如果是性能敏感的系統中,儘可能地提前分配好 slice 是較好的選擇。

var arr = make([]int, 0, 10)
func growslice(et *_type, old slice, cap int) slice {

    if et.size == 0 {
        if cap < old.cap {
            panic(errorString("growslice: cap out of range"))
        }
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        // 注意這裏的 1024 閾值
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    var overflow bool
    var lenmem, newlenmem, capmem uintptr
    const ptrSize = unsafe.Sizeof((*byte)(nil))
    switch et.size {
    case 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        overflow = uintptr(newcap) > _MaxMem
        newcap = int(capmem)
    case ptrSize:
        lenmem = uintptr(old.len) * ptrSize
        newlenmem = uintptr(cap) * ptrSize
        capmem = roundupsize(uintptr(newcap) * ptrSize)
        overflow = uintptr(newcap) > _MaxMem/ptrSize
        newcap = int(capmem / ptrSize)
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem = roundupsize(uintptr(newcap) * et.size)
        overflow = uintptr(newcap) > maxSliceCap(et.size)
        newcap = int(capmem / et.size)
    }

    if cap < old.cap || overflow || capmem > _MaxMem {
        panic(errorString("growslice: cap out of range"))
    }

    var p unsafe.Pointer
    if et.kind&kindNoPointers != 0 {
        p = mallocgc(capmem, nil, false)
        memmove(p, old.array, lenmem)
        // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
        // Only clear the part that will not be overwritten.
        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
    } else {
        // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
        p = mallocgc(capmem, et, true)
        if !writeBarrier.enabled {
            memmove(p, old.array, lenmem)
        } else {
            for i := uintptr(0); i < lenmem; i += et.size {
                typedmemmove(et, add(p, i), add(old.array, i))
            }
        }
    }

    return slice{p, old.len, newcap}
}

slice的append()函數

func append(slice []Type, elems ...Type) []Type

函數說明:內建函數append追加一個或多個elems到一個slice依賴的array的末尾,如果這個slice有足夠的capacity,則reslice以容納新增元素;如果capacity空間不夠,則進行擴容,重新分配內存保存新的slice依賴的array,函數返回更新後的slice.

注意:append不會修改傳參進來的slice(len和cap),只會在不夠用的時候新分配一個array,並把之前的slice依賴的array數據拷貝過來;所以對同一個slice 重複 append,只要不超過cap,都是修改的同一個array,後面的會覆蓋前面

slice是做函數參數是值傳遞:

func main() {
    var a = make([]int, 10)
    fmt.Println(a)
}

func doSomeHappyThings(sl []int) {
    if len(sl) > 0 {
        sl[0] = 1
    }
}

把 a 傳入到 doSomeHappyThings,然後 a 的第一個元素就被修改了,進而認爲在 Go 中,slice 是引用傳遞的。其實這是錯誤,go裏面都是值傳遞,不存在引用傳遞。

發佈了331 篇原創文章 · 獲贊 132 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章