golang slice 使用及源碼分析

來源:互聯網
上載者:User

 

  • 1.先做個小實驗
func main(){    s1:=make([]int,0,10)    s1=[]int{1,2,3}    ss:=make([]int,0,10)    ss = s1[1:]    for i:=0;i<len(ss);i++{        ss[i] +=10    }    fmt.Println(s1)   // [1 12 13]    ss =append(ss,4)    for i:=0;i<len(ss);i++{        ss[i] +=10    }    fmt.Println(s1)  // [1 12 13] 而不是 [1,22,23]    t:=[]int{0}    printPoint(t)  // 0xc4200140a8 cap(s)= 1    t = append(t,1)    printPoint(t)  // 0xc4200140c0 0xc4200140c8 cap(s)= 2    t = append(t,2)    printPoint(t)   // 0xc4200160e0 0xc4200160e8 0xc4200160f0 cap(s)= 4    t = append(t,3)    printPoint(t)   //  0xc4200160e0 0xc4200160e8 0xc4200160f0 0xc4200160f8 cap(s)= 4}func printPoint(s []int){    for i:=0;i<len(s);i++{        fmt.Print(unsafe.Pointer(&s[i])," ")    }    fmt.Println("cap(s)=",cap(s))}

發現slice在進行append操作時會跟據原來的slice容量,如果append完成後新slice的容量超過原來slice的容量,則需要擴容,並且將舊的slice資料全部遷移到新的slice開闢的地址裡。

  • 2.在runtime目錄下找到slice.go,定位到growslice(et *_type, old slice, cap int)這個函數
type slice struct {    array unsafe.Pointer    len   int    cap   int}// growslice handles slice growth during append.// It is passed the slice element type, the old slice, and the desired new minimum capacity,// and it returns a new slice with at least that capacity, with the old data// copied into it.// The new slice's length is set to the old slice's length,// NOT to the new requested capacity.// This is for codegen convenience. The old slice's length is used immediately// to calculate where to write new values during an append.// TODO: When the old backend is gone, reconsider this decision.// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.// 與append(slice,s)對應的函數growslice// 通過切片的類型,舊切片的容量和資料得出新切片的容量,新切片跟據容量重新申請一塊地址,把舊切片的資料拷貝到新切片中func growslice(et *_type, old slice, cap int) slice {// 單純地擴容,不寫資料    if et.size == 0 {        if cap < old.cap {            panic(errorString("growslice: cap out of range"))        }        // append should not create a slice with nil pointer but non-zero len.        // We assume that append doesn't need to preserve old.array in this case.        return slice{unsafe.Pointer(&zerobase), old.len, cap}    }// 擴容規則 1.新的容量大於舊的2倍,直接擴容至新的容量// 2.新的容量不大於舊的2倍,當舊的長度小於1024時,擴容至舊的2倍,否則擴容至舊的5/4倍    newcap := old.cap    doublecap := newcap + newcap    if cap > doublecap {        newcap = cap    } else {        if old.len < 1024 {            newcap = doublecap        } else {            for newcap < cap {                newcap += newcap / 4            }        }    }// 跟據切片類型和容量計算要分配記憶體的大小    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))        newcap = int(capmem)    case ptrSize:        lenmem = uintptr(old.len) * ptrSize        newlenmem = uintptr(cap) * ptrSize        capmem = roundupsize(uintptr(newcap) * ptrSize)        newcap = int(capmem / ptrSize)    default:        lenmem = uintptr(old.len) * et.size        newlenmem = uintptr(cap) * et.size        capmem = roundupsize(uintptr(newcap) * et.size)        newcap = int(capmem / et.size)    }// 異常情況,舊的容量比新的容量還大或者新的容量超過限制了    if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {        panic(errorString("growslice: cap out of range"))    }    var p unsafe.Pointer    if et.kind&kindNoPointers != 0 {// 為新的切片開闢容量為capmem的地址空間        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 clears n bytes starting at ptr.//// Usually you should use typedmemclr. memclrNoHeapPointers should be// used only when the caller knows that *ptr contains no heap pointers// because either://// 1. *ptr is initialized memory and its type is pointer-free.//// 2. *ptr is uninitialized memory (e.g., memory that's being reused//    for a new allocation) and hence contains only "junk".        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}}

 

  • 3.總結
  1. 不要輕易的對切片append,如果新的切片容量比舊的大的話,需要進行growslice操作,新的地址開闢,資料拷貝
  2. 盡量對切片設定初始容量值以避免growslice,類似make([]int,0,100)
  3. 切片是一個結構體,儲存著切片的容量,實際長度以及數組的地址

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.