本篇整理的内容是基于go 1.18 版本。

    切片底层结构:

    1. type slice struct {
    2. array unsafe.Pointer
    3. len int
    4. cap int
    5. }

    在创建slice时,容量和长度是相等的。例如,创建一个长度为5的切片,那么它的容量也是5。
    在调用append函数时,如果新的长度 > 容量,会发生扩容,并改变底层数组。
    扩容机制:

    • 如果新申请长度大于 2 倍的旧容量,那么最终容量就是新的切片长度 。
    • 如果旧容量小于256,那么最终容量就是旧容量的两倍 。
    • 最终容量不一定是多少,因为涉及到移位。(大概是之前容量的1.25倍)
    • 最终容量计算值溢出,则最终容量就是新的切片长度。 ```python newcap := old.cap doublecap := newcap + newcap if cap > doublecap {
      1. newcap = cap
      } else {
      1. const threshold = 256
      2. if old.cap < threshold {
      3. newcap = doublecap
      4. } else {
      5. // Check 0 < newcap to detect overflow
      6. // and prevent an infinite loop.
      7. for 0 < newcap && newcap < cap {
      8. // Transition from growing 2x for small slices
      9. // to growing 1.25x for large slices. This formula
      10. // gives a smooth-ish transition between the two.
      11. newcap += (newcap + 3*threshold) / 4
      12. }
      13. // Set newcap to the requested cap when
      14. // the newcap calculation overflowed.
      15. if newcap <= 0 {
      16. newcap = cap
      17. }
      18. }
      }
    1. ```python
    2. var overflow bool
    3. var lenmem, newlenmem, capmem uintptr
    4. // Specialize for common values of et.size.
    5. // For 1 we don't need any division/multiplication.
    6. // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    7. // For powers of 2, use a variable shift.
    8. switch {
    9. case et.size == 1:
    10. lenmem = uintptr(old.len)
    11. newlenmem = uintptr(cap)
    12. capmem = roundupsize(uintptr(newcap))
    13. overflow = uintptr(newcap) > maxAlloc
    14. newcap = int(capmem)
    15. case et.size == goarch.PtrSize:
    16. lenmem = uintptr(old.len) * goarch.PtrSize
    17. newlenmem = uintptr(cap) * goarch.PtrSize
    18. capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
    19. overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
    20. newcap = int(capmem / goarch.PtrSize)
    21. case isPowerOfTwo(et.size):
    22. var shift uintptr
    23. if goarch.PtrSize == 8 {
    24. // Mask shift for better code generation.
    25. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
    26. } else {
    27. shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
    28. }
    29. lenmem = uintptr(old.len) << shift
    30. newlenmem = uintptr(cap) << shift
    31. capmem = roundupsize(uintptr(newcap) << shift)
    32. overflow = uintptr(newcap) > (maxAlloc >> shift)
    33. newcap = int(capmem >> shift)
    34. default:
    35. lenmem = uintptr(old.len) * et.size
    36. newlenmem = uintptr(cap) * et.size
    37. capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
    38. capmem = roundupsize(capmem)
    39. newcap = int(capmem / et.size)
    40. }

    我们只需知晓扩容的原理即可,不必纠结扩容后具体的数字。

    案例:

    1. func main() {
    2. a := []int{1, 2, 3}
    3. // silce 函数实现在下面
    4. slice(a)
    5. fmt.Println("1", a)
    6. // slicePtr1 函数实现在下面
    7. slicePtr1(&a)2
    8. fmt.Println("2", a)
    9. // slicePtr2 函数实现在下面
    10. slicePtr2(&a)
    11. fmt.Println("3", a)
    12. slicePtr3(&a)
    13. }
    14. func slice(s []int) {
    15. s[0] = 10
    16. s = append(s, 10)
    17. s[1] = 10
    18. }
    19. func slicePtr1(s *[]int) {
    20. (*s)[0] = 20
    21. *s = append(*s, 20)
    22. (*s)[1] = 20
    23. }
    24. func slicePtr2(s *[]int) {
    25. b := *s
    26. b[0] = 30
    27. b = append(b, 30)
    28. b[1] = 30
    29. }
    30. func slicePtr3(s *[]int) {
    31. b := *s
    32. b = append(b, 40)
    33. fmt.Println("4", b)
    34. *s = append(*s, 50)
    35. fmt.Println("5", b)
    36. }
    37. 输出结果
    38. 1:[10,2,3] //append前,共用同一底层数组;append后,发生扩容,不共用底层数组。
    39. 2:[20,20,3,20] //传入指针,因此函数内修改会影响到外部。
    40. 3:[30,30,3,20] //整个过程没有发生扩容,因此共用底层数组。
    41. 4:[30,30,3,20,40]
    42. 5:[30,30,3,20,50] //会将a底层数组的第五个元素覆盖为50,又共用同一底层数组。

    线程安全切片:
    切片不是线程安全的,如果我们想要线程安全的切片,可加锁或使用channel。

    1. list := make([]int, 0)
    2. var wg sync.WaitGroup
    3. var mu sync.Mutex
    4. for i := 0; i < 100; i++ {
    5. i := i
    6. wg.Add(1)
    7. go func() {
    8. defer wg.Done()
    9. mu.Lock()
    10. list = append(list, i)
    11. mu.Unlock()
    12. }()
    13. }
    14. wg.Wait()
    1. list := make([]int, 0)
    2. ch := make(chan int, 100)
    3. stopCh := make(chan int)
    4. var wgOne sync.WaitGroup
    5. go func() {
    6. for i := 0; i < 100; i++ {
    7. i := i
    8. wgOne.Add(1)
    9. go func() {
    10. defer wgOne.Done()
    11. ch <- i
    12. }()
    13. }
    14. wgOne.Wait()
    15. close(ch)
    16. }()
    17. go func() {
    18. for i := range ch {
    19. list = append(list, i)
    20. }
    21. stopCh <- 1
    22. }()
    23. select {
    24. case <-stopCh:
    25. fmt.Println("接收到了信号,准备关闭goroutine")
    26. }