slicesliceruntime/slice.goslice

Slice结构体

GO1.15+
type slice struct {
  array unsafe.Pointer  // 一个指向底层数组的指针
  len   int // 切片当前元素的个数 
  cap   int // 切片的容量,cap永远要大于len
}

切片初始化

slice
package main
​
import (
  "fmt"
)
​
func main() {
  slice1 := []int{1, 2, 3, 4}
  slice2 := make([]int, 0, 5)
  slice3 := new([]int)
  fmt.Printf("切片slice1,分配大小:%d,实际长度: %d,指针地址:%p \n", cap(slice1), len(slice1), slice1)
  fmt.Printf("切片slice2,分配大小:%d,实际长度: %d,指针地址:%p \n", cap(slice2), len(slice2), slice2)
  fmt.Printf("切片slice3,分配大小:%d,实际长度: %d,指针地址:%p \n", cap(*slice3), len(*slice3), slice3)
}
// 输出如下
// 切片slice1,分配大小:4,实际长度: 4,指针地址:0xc00000e200
// 切片slice2,分配大小:5,实际长度: 0,指针地址:0xc00000a540 
// 切片slice3,分配大小:0,实际长度: 0,指针地址:0xc0000040d8 
go tool compile -S main.go
[]Type
runtime.newobjectruntime.malloc.gogolang内存分配
func newobject(typ *_type) unsafe.Pointer {
 return mallocgc(typ.size, typ, true)
}
make
slicebuiltinmakeruntime.makesliceruntime/slice.go
func makeslice(et *_type, len, cap int) unsafe.Pointer {
 // 判断是否溢出,以及len与cap是否越界
 mem, overflow := math.MulUintptr(et.size, uintptr(cap))
 if overflow || mem > maxAlloc || len < 0 || len > cap {
  mem, overflow := math.MulUintptr(et.size, uintptr(len))
  if overflow || mem > maxAlloc || len < 0 {
   panicmakeslicelen()
  }
  panicmakeslicecap()
 }
 // 如上面方式一样调用系统分配对象的方法
 return mallocgc(mem, et, true)
}
new
runtime.newobjectruntime.malloc.go
func newobject(typ *_type) unsafe.Pointer {
  return mallocgc(typ.size, typ, true)
}

访问元素

sliceslice
package main
​
import (
  "fmt"
)
​
func main() {
  src := []int{1, 2, 3}
  fmt.Println(src[1])  
}
// 输出结果:2

追加操作

slicearrayappendsliceslicecaplenappendruntime.growslice
package main
​
import "fmt"
​
func main() {
  slice1 := make([]int, 0, 2)
  slice1 = append(slice1, 1)
  slice1 = append(slice1, 1)
  slice1 = append(slice1, 1)
  fmt.Println(slice1)
}
go tool compile -S main.goappendappendappend

扩容机制

sliceruntime.growsliceruntime/slice.go
func growslice(et *_type, old slice, cap int) slice {
  // 是否启用数据竞争监测
  if raceenabled {
    callerpc := getcallerpc()
    racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
  }
  if msanenabled {
    msanread(old.array, uintptr(old.len*int(et.size)))
  }
  // 如果扩容后的cap还小于扩容前的cap 直接panic
  if cap < old.cap {
    panic(errorString("growslice: cap out of range"))
  }
  // 如果当前slice大小为0还调用了扩容,则直接返回一个新slice
  if et.size == 0 {
    // append 没法创建一个nil指针的但是len不为0的切片
    return slice{unsafe.Pointer(&zerobase), old.len, cap}
  }
  // 新容量的计算
  newcap := old.cap
  doublecap := newcap + newcap
  if cap > doublecap { // 如果指定容量>2倍扩容前slice容量,则直接使用指定的容量
    newcap = cap
  } else { // 如果指定容量小于或者等于2倍当前slice容量
    if old.len < 1024 { // 当扩容前slice长度<1024时,则直接扩容2倍
      newcap = doublecap
    } else { // 当扩容前的长度>=1024
      // 检查是否存在越界,防止死循环;
      for 0 < newcap && newcap < cap { // 元素个数>=1024,则扩容为1.25倍,但<cap
        newcap += newcap / 4
      }
      if newcap <= 0 {
        newcap = cap
      }
    }
  }
  // 计算对应的内存块大小(span)
  var overflow bool
  var lenmem, newlenmem, capmem uintptr
  switch {
  case et.size == 1:
    lenmem = uintptr(old.len)
    newlenmem = uintptr(cap)
    capmem = roundupsize(uintptr(newcap))
    overflow = uintptr(newcap) > maxAlloc
    newcap = int(capmem)
  case et.size == sys.PtrSize:
    lenmem = uintptr(old.len) * sys.PtrSize
    newlenmem = uintptr(cap) * sys.PtrSize
    capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
    overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
    newcap = int(capmem / sys.PtrSize)
  case isPowerOfTwo(et.size):
    var shift uintptr
    if sys.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)
    overflow = uintptr(newcap) > (maxAlloc >> shift)
    newcap = int(capmem >> shift)
  default:
    lenmem = uintptr(old.len) * et.size
    newlenmem = uintptr(cap) * et.size
    capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
    capmem = roundupsize(capmem)
    newcap = int(capmem / et.size)
  }
  // 如果所需要的内存超过最大可分配内存则panic
  if overflow || capmem > maxAlloc {
    panic(errorString("growslice: cap out of range"))
  }
  var p unsafe.Pointer
  if et.ptrdata == 0 {
    //分配一段新内存
    p = mallocgc(capmem, nil, false)
    memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
  } else {
    //如果元素是指针类型,申请一段新内存时需要置0,不然会被GC扫描,指针类型要考虑GC写屏障
    p = mallocgc(capmem, et, true)
    if lenmem > 0 && writeBarrier.enabled {
      bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
    }
  }
  // 把slice中的数据拷贝到新申请内存的低地址
  memmove(p, old.array, lenmem)
  // 返回新创建slice并设置cap为newcap
  return slice{p, old.len, newcap}
}

拷贝操作

slicecopy
package main
​
func main() {
  src := []int{ 1, 2, 3 }
  tar := make([]int, 2)
  copy(tar, src)
}
runtime.makeslicecopyruntime/slice.go
func makeslicecopy(et *_type, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer {
  // 计算需要复制的内存块大小
  // 目标slice长度 > 来源slice长度,以来源长度为准
  // 目标slice长度 <= 来源slice长度,以目标长度为准
  var tomem, copymem uintptr
  if uintptr(tolen) > uintptr(fromlen) { //目标slice长度>来源slice长度,已目标
    // 目标slice内存,如果大于最大可分配内存,则panic
    var overflow bool
    tomem, overflow = math.MulUintptr(et.size, uintptr(tolen))  
    if overflow || tomem > maxAlloc || tolen < 0 { 
      panicmakeslicelen()
    }
    // 需要拷贝的内存为源目标内存
    copymem = et.size * uintptr(fromlen) 
  } else {
    // 需要拷贝的内存为目标内存
    tomem = et.size * uintptr(tolen)
    copymem = tomem
  }
  // 申请目标slice内存块
  var to unsafe.Pointer
  if et.ptrdata == 0 {
    to = mallocgc(tomem, nil, false)
    if copymem < tomem {
      memclrNoHeapPointers(add(to, copymem), tomem-copymem)
    }
  } else {
    to = mallocgc(tomem, et, true)
    if copymem > 0 && writeBarrier.enabled {
      bulkBarrierPreWriteSrcOnly(uintptr(to), uintptr(from), copymem)
    }
  }
  // 数据竞争检测和支持与内存清理程序的互操作
  if raceenabled {
    callerpc := getcallerpc()
    pc := funcPC(makeslicecopy)
    racereadrangepc(from, copymem, callerpc, pc)
  }
  if msanenabled {
    msanread(from, copymem)
  }
  // 复制内存块
  memmove(to, from, copymem)
  return to
}

删除操作

slice
func removeElem(src []int, toDel int) []int {
  for i := 0; i < len(src); i++ {
    if src[i] == toDel {
      src = append(src[:i], src[i+1:]...)
      i--
    }
  }
  return src
}
​
func removeElem(src []int, toDel int) []int {
  tar := src[:0]
  for _, num := range src {
    if num != toDel {
      tar = append(tar, num)
    }
  }
  return tar
}
​
func removeElem(src []int, toDel int) []int {
  tar := make([]int, 0, len(src))
  for _, num := range src {
    if num != toDel {
      tar = append(tar, num)
    }
  }
  return tar
}