Go語言相關基礎
一、Golang的new和make方法
new和make方法是GO語言內建的兩個方法,主要用來建立分配型別記憶體,但二者有些許不同:
變數宣告:
var i int var str string
通過var關鍵字宣告變數,然後在程式中使用,我們不指定其預設值時,這些變數的預設值也是其零值,(PS:建議不要使用零值做一些特殊情況的判斷,會對結果產生影響)
對於引用型別,預設值為nil,看一下下面一段程式碼:
import ( "fmt" ) func main() { var i *int *i=10 fmt.Println(*i) }
結果會輸出什麼?
執行時會報panic,
panic: runtime error: invalid memory address or nil pointer dereference
對於引用型別的變數,我們不光要宣告它,還要為它分配內容空間,否則我們的值放在哪裡去呢?這就是上面錯誤提示的原因。PS:對於值型別的宣告不需要,因為已預設分配好了。
還是原來的例子,新增 i = new(int)
// 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
它只接受一個引數,這個引數是一個型別,分配好記憶體後,返回一個指向該型別記憶體地址的指標。同時請注意它同時把分配的記憶體置為零,也就是型別的零值。
再看一個例子:
func main() { u:=new(user) u.lock.Lock() u.name = "張三" u.lock.Unlock() fmt.Println(u) } type user struct { lock sync.Mutex name string age int }
示例中的user
型別中的lock
欄位我不用初始化,直接可以拿來用,不會有無效記憶體引用異常,因為它已經被零值了。
這就是new
,它返回的永遠是型別的指標,指向分配型別的記憶體地址。
make
make
也是用於記憶體分配的,但是和new
不同,它只用於chan
、map
以及切片的記憶體建立,而且它返回的型別就是這三個型別本身,而不是他們的指標型別,因為這三種類型就是引用型別,所以就沒有必要返回他們的指標了。
PS:因為這三種類型是引用型別,所以必須得初始化,但是不是置為零值,這個和new
是不一樣的。
func make(t Type, size ...IntegerType) Type
二者異同:
二者都是記憶體的分配(堆上),但是make
只用於slice、map以及channel的初始化(非零值);而new
用於型別的記憶體分配,並且記憶體置為零。
make
返回的還是這三個引用型別本身;而new
返回的是指向型別的指標。
new這個內建函式其實不常用,new的作用是可以分配記憶體讓我們使用。但現實中,我們直接使用短語句宣告和結構體的初始化達到我們的目的,例如:
x := 0 u := User{}
PS:一般工程中的結構體命名建議使用大寫,如TimeSlice,變數的宣告使用單駝峰的寫法,如timeSlice := TimeSlice{}
二、陣列和Slice
Go語言中陣列是具有固定長度而且擁有零個或者多個相同資料型別元素的序列。陣列長度固定,在Go語言中比較少直接使用。Slice長度可增可減,使用場合較多。
區別:
(1)陣列在使用的過程中都是值傳遞,將一個數組賦值給一個新變數或作為方法引數傳遞時,是將源陣列在記憶體中完全複製了一份,而不是引用源陣列在記憶體中的地址。
(2)每個Slice都是都源陣列在記憶體中的地址的一個引用,源陣列可以衍生出多個Slice。滿足了記憶體空間的複用和陣列元素的值的一致性的應用需求。
詳細看一下
1、陣列中每個元素是按照索引來訪問的,索引從0到陣列長度減1。Go語言內建函式len()可以返回陣列中的元素個數
// 初始化 var a [3] int //3個整數型的陣列,初始值是3個0 arr:=[5]int{1,2,3,4,5} //長度為5 var array2 = [...]int{6, 7, 8} //不宣告長度 q := [...] int {1,2,3} //不宣告長度 r := [...] int {99:-1} //長度為100的陣列,只有最後一個是-1,其他都是0
2、slice表示一個擁有相同型別元素的可變長度序列。
slice通常被寫為[]T,其中元素的型別都是T;它看上去就像沒有長度的陣列型別。
陣列和slice其實是緊密關聯的。slice可以看成是一種輕量級的資料結構,可以用來訪問陣列的部分或者全部元素,而這個陣列稱之為slice的底層陣列。
Slice有三個屬性:指標,長度和容量。指標指向陣列的第一個可以從slice中訪問的元素,這個元素不一定是陣列的第一個元素。長度指的是slice中的元素個數,不能超過slice的容量。指標通常是從指向陣列的第一個可以從slice中訪問的元素,這個元素不一定是陣列的第一個元素。長度指的是slice中的元素個數,它不能超過slice的容量。容量的大小通常大於等於長度,會隨著元素個數增多而動態變化。Go語言的內建函式len()和cap()用來返回slice的長度和容量。
//初始化 s1 := []int{1, 2, 3} //注意與陣列初始化的區別,在記憶體中構建一個包括有3個元素的陣列, //然後將這個陣列的應用賦值給s這個Slice a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //a是陣列 s2 := a[2:8] //從陣列中切片構建Slice s3 := make([]int, 10, 20) //make函式初始化,len=10,cap=20
len和cap關係
在新增元素時,若cap容量不足時,cap一般擴容2倍。
注:Slice的擴容規則
// 如果新的大小是當前大小2倍以上,則直接擴容為這個新的cap; // 否則迴圈以下操作:如果當前大小小於1024,按每次2倍增長,否則每次按當前大小1/4增長。直到增長的大小超過或等於新cap。
三、無緩衝 Channel 和有緩衝 Channel
首先,要了解一個概念:
阻塞:
- 在執行過程中暫停,以等待某個條件的觸發,我們就稱之為阻塞
我們建立Channel有兩種方式:
- 緩衝
channel
即buffer channel
建立方式為make(chan TYPE,SIZE)
- 如
make(chan int,3)
就是建立一個int
型別,緩衝大小為3
的channel
- 如
- 非緩衝
channel
即unbuffer channel
建立方式為make(chan TYPE)
- 如
make(chan int)
就是建立一個int
型別的非緩衝channel
- 如
c1:=make(chan int) 無緩衝 c2:=make(chan int,1) 有緩衝
無緩衝的 不僅僅是 向 c1 通道放 1 ,而是一直要有別的協程 <-c1 接手了 這個引數,那麼c1<-1才會繼續下去,要不然就一直阻塞著
而 c2<-1 則不會阻塞,因為緩衝大小是1 只有當 放第二個值的時候 第一個還沒被人拿走,這時候才會阻塞。
無緩衝Channel是同步的,有緩衝Channel是非同步的
// channel 中自帶緩衝區。建立時可以指定緩衝區的大小。 // w:直到緩衝區被填滿後,寫端才會阻塞。 // r:緩衝區被讀空,讀端才會阻塞。 // len:代表緩衝區中,剩餘元素個數, // cap:代表緩衝區的容量。 // 在這裡可以舉個小小的例子來解釋一下有緩衝channel和無緩衝channel // 同步通訊: 資料傳送端,和資料接收端,必須同時線上。 —— 無緩衝channel // 打電話。打電話只有等對方接收才會通,要不然只能阻塞 // 非同步通訊:資料傳送端,傳送完資料,立即返回。資料接收端有可能立即讀取,也可能延遲處理。 —— 有緩衝channel 不用等對方接受,只需傳送過去就行
// 發信息。簡訊
緩衝channel
的阻塞只會發生在channel
的緩衝使用完的情況下
package main import ( "fmt" "time" ) func loop(ch chan int) { for { select { case i := <-ch: fmt.Println("this value of unbuffer channel", i) } } } func main() { ch := make(chan int,3) ch <- 1 ch <- 2 ch <- 3 ch <- 4 go loop(ch) time.Sleep(1 * time.Millisecond) }
- 這裡也會報
fatal error: all goroutines are asleep - deadlock!
,這是因為channel
的大小為3
,而我們要往裡面塞4
個數據,所以就會阻塞住 - 解決的辦法有兩個
- 把
channel
開大一點,這是最簡單的方法,也是最暴力的 - 把
channel
的資訊傳送者ch <- 1
這些程式碼移動到go loop(ch)
下面 ,讓channel
實時消費就不會導致阻塞了
- 把