数组的初始化
package main
import "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 main
import "fmt"
func main() {
// 借助 make 函数,此时 len = cap = 5,每个元素初始化为 byte 的 zero-value
s0 := make([]byte, 5)
// 字面值初始化,此时 len = cap = 5
s1 := []byte{0, 0, 0, 0, 0}
// 自动初始化为 slice 的“零值(zero-value)”:nil
var s2 []byte
fmt.Println("s0: ", s0)
fmt.Println("s1: ", s1)
fmt.Println("s2: ", s2)
}
数组和切片的不同
- 数组定义后长度不可变、而切片可变
- 切片是引用类型、而数组是 值类型、
- 引用类型的除了切片还有map、channel、函数等
- 值类型的有 基础数据类型 和 结构体类型
查看数组及切片的长度、容量
package main
import "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 main
import "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: 3
The capacity of s4: 5
The value of s4: [4 5 6]
s3中的 元素4地址是: 824633827544
s4中的 元素4地址是: 824633827544
*/
切片的底层数组 什么时候被替换?
- 准确来说 一个切片的底层数组永远不会被替换
- 虽然扩容时、Go语言一定会生成一个新的底层数组、但同时也生成了新的切片
- 在 容量足够的情况下、append函数返回的是 指向原切片的底层数组
- append 需要扩容时、返回的是 指向 新底层数组的新切片
slice 源码解析
代码位置 runtime/slice.go
make
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 获取需要申请的内存大小
// overflow 乘法是否溢出、这里指的是 size * cap 是否溢出
// mem 等于 size * cap
mem, 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)))
}
// 新容量如果小于 当前容量直接 panic
if 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.cap
doublecap := newcap + newcap
if 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 bool
var 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) > 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):
// 2的倍数 用位移计算
var shift uintptr
if 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) << 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)
}
// 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.Pointer
if 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.len
if 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) * width
if 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
}