原文連接: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學習交流羣,歡迎各位大佬們踊躍入羣,咱們一塊兒學習交流。入羣方式:關注公衆號獲取。更多學習資料請到公衆號領取。
推薦往期文章: