首先,無心進行語言之爭,畢竟,PHP是世界上最好的語言,沒有之一。這個話題能夠停下來了。javascript
2016年已通過去,16年的年度語言給了go語言
,而正好這一年我都是用go用得比較多,並且版本從1.2一直用到了1.8,有一些感覺,來講說我對這個年度編程語言的一些粗淺理解吧。以前也寫過一篇go語言的文章,可是那時候用得還不是不少,有些特性沒有用上,因此理解上和今天的有些不一樣。java
這篇文章就不分什麼優點和劣勢了,想到哪裏說到哪裏。golang
先看一個小坑,可能不少初次接觸go的會遇到,go的range迭代用得也不少,下面這個例子不知道你以前遇到過沒有,其實值是不會變的,仍是1,2,3。編程
type a struct {
b int
}
func main() {
m := make([]a, 0)
m = append(m, a{b: 0, c: 0})
m = append(m, a{b: 1, c: 1})
m = append(m, a{b: 2, c: 2})
for _, e := range m {
e.b = 9
}
for _, x := range m {
fmt.Printf("%v\n", x.b)
}
}複製代碼
在range中,後面那個元素是值傳遞,這個很關鍵,因此修改不了元素的內容,並且若是元素很大的話,迭代的開銷仍是挺大的,因此要麼你就變成for idx, _ := range m
這樣的形式,用下標更新,要麼就變成m := make([]*a, 0)
這樣的指針,這樣雖然傳的仍是值,不過是個指針的值,一是開銷小,二是能夠直接修改元素內容了。json
因此說,指針在go中仍是不可或缺的一個存在,這也是爲何像我這種以前都是作C和C++的人喜歡go的緣由,由於仍是能夠指針滿天飛,寫出只能本身看懂的代碼出去裝逼,而後告訴別人,仍是有指針性能好啊。數組
若是你以前對指針沒概念,或者一直沒怎麼理解指針,那go可能要用好仍是要花點時間的,go確實入門很容易,但用好也不是那麼容易,以前我開始用的時候,沒仔細想過這方面的東西,並且特地減小了指針的使用,懼怕出現C中的野指針的狀況,後來越寫越以爲不是那個味道,go把指針這個功能保留下來仍是讓你用起來的,後來寫的代碼就又開始偏C風格了,指針處處飛。安全
雖然如此,但爲了安全性的考慮,go的指針仍是有一些侷限性的,各個類型之間的轉換是不行的,像C語言那樣把各類類型的變量經過指針轉來轉去是很難直接作到的,可是仍是給有這種需求的人給開了個口子,那就是unsafe
包,看這個包的名字就知道是警告你,這是不安全的啊,掛了別來找我,我出這個包只是爲了給你裝逼用的。微信
好比咱們有個需求,須要把一個結構體數組序列成一個byte數組後,還須要還原回來,通常的作法是序列化的方式,序列化成json或者用gob序列化成二進制,而後在反序列化回來,代碼通常是這樣的。數據結構
//do some append
jsonbyte, err := json.Marshal(YYY)
//do some thing
structArray,err:=json.Unmarshal(jsonbyte,&XXX)複製代碼
先不說序列化和反序列化都要耗費計算資源,影響速度,並且還有數據的拷貝,這對於一個高性能裝逼語言寫的高性能服務怎麼能忍,那隻能祭出指針神器了,而且還得用unsafe
包來加光環才行,通常狀況下,序列化的過程當中那次拷貝跑不掉,你總不須要須要序列化到自己吧,因此序列化的時候直接轉成byte數組,固然,須要記錄長度。app
buffer := new(bytes.Buffer)
err = binary.Write(buffer, binary.LittleEndian, YYY)
lens=len(YYY)
resBytes:=buffer.Buffer()複製代碼
這時候,YYY結構體數組就序列化成了resBytes這個byte數組了,長度是lens,反序列化的時候,直接用指針和unsafe
包就好了,整個過程沒有數據拷貝,也沒有序列化和反序列開銷,就像下面代碼同樣。
XXX := *(*[]structNode)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&resBytes[0])),
Len: int(lens),
Cap: int(lens),
}))複製代碼
固然,這種適合的是structNode
結構體裏面的元素是定長的,若是裏面的元素還有byte數組或者string的話,甚至是int
這種和操做系統體系結構相關的元素,就真的是unsafe
了,呵呵。
上面兩個例子,告訴咱們,指針在golang中保留下來後,對性能有強需求的開發仍是有好處的,而且,unsafe
包開放出來的功能,至少能讓你知道數據在底層到底存到什麼地址上,內心面也有底了。
對於map不是協程安全這一點,仍是有些想吐槽的,其餘語言不少也不是線程安全的,這原本沒什麼可說的,本身寫代碼的時候注意一下吧,可是golang自己就是以協程在語言中集成,開協程特別容易的語言,並且是鼓勵你們多多使用協程的思惟來編程,可是做爲一個基礎的集成到語言自己的數據結構,居然不是協程安全的,我去,您至少提供一個協程安全的版本讓你們去選啊,雖然加讀寫鎖比較容易實現,可是也有幾個問題:
要是有一個能夠選的map實現方式就行了,要競爭的時候選讀寫安全的,不競爭的時候選簡單粗暴的。
不少時候,咱們會由於GC的問題,想本身作一個內存池,比較主流的作法就是用管道的方式來申請釋放內存,如今也有sync.pool
包了。
可是用管道的方式來作內存池,只適合數組類型的數據,不適合map,由於數組的話,你只須要把len置爲0,cap不變,吐出去就好了,這樣會減小內存的申請開銷,可是map的話,不刪除key,這個key永遠在,因此想用內存池來申請map是不行的。
固然,通常狀況下也沒有語言能支持map的內存池,只不過由於go的管道概念,讓你們都以爲什麼均可以往裏面丟,作內存池的時候順便就把map給支持了,這個坑就大了。呵呵,我就是。。。。。。
若是一個map的value是一個結構體的話,那你不能用map[key].sturct.ele=XX
給這個map中的這個結構體的元素賦值,還好是編譯性的語言,會蹦一條編譯cannot assign to
錯誤出來,算個小坑吧,因爲map會在使用的過程當中不斷的申請新內存,拷貝對象到新內存中,因此直接的尋址是不支持的。
沒有泛型是不少人以爲go語言不夠人情味的一個地方,我也是其中之一,竟然沒有泛型,你叫人怎麼寫出裝逼的,簡潔的代碼??!!並且golang的設計者們竟然說不許備支持泛型(不過目前好像改口了,說Go2.0會考慮支持泛型,呵呵),這點簡直了,爲何不支持泛型,難道interface{}就夠用了?不停的類型判斷必然致使代碼的難看和性能的損失,這點都想不清楚嗎?可是。。。。。
可是若是咱們仔細想一想泛型的實現就稍微理解了他們了,首先,泛型的實現有兩種方式,一種是C++的模板方式,一種是JAVA的類型擦除(好像叫這個名字吧)方式,咱們來看看這兩種方式的泛型,再來猜猜看golang爲何不支持了。
*void
,這不就擦除了麼,而後用的時候再轉回來就好了哈。優點和劣勢也很明顯
好了,咱們簡單的說了一下泛型的原理,那麼若是go要實現泛型的話,基本上就是這兩種方式,第二種方式是否是感受和interface有種似曾相識的趕腳呢?恩,看上去同樣,仍是有本質區別的,第二種java那種方式是JIT實現,而interface是runtime的運行時實現,效率差得不是一點半點的。若是用第一種方式進行編譯時的模板擴展呢?一樣會遇到代碼增多的狀況,golang的目標文件原本就是把全部東西都集成進行來了,原本就很大了,再這麼整一下,估計目標文件更大了。
我以爲即使golang開放泛型,估計也是用第一種方式,由於若是用運行時的方式的話,給runtime調度器平增很多壓力,而golang確定不會用JIT吧,因此第二種實現方式估計有點夠嗆。
對於GC,就不吐槽了,由於畢竟,真有GC問題的話,我就用CGO了,呵呵,或者說在設計的時候就會直接考慮某些模塊用C來作了,並且目前的go版本,GC已經很不錯了,大部分應用沒啥問題了,golang把協程集成進語言中,勢必致使你們不計性能問題,奔放的開協程,那這個坑就只能google本身來填了,新版本(1.8)對GC的支持已經很好了,可是,對性能有強要求的服務,某些代碼,仍是用C吧,哈哈。
固然,要是go有一個不帶gc的實現,本身來管理內存的版本,那就行了,語法比C舒服,還有協程和管道這些東西,要是能本身管理堆內存的話,那就完美了。
最後,還有一些沒有說完的,這篇就不說了,下次接着聊聊channel和goroutine,以及和C的混合編程。
若是你以爲不錯,歡迎轉發給更多人看到,也歡迎關注個人公衆號,主要聊聊搜索,推薦,廣告技術,還有瞎扯。。文章會在這裏首先發出來:)掃描或者搜索微信號XJJ267或者搜索西加加語言就行