数组的初始化
package mainimport "fmt"func echo(x [4]int) {fmt.Println(x)}func main() {// 变量 a 类型为 [4]int 是一个 type,每个元素自动初始化为 int 的零值(zero-value)var a [4]int// 变量 b 类型为 [5]int 是不同于 [4]int 的类型,且 b[4] 会自动初始化为 int 的零值b := [5]int{1, 2, 3, 4}// 变量 c 被自动推导为 [5]int 类型,与 b 类型同c := [...]int{1, 2, 3, 4, 5}// a里面的所有元素都会被复制一遍、因为go语言的函数传参是 值传递echo(a)fmt.Println("b: ", b)fmt.Println("c: ", c)}
slice 初始化
package mainimport "fmt"func main() {// 借助 make 函数,此时 len = cap = 5,每个元素初始化为 byte 的 zero-values0 := make([]byte, 5)// 字面值初始化,此时 len = cap = 5s1 := []byte{0, 0, 0, 0, 0}// 自动初始化为 slice 的“零值(zero-value)”:nilvar s2 []bytefmt.Println("s0: ", s0)fmt.Println("s1: ", s1)fmt.Println("s2: ", s2)}
数组和切片的不同
- 数组定义后长度不可变、而切片可变
- 切片是引用类型、而数组是 值类型、
- 引用类型的除了切片还有map、channel、函数等
- 值类型的有 基础数据类型 和 结构体类型
查看数组及切片的长度、容量
package mainimport "fmt"func main() {// 创建一个slice 其实就是分配内存、cap 和 len的设置是在汇编中完成的s1 := make([]int, 5)fmt.Printf("The length of s1: %d\n", len(s1))fmt.Printf("The capacity of s1: %d\n", cap(s1))fmt.Printf("The value of s1: %d\n", s1)s2 := make([]int, 5, 8)fmt.Printf("The length of s2: %d\n", len(s2))fmt.Printf("The capacity of s2: %d\n", cap(s2))fmt.Printf("The value of s2: %d\n", s2)}
切片同时 指向一个底层数组
package mainimport "fmt"func main() {s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}s4 := s3[3:6]fmt.Printf("The length of s4: %d\n", len(s4))fmt.Printf("The capacity of s4: %d\n", cap(s4))fmt.Printf("The value of s4: %d\n", s4)fmt.Printf("s3中的 元素4地址是: %d\n", &s3[3])fmt.Printf("s4中的 元素4地址是: %d\n", &s4[0])}/**Output:The length of s4: 3The capacity of s4: 5The value of s4: [4 5 6]s3中的 元素4地址是: 824633827544s4中的 元素4地址是: 824633827544*/
切片的底层数组 什么时候被替换?
- 准确来说 一个切片的底层数组永远不会被替换
- 虽然扩容时、Go语言一定会生成一个新的底层数组、但同时也生成了新的切片
- 在 容量足够的情况下、append函数返回的是 指向原切片的底层数组
- append 需要扩容时、返回的是 指向 新底层数组的新切片
slice 源码解析
代码位置 runtime/slice.go
make
func makeslice(et *_type, len, cap int) unsafe.Pointer {// 获取需要申请的内存大小// overflow 乘法是否溢出、这里指的是 size * cap 是否溢出// mem 等于 size * capmem, overflow := math.MulUintptr(et.size, uintptr(cap))// 如果溢出了 或// size * cap大于最大分配 或// len 小于0 或// len 大于 cap// 都会报错、len上限超出范围 或者 cap上限超出范围if overflow || mem > maxAlloc || len < 0 || len > cap {// NOTE: Produce a 'len out of range' error instead of a// 'cap out of range' error when someone does make([]T, bignumber).// 'cap out of range' is true too, but since the cap is only being// supplied implicitly, saying len is clearer.// See golang.org/issue/4085.mem, overflow := math.MulUintptr(et.size, uintptr(len))if overflow || mem > maxAlloc || len < 0 {panicmakeslicelen()}panicmakeslicecap()}// 分配 slice内存// 小对象从 当前 P的 cache中空闲数据里面分配// 大对象 (size > 32KB) 直接从 heap(堆) 中分配return mallocgc(mem, et, true)}
append
func growslice(et *_type, old slice, cap int) slice {// 是否开启 race检测、也就是数据竞争检测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)))}// 新容量如果小于 当前容量直接 panicif cap < old.cap {panic(errorString("growslice: cap out of range"))}// 存储的类型空间为0、长度不为空、则重新创建if et.size == 0 {// 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}}newcap := old.capdoublecap := newcap + newcapif cap > doublecap {// 如果新容量大于 原有容量的两倍、则直接按照新增容量大小申请newcap = cap} else {if old.len < 1024 {// 如果原有长度 小于1024 kb、新容量则按照两倍创建newcap = doublecap} else {// 大于1024kb 按照原有容量的 1/4 扩容、直到满足新容量的需要// 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}}}// 为了加速计算、对于不同的slice 元素大小、// 选择不同的计算方法、获取需要申请的内存大小var overflow boolvar lenmem, newlenmem, capmem uintptr// Specialize for common values of et.size.// For 1 we don't need any division/multiplication.// For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.// For powers of 2, use a variable shift.switch {case et.size == 1:lenmem = uintptr(old.len)newlenmem = uintptr(cap)capmem = roundupsize(uintptr(newcap))overflow = uintptr(newcap) > maxAllocnewcap = int(capmem)case et.size == sys.PtrSize:lenmem = uintptr(old.len) * sys.PtrSizenewlenmem = uintptr(cap) * sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)overflow = uintptr(newcap) > maxAlloc/sys.PtrSizenewcap = int(capmem / sys.PtrSize)case isPowerOfTwo(et.size):// 2的倍数 用位移计算var shift uintptrif sys.PtrSize == 8 {// Mask shift for better code generation.shift = uintptr(sys.Ctz64(uint64(et.size))) & 63} else {shift = uintptr(sys.Ctz32(uint32(et.size))) & 31}lenmem = uintptr(old.len) << shiftnewlenmem = uintptr(cap) << shiftcapmem = roundupsize(uintptr(newcap) << shift)overflow = uintptr(newcap) > (maxAlloc >> shift)newcap = int(capmem >> shift)default:// 其他计算用 除法lenmem = uintptr(old.len) * et.sizenewlenmem = uintptr(cap) * et.sizecapmem, overflow = math.MulUintptr(et.size, uintptr(newcap))capmem = roundupsize(capmem)newcap = int(capmem / et.size)}// The check of overflow in addition to capmem > maxAlloc is needed// to prevent an overflow which can be used to trigger a segfault// on 32bit architectures with this example program://// type T [1<<27 + 1]int64//// var d T// var s []T//// func main() {// s = append(s, d, d, d, d)// print(len(s), "\n")// }// 判断是否溢出if overflow || capmem > maxAlloc {panic(errorString("growslice: cap out of range"))}// 内存分配var p unsafe.Pointerif et.ptrdata == 0 {p = mallocgc(capmem, nil, false)// 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)// gc相关if lenmem > 0 && writeBarrier.enabled {// Only shade the pointers in old.array since we know the destination slice p// only contains nil pointers because it has been cleared during alloc.bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem)}}// 数据拷贝memmove(p, old.array, lenmem)return slice{p, old.len, newcap}}
copy
func slicecopy(to, fm slice, width uintptr) int {if fm.len == 0 || to.len == 0 {return 0}n := fm.lenif to.len < n {n = to.len}// 元素大小为0、直接返回if width == 0 {return n}// 数据竞争检测if raceenabled {callerpc := getcallerpc()pc := funcPC(slicecopy)racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)}// 内存扫描if msanenabled {msanwrite(to.array, uintptr(n*int(width)))msanread(fm.array, uintptr(n*int(width)))}size := uintptr(n) * widthif size == 1 { // common case worth about 2x to do here// TODO: is this still worth it with new memmove impl?// 直接拷贝*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer} else {// 拷贝memmove(to.array, fm.array, size)}return n}// 字符串slice的拷贝func slicestringcopy(to []byte, fm string) int {if len(fm) == 0 || len(to) == 0 {return 0}n := len(fm)if len(to) < n {n = len(to)}if raceenabled {callerpc := getcallerpc()pc := funcPC(slicestringcopy)racewriterangepc(unsafe.Pointer(&to[0]), uintptr(n), callerpc, pc)}if msanenabled {msanwrite(unsafe.Pointer(&to[0]), uintptr(n))}memmove(unsafe.Pointer(&to[0]), stringStructOf(&fm).str, uintptr(n))return n}
