Go Slice 与 Array的区别
文章目录
1. 创建语法不同
Slice 创建语法
// 字面量初始化:创建一个拥有三个int元素的slice
slice := []int{1, 2, 3}
// 创建一个空切片,make: arg1 type{slice||chan||map}, arg2 大小
slice1 := make([]int, 0)
Array 创建语法
// 字面量初始化:创建一个拥有三个int元素的Array
array := [3]int{1,2,3}
// 定长空数组
array2 := [3]int{}
// 创建一个int类型的Array,使用[...] 表示该Array的大小由编译器自行推断
array2 := [...]int{1,2,3}
2. 可比性
Slice的可比性
Slice 不具备可比性,两个Slice无法进行比较。
Array的可比性
Array 具备可比性,两个Array满足以下两个条件即为true。
- 条件一:两个 Array元素个数相同。
- 条件二:满足条件一的基础上,每个下标的对应的元素值相等。
3.传递方式不同
Slice 的传递方式
Slice 为引用传递
Array 的传递方式
Array为值传递
与C语言中的数组显著不同的是,Go语言中的数组在赋值和函数调 用时的形参都是值复制。
如下代码段
func main() {
arr2 := [3]int{1, 2, 3}
temp := arr2
// 输出复制操作后,temp的地址与值
fmt.Printf("temp Addr: %p Value: %v\n", &temp, temp)
// 输出ChangeArray调用前arr2的地址、值
fmt.Printf("Befor func arr2 Addr: %p Value: %v\n", &arr2, arr2)
ChangeArray(arr2)
// 输出ChangeArray调用后arr2的地址、值
fmt.Printf("After func arr2 Addr: %p Value: %v\n", &arr2, arr2)
}
func ChangeArray(arr [3]int) {
arr[0] = 99
fmt.Printf("In func arr Addr: %p Value: %v\n", &arr, arr)
}
执行结果:赋值操作、函数传递都会触发数组复制
4. 底层原理不同
Array
Array的底层数据结构
在编译时数组将被解析成 Array,并由types.NewArray()
进行初始化数组的类型和大小。
// Array contains Type fields specific to array types.
type Array struct {
Elem *Type // element type
Bound int64 // number of elements; <0 if unknown yet
}
Array的初始化
数组在编译时构建抽象语法树阶段的数据类型为TARRAY,通过NewArray函数进行创建,AST节点的Op操作为OARRAYLIT。
// NewArray returns a new fixed-length array Type.
func NewArray(elem *Type, bound int64) *Type {
if bound < 0 {
base.Fatalf("NewArray: invalid bound %v", bound)
} t := New(TARRAY)
// 创建Type,内部指向Array
t.Extra = &Array{Elem: elem, Bound: bound}
t.SetNotInHeap(elem.NotInHeap())
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
Array 字面量初始化
在编译时还会进行重要的优化。在函数walk遍历阶段,anylit函数用于处理各种类型的字面量。当数组的长度小于4时,在运行时数组会被放置在栈中,当数组的长度大于4时,数组会被放置到内存的静态只读区。fixedlit函数将执行数组初始化与赋值的逻辑。
func anylit(n ir.Node, var_ ir.Node, init *ir.Nodes) {
.............
t := n.Type()
switch n.Op() {
case ir.OSTRUCTLIT, ir.OARRAYLIT:
n := n.(*ir.CompLitExpr)
if !t.IsStruct() && !t.IsArray() {
base.Fatalf("anylit: not struct/array")
}
if isSimpleName(var_) && len(n.List) > 4 {
// lay out static data
vstat := readonlystaticname(t)
ctxt := inInitFunction
if n.Op() == ir.OARRAYLIT {
ctxt = inNonInitFunction
}
fixedlit(ctxt, initKindStatic, n, vstat, init)
// copy static to var
appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, vstat))
// add expressions to automatic
fixedlit(inInitFunction, initKindDynamic, n, var_, init)
break
}
}
............
}
Slice
Slice 底层数据结构
Slice 在运行时的数据结构,由数据指针、长度、容量 三个字段组成
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Slice 的字面量初始化
当使用形如[]int{1,2,3}的字面量创建新的切片时,会创建一个array数组([3]int{1,2,3})存储于静态区中,并在堆区创建一个新的切片,在程序启动时将静态区的数据复制到堆区,这样可以加快切片的初始化过程。其核心逻辑位于cmd/compile/internal/gc.slicelit函数中。
Slice make初始化
使用make创建Slice时,编译时调用runtime.makeslice()
函数进行创建分配。
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(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()
}
return mallocgc(mem, et, true)
}
当make创建slice时,当slice占用的内存大于64KB时,该slice将会分配在堆上。MaxImplicitStackVarSize
变量规定了隐式创建的变量,大于64KB时将会分配在堆上。
var (
// 定义显示声明的变量,在栈上能分配的最大容量(10MB),若大于该值,显示变量将被分配在堆上
// maximum size variable which we will allocate on the stack.
// This limit is for explicit variable declarations like "var x T" or "x := ...".
// Note: the flag smallframes can update this value.
MaxStackVarSize = int64(10 * 1024 * 1024)
// 定义隐式声明的变量,在栈上能够分配的最大容量(64KB),若大于改值,隐式变量将被分配在堆上
// maximum size of implicit variables that we will allocate on the stack.
// p := new(T) allocating T on the stack
// p := &T{} allocating T on the stack
// s := make([]T, n) allocating [n]T on the stack
// s := []byte("...") allocating [n]byte on the stack
// Note: the flag smallframes can update this value.
MaxImplicitStackVarSize = int64(64 * 1024)
//MaxSmallArraySize 是被认为很小的数组的最大大小。小数组将直接使用一系列常量存储进行初始化。大型数组将通过从静态临时复制来初始化。选择 256 字节以最小化生成的代码 + statictmp 大小。
// MaxSmallArraySize is the maximum size of an array which is considered small.
// Small arrays will be initialized directly with a sequence of constant stores.
// Large arrays will be initialized by copying from a static temp.
// 256 bytes was chosen to minimize generated code + statictmp size.
MaxSmallArraySize = int64(256)
)
验证大于64KB的slice 是否分配在堆上,int在64位cpu上,占用8B内存,8192=2^13 = 8KB , 8KB * 8 = 64KB
package testSlice
func test() {
slice := make([]int, 8192)
_ = slice
slice1 := make([]int, 8193)
_ = slice1
}
通过命令检测内存逃逸:go build -gcflags=-m .\GomomTest.go
总结
Slice 和 Array 在创建语法、可比性、传递方式、底层原理上都存在不同点。