Go Slice 与 Array的区别

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)
}

执行结果:赋值操作、函数传递都会触发数组复制

image-20220318143612599

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
}

image-20220623125806989

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

image-20220623132839665

总结

Slice 和 Array 在创建语法、可比性、传递方式、底层原理上都存在不同点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值