原文连接:Go语言中new和make你使用哪一个来分配内存?golang
哈喽,你们好,我是拖更很久的鸽子
asong
。由于5.1
去找女友,因此一直没有时间写文章啦,想着回来就抓紧学习,无奈,依然沉浸在5.1的甜蜜生活中,一拖再拖,就到如今啦。果真女人影响了我拔刀的速度,可是我很喜欢,略略略。面试好啦,不撒狗粮了,开始进入正题,今天咱们就来探讨一下
Go
语言中的make
和new
到底怎么使用?它们又有什么不一样?数组
new
官方文档定义:数据结构
// The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type
翻译出来就是:new
是一个分配内存的内置函数,第一个参数是类型,而不是值,返回的值是指向该类型新分配的零值的指针。
咱们日常在使用指针的时候是须要分配内存空间的,未分配内存空间的指针直接使用会使程序崩溃,好比这样:app
var a *int64 *a = 10
咱们声明了一个指针变量,直接就去使用它,就会使用程序触发panic
,由于如今这个指针变量a
在内存中没有块地址属于它,就没法直接使用该指针变量,因此new
函数的做用就出现了,经过new
来分配一下内存,就没有问题了:frontend
var a *int64 = new(int64) *a = 10
上面的例子,咱们是针对普通类型int64
进行new
处理的,若是是复合类型,使用new
会是什么样呢?来看一个示例:分布式
func main(){ // 数组 array := new([5]int64) fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{0, 0, 0, 0, 0} (*array)[0] = 1 fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{1, 0, 0, 0, 0} // 切片 slice := new([]int64) fmt.Printf("slice: %p %#v \n", &slice, slice) // slice: 0xc0000ae028 &[]int64(nil) (*slice)[0] = 1 fmt.Printf("slice: %p %#v \n", &slice, slice) // panic: runtime error: index out of range [0] with length 0 // map map1 := new(map[string]string) fmt.Printf("map1: %p %#v \n", &map1, map1) // map1: 0xc00000e038 &map[string]string(nil) (*map1)["key"] = "value" fmt.Printf("map1: %p %#v \n", &map1, map1) // panic: assignment to entry in nil map // channel channel := new(chan string) fmt.Printf("channel: %p %#v \n", &channel, channel) // channel: 0xc0000ae028 (*chan string)(0xc0000ae030) channel <- "123" // Invalid operation: channel <- "123" (send to non-chan type *chan string) }
从运行结果能够看出,咱们使用new
函数分配内存后,只有数组在初始化后能够直接使用,slice
、map
、chan
初始化后仍是不能使用,会触发panic
,这是由于slice
、map
、chan
基本数据结构是一个struct
,也就是说他里面的成员变量仍未进行初始化,因此他们初始化要使用make
来进行,make
会初始化他们的内部结构,咱们下面一节细说。仍是回到struct
初始化的问题上,先看一个例子:ide
type test struct { A *int64 } func main(){ t := new(test) *t.A = 10 // panic: runtime error: invalid memory address or nil pointer dereference // [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a89fd] fmt.Println(t.A) }
从运行结果得出使用new()
函数初始化结构体时,咱们只是初始化了struct
这个类型的,而它的成员变量是没有初始化的,因此初始化结构体不建议使用new
函数,使用键值对进行初始化效果更佳。函数
其实 new
函数在平常工程代码中是比较少见的,由于它是能够被代替,使用T{}
方式更加便捷方便。学习
make
在上一节咱们说到了,make
函数是专门支持 slice
、map
、channel
三种数据类型的内存建立,其官方定义以下:
// The make built-in function allocates and initializes an object of type // slice, map, or chan (only). Like new, the first argument is a type, not a // value. Unlike new, make's return type is the same as the type of its // argument, not a pointer to it. The specification of the result depends on // the type: // Slice: The size specifies the length. The capacity of the slice is // equal to its length. A second integer argument may be provided to // specify a different capacity; it must be no smaller than the // length. For example, make([]int, 0, 10) allocates an underlying array // of size 10 and returns a slice of length 0 and capacity 10 that is // backed by this underlying array. // Map: An empty map is allocated with enough space to hold the // specified number of elements. The size may be omitted, in which case // a small starting size is allocated. // Channel: The channel's buffer is initialized with the specified // buffer capacity. If zero, or the size is omitted, the channel is // unbuffered. func make(t Type, size ...IntegerType) Type
大概翻译最上面一段:make
内置函数分配并初始化一个slice
、map
或chan
类型的对象。像new
函数同样,第一个参数是类型,而不是值。与new
不一样,make
的返回类型与其参数的类型相同,而不是指向它的指针。结果的取决于传入的类型。
使用make
初始化传入的类型也是不一样的,具体能够这样区分:
Func Type T res make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of type T with initial space for approximately n elements make(T) channel unbuffered channel of type T make(T, n) channel buffered channel of type T, buffer size n
不一样的类型初始化可使用不一样的姿式,主要区别主要是长度(len)和容量(cap)的指定,有的类型是没有容量这一说法,所以天然也就没法指定。若是肯定长度和容量大小,能很好节省内存空间。
写个简单的示例:
func main(){ slice := make([]int64, 3, 5) fmt.Println(slice) // [0 0 0] map1 := make(map[int64]bool, 5) fmt.Println(map1) // map[] channel := make(chan int, 1) fmt.Println(channel) // 0xc000066070 }
这里有一个须要注意的点,就是slice
在进行初始化时,默认会给零值,在开发中要注意这个问题,我就犯过这个错误,致使数据不一致。
new
和make
区别总结new
函数主要是为类型申请一片内存空间,返回执行内存的指针make
函数可以分配并初始化类型所需的内存空间和结构,返回复合类型的自己。make
函数仅支持 channel
、map
、slice
三种类型,其余类型不可使用使用make
。new
函数在平常开发中使用是比较少的,能够被替代。make
函数初始化slice
会初始化零值,平常开发要注意这个问题。make
函数底层实现我仍是比较好奇make
底层实现是怎样的,因此执行汇编指令:go tool compile -N -l -S file.go
,咱们能够看到make
函数初始化slice
、map
、chan
分别调用的是runtime.makeslice
、runtime.makemap_small
、runtime.makechan
这三个方法,由于不一样类型底层数据结构不一样,因此初始化方式也不一样,咱们只看一下slice
的内部实现就行了,其余的交给你们本身去看,其实都是大同小异的。
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) }
这个函数功能其实也比较简单:
mallocgc
在堆上申请一片连续的内存。检查内存空间这里是根据切片容量进行计算的,根据当前切片元素的大小与切片容量的乘积得出当前内存空间的大小,检查溢出的条件有四个:
len
小于0
,cap
的大小只小于len
mallocgc
函数实现比较复杂,我暂时尚未看懂,不过也不是很重要,你们有兴趣能够自行学习。
new
函数底层实现new
函数底层主要是调用runtime.newobject
:
// implementation of new builtin // compiler (both frontend and SSA backend) knows the signature // of this function func newobject(typ *_type) unsafe.Pointer { return mallocgc(typ.size, typ, true) }
内部实现就是直接调用mallocgc
函数去堆上申请内存,返回值是指针类型。
今天这篇文章咱们主要介绍了make
和new
的使用场景、以及其不一样之处,其实他们都是用来分配内存的,只不过make
函数为slice
、map
、chan
这三种类型服务。平常开发中使用make
初始化slice
时要注意零值问题,不然又是一个p0
事故。
好啦,这篇文章到此结束啦,素质三连(分享、点赞、在看)都是笔者持续创做更多优质内容的动力!我是asong
,咱们下期见。
建立了一个Golang学习交流群,欢迎各位大佬们踊跃入群,咱们一块儿学习交流。入群方式:关注公众号获取。更多学习资料请到公众号领取。
推荐往期文章: