首先,无心进行语言之争,毕竟,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或者搜索西加加语言就行