一、nil基础
每个语言基本都有null,java中的null,python中的NULL,之前以为golang中的nil和java的null差不多,但是后来发现其实还是有一些不同的
在go文档中,nil是预先声明的标识符,表示pointer,channel,func,interface,map或slice类型的零值。这意思是nil并不是一个关键字,并且只可以用于特定的类型。 这意思是我们可以 nil := “ok”,但是肯定不要这样了
声明一个变量,但是不赋值,这个变量默认拥有的是零值:12345678910bool -> falsenumbers -> 0string -> "" 与Java不一样pointers -> nilslices -> nilmaps -> nilchannels -> nilfunctions -> nilinterfaces -> nil
即:pointer、slice、map、channel、function、interface类型的零值是nil也就是说,可以理解为nil是一种预定义的类型,这个nil可以表示上面这几个类型的零值,也即nil可能有几种不同的类型后面通过%T来格式化输出,可以知道nil是有不同类型的但是要注意的是nil是没有默认类型的
12value := nil // 编译错误:use of untyped nil// goland 提示 Cannot assign nil without explicit type
这里并没有说struct的零值是什么,原因是struct的零值跟其属性有关123456789101112131415type Album struct {}type RandomStruct struct { key string value int offline bool}func main() { var a Album fmt.Println(a) // 输出{} var rs RandomStruct fmt.Println(rs) // 输出{ 0 false},因为string的零值是空字符串所以第一个是空}
也就是说,声明一个struct但是不赋值,其属性默认初始化成对应类型的零值
引用类型和值类型 a. 对于引用类型,其在栈上是一个指针,指向堆上的对象 b. golang中的值类型:int、float、bool、string、array、struct c. golang中的引用类型:pointer、slice、channel、interface、map、func
值传递和引用传递 a. 在函数调用时,golang默认都是按值传递,因此传array、struct时都是传副本,修改函数入参的array、struct不会影响外层调用者的array和struct b. 至于传指针,实际上也是传了其地址的副本,两个地址指向同一个对象 c. 讲道理golang中没有引用传递,都是传值的,就算函数入参是一个指针ptr2,和外层指针ptr1,地址也是不一样的,只不过ptr1和ptr2都指向了同一个对象,这个和java一样,只不过golang大概率需要显式地传一个指针,java把指针封装了注意点:map、slice、channel、struct
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980package mainimport "fmt"type Album struct { key string value int offline bool}func main() { a := Album{ key: "key1", value: 10, offline: true, } fmt.Printf("原始album的内存地址是:%p\n", &a) fmt.Println(a) updateAlbum(a) fmt.Println("updateAlbum后的album:", a) updateAlbumByPtr(&a) fmt.Println("updateAlbumByPtr后的album:", a) fmt.Println("===================================") m := make(map[string]int) m["key1"] = 100 fmt.Println(m) fmt.Printf("原始map的内存地址是:%p\n", m) updateMap(m) fmt.Println("updateMap后的map:", m) fmt.Println("===================================") s := []int{1, 2, 3} fmt.Printf("原始slice的内存地址是:%p\n", s) updateSlice(s) fmt.Println("updateSlice后的slice:", s)}func updateAlbum(a Album) { fmt.Printf("updateAlbum函数里接收到album的内存地址是:%p\n", &a) a.key = "updated key 1"}func updateAlbumByPtr(a *Album) { fmt.Printf("updateAlbumByPtr函数里接收到album的内存地址是:%p\n", &a) a.key = "updated key 2"}func updateMap(m map[string]int) { fmt.Printf("updateMap函数里接收到map的内存地址是:%p\n", &m) m["key1"] = 50}func updateSlice(s []int) { fmt.Printf("updateSlice函数里接收到slice的内存地址是:%p\n", &s) s[0] = 60}输出:原始album的内存地址是:0x11066020{key1 10 true}updateAlbum函数里接收到album的内存地址是:0x11066050updateAlbum后的album: {key1 10 true}updateAlbumByPtr函数里接收到album的内存地址是:0x11068040updateAlbumByPtr后的album: {updated key 2 10 true}===================================map[key1:100]原始map的内存地址是:0x1107a000updateMap函数里接收到map的内存地址是:0x11068050updateMap后的map: map[key1:50]===================================原始slice的内存地址是:0x11064054updateSlice函数里接收到slice的内存地址是:0x110660f0updateSlice后的slice: [60 2 3]
map和channel不用显式传指针,因为在go源码中都默认是创建的一个指针:runtime/map.go
runtime/chan.go
至于slice,传的实际上就是其底层数组的地址
12345type slice struct { array unsafe.Pointer len int cap int}
声明和赋值,内存分布以struct为例,大概了解一下结构体的内存空间12345678声明:声明一个struct,也会分配内存,并且以零值初始化其内存var p Person // 此时在内存中已经创建了一个结构体空间,存放p,&p的值是内存地址,该结构体的各项属性均为初始值(空串、零值)创建struct变量:1)直接声明,var a Album2)a := Album{},括号中可以直接赋值3)var a *Album = new(Album),new出来的是指向结构体的指针4)var a *Album = &Album{}
二、nil的大小、地址、类型不同的nil类型的占用的内存大小是不一样的,同一个nil类型大小是相同的对于指针类型,不同的nil指针类型的地址都是一样的,都是0x0,因为nil是预定义的类型
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354package mainimport ( "fmt" "sync" "unsafe")func main() { var b bool var s string var intPtr *int var aSlice []int var aMap map[string]int var aChannel chan int var aFunc func(string) int var err error var aInterface interface{} var mutex sync.Mutex fmt.Println("========================") fmt.Println(unsafe.Sizeof(b)) // 1 fmt.Println(unsafe.Sizeof(s)) // 8 fmt.Println(unsafe.Sizeof(intPtr)) // 4 fmt.Println(unsafe.Sizeof(aSlice)) // 12 fmt.Println(unsafe.Sizeof(aMap)) // 4 fmt.Println(unsafe.Sizeof(aChannel)) // 4 fmt.Println(unsafe.Sizeof(aFunc)) // 4 fmt.Println(unsafe.Sizeof(err)) // 8 fmt.Println(unsafe.Sizeof(aInterface)) // 8 fmt.Println(unsafe.Sizeof(mutex)) // 8 fmt.Println("========================") fmt.Printf("%p\n", intPtr) // 0x0 fmt.Printf("%p\n", aSlice) // 0x0 fmt.Printf("%p\n", aMap) // 0x0 fmt.Printf("%p\n", aChannel) // 0x0 fmt.Printf("%p\n", aFunc) // 0x0 fmt.Println("========================") fmt.Printf("%T\n", b) // bool fmt.Printf("%T\n", s) // string fmt.Printf("%T\n", intPtr) // *int fmt.Printf("%T\n", aSlice) // []int fmt.Printf("%T\n", aMap) // map[string]int fmt.Printf("%T\n", aChannel) // chan int fmt.Printf("%T\n", aFunc) // func(string) int fmt.Printf("%T\n", err) //
三、nil比较在java中,null值是可以比较的,并且结果是相等的
123public static void main(String[] args) { System.out.Println(null == null); // true}
但是go中,nil是具有类型的,不同类型的nil是不可以比较的
nil跟nil不能比较:
12fmt.Println(nil == nil)// 编译错误:invalid operation: nil == nil (operator == not defined on nil)
不同类型的nil不能比较,很明显两个类型是不能直接比较的
1234var intPtr *intvar array []intfmt.Println(intPtr == array)// invalid operation: intPtr == array (mismatched types *int and []int)
相同类型的nil 有时不能比较map、slice和function类型的nil值不能比较
1234567var a *intvar b *intfmt.Println(a == b) // 可以var s1 []intvar s2 []intfmt.Println(s1 == s2) // 不可以
Read More:
go语言的比较运算 和 Golang中的struct能不能比较,讲的是struct的比较
reflect.deepEqual()函数
自定义equals和hashcode
struct类型自定义排序,比如根据name字典序排序,或者是先根据age排序再根据name排序
四、注意点4.1 引用类型的nil和empty这里联系一下最开始讲的引用类型和值类型,其中map、slice、channel
对于一个nil的slice、map,是可以对其进行遍历的,这个跟java不一样,java中如果遍历一个null的引用类型会NPE。 但是,不能对nil的引用类型进行赋值
1234567891011var nilSlice []int // nilSlice 是nilfor i, v := range nilSlice { // 循环次数为0 fmt.Println(i, ":", v)}var nilMap map[string]int // nilMap 是nilfor k, v := range nilMap { // 循环次数为0 fmt.Printf("%s -> %d\n", k, v)}fmt.Println(nilMap["key1"]) // 输出0nilMap["key1"] = 1 // panic: assignment to entry in nil map
nil slice 和 empty slice: a. 我们知道slice底层引用的是一个数组,可以将slice看成[ pointer, length, capacity ],则: b. nil slice对应着[ nil, 0, 0 ],底层没有引用一个数组 c. empty slice对应着[ address, 0, 0 ],底层引用了一个数组
1234567891011// go源码 runtime/slice.gotype slice struct { array unsafe.Pointer len int cap int}// test codevar slice []int // nil sliceslice := make([]int, 0) // empty sliceslice := []int{} // empty slice
这里就要注意一些参数校验的场景,比如判断集合是nil,或者判断集合是否包含元素 注意在go中slice/array统一用len(a)>0来判断即可,不需要再重复判断是否为nil
1234// java, org.apache.commons.collections4.CollectionUtilspublic static boolean isEmpty(final Collection> coll) { return coll == null || coll.isEmpty();}
对于一个nil的array指针,其循环次数是其数组长度,但是如果数组长度不为0,且range遍历的时候不忽略第二个值,则会panic
1234567891011121314var nilArrayPtr *[3]intfmt.Pritnln(nilArrayPtr == nil) // truefor i, _ := range nilArrayPtr { fmt.Println(i) // 输出0 1 2}for i, v := range nilArrayPtr { fmt.Println(i, v) // panic: runtime error: invalid memory address or nil pointer dereference}var nilArrayPtr2 *[0]intfor i, v := range nilArrayPtr2 { fmt.Println(i, v) // 循环0次,不报错}
4.2 channel
channel有三种状态: a. nil,只声明但没有初始化 b. 正常使用,可读or可写 c. closed,已经关闭了,已经关闭的channel不是nil
close一个nil的channel会panic
close一个已经close的channel也会panic
读或者写一个nil的channel的操作会永远阻塞
给一个已经关闭的channel发送数据,引起panic
从一个已经关闭的channel接收数据,如果缓冲区中为空,则返回一个零值
无缓冲的channel是同步的,而有缓冲的channel是非同步的
4.3 interface
https://golang.google.cn/doc/faq#nil_error
https://research.swtch.com/interfaces
Go语言接口的原理
Go接口详解
Dig101-Go 之读懂 interface 的底层设计
go 接口断言效率
interface底层实际上可以理解为
也就是说,显式地将nil赋值给接口时,接口的type和value都将为nil,此时接口与nil值判断是相等的。但是如果将一个带有类型的nil赋值给接口时,只有value为nil,而type不为nil,此时接口与nil判断将不相等 a. 注意,这个type并不是interface type,而是存储的concrete type,接口类型变量的值不能存储接口变量类型本身的类型这也解释了为什么interface可以存储任意值
理解:
123var a interface{} // 等价于 var a interface{} = nilfmt.Println(reflect.TypeOf(a), reflect.ValueOf(a)) //
a. 第一行var a interface{}相当于 var a interface{} = nil,则a是interface类型的,可以用
123var a = (interface{})(nil)fmt.Println(reflect.TypeOf(a), reflect.ValueOf(a)) //
a. 相当于var a interface{} = (interface{})(nil),则a是interface类型,并且对应着
1234567891011121314var a interface{} = (*int)(nil)var b interface{} = (*interface{})(nil)fmt.Println(a == nil) // falsefmt.Println(b == nil) // falsevar c = (*int)(nil)var d = (*interface{}})(nil)fmt.Println(c == nil) // truefmt.Println(d == nil) // truefmt.Println(reflect.TypeOf(a), reflect.ValueOf(a)) // *int
a. var b interface{} = (interface{})(nil)代表着b是interface{}类型,且其对应着
12345678var a *intfmt.Println(a == nil) // truefmt.Println((interface{})(a) == nil) // false可以理解成如下:var b = (interface{})(a)fmt.Println(reflect.TypeOf(b), reflect.ValueOf(b)) // *int
a. 第一行到第三行相当于var b = (interface{})(int),又相当于var b interface{} = (interface{})(\int),则b是一个interface{}类型,对应<*int, nil>,因此不为nil
123456789func main() { var a *int fmt.Println(a == nil) // true fmt.Pritnln(CheckNull(a)) // false}func CheckNull(v interface{}) bool { return v == nil}
a. 这是因为将a传入CheckNull方法时,有一个隐含的类型转换,将int类型转换成了interface{}类型,对于CheckNull函数的入参v而言,其对应着
nil经常用在判断err上,这里也有值得注意的地方// TODO
4.4 sync.Mutex
sync.Mutex是互斥锁,只有Lock和UnLock两个public的方法
一般Lock完之后马上defer UnLock
sync.Mutex的零值表示了未被锁定的互斥量,源码:1234567891011121314151617181920212223242526272829303132333435// A Mutex is a mutual exclusion lock.// The zero value for a Mutex is an unlocked mutex.//// A Mutex must not be copied after first use.// Mutex是吸纳了Locker接口type Mutex struct { state int32 sema uint32}type Locker interface { Lock() Unlock()}const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexStarving mutexWaiterShift = iota starvationThresholdNs = 1e6}func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. // 通过CAS加锁,如果锁是没有 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } // Slow path (outlined so that the fast path can be inlined) m.lockSlow()}
4.5 sync.RWMutex
读写锁,多读单写
RWMutex的零值表示未加锁状态12345678910// A RWMutex is a reader/writer mutual exclusion lock.// The lock can be held by an arbitrary number of readers or a single writer.// The zero value for a RWMutex is an unlocked mutex.type RWMutex struct { w Mutex // held if there are pending writers writerSem uint32 // semaphore for writers to wait for completing readers readerSem uint32 // semaphore for readers to wait for completing writers readerCount int32 // number of pending readers readerWait int32 // number of departing readers}
五、nil struct 和 nil interface1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465package mainimport ( "context" "fmt")type AlbumService struct { ctx context.Context value int}func NewAlbumService(ctx context.Context, value int) *AlbumService { return &AlbumService{ ctx: ctx, value: value, }}func (s *AlbumService) SayHello() { fmt.Println("hello")}func (s AlbumService) SayHello2() { fmt.Println("hello2")}type AnotherService interface { SayHi()}type AnotherServiceImpl struct{}func (as *AnotherServiceImpl) SayHi() { fmt.Println("another service impl")}func main() { var s *AlbumService fmt.Println("s == nil?", s == nil) // s == nil? true s.SayHello() // 正常调用 // s.SayHello2() // NPE // fmt.Println(s.value) // NPE var s2 AlbumService s2.SayHello() // 正常调用 s2.SayHello2() // 正常调用 fmt.Println(s2.value) // 0 fmt.Println("s2 == nil?", s2 == nil) // 编译错误,struct不能和nil比较 // cannot convert nil to type AlbumService // nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type. // Type must be a pointer, channel, func, interface, map, or slice type s2 = *NewAlbumService(context.Background(), 100) s2.SayHello() // 正常 hello s2.SayHello2() // 正常 hello2 fmt.Println(s2.value) // 正常 100 var as AnotherService fmt.Println(as == nil) // true // as.SayHi() // NPE as = &AnotherServiceImpl{} as.SayHi() // 正常 another service impl}
从以上代码可以发现:
对于nil的pointer类型(var s *AlbumService),注意这个AlbumService是一个struct a. 如果该struct的某个方法的receiver是指针类型,那么可以通过一个nil的指针去调用 b. 如果某个方法的receiver是值类型,那么不可以通过nil的指针去调用该方法 c. 不能通过nil的指针去访问struct中的属性
var s2 AlbumService,此时声明了一个AlbumService结构体类型的变量s2,os已经为其内配内存了,且初始化为零值,因此可以通过s2来调用方法,访问属性(属性为零值)
struct不能喝nil比较,前面提到,只有特定的类型才可以和nil比较
对于nil的接口(var as AnotherService),不能通过它调用方法,只有将这个接口指向其对应的某个实现类才可以调用方法
六、nil的使用场景6.1 判断方法调用是否有error// TODO
七、empty的使用场景7.1 empty struct的使用
空struct{}代表不包含任何字段的结构体类型,不占用系统内存,在go源码中,所有空struct都返回相同的地址(Go1.6后有变化】,注意空struct也是可以寻址的
在channel中如果不需要传递更多信息,可以使用空struct作为元素类型,由于channel中传递的是副本,用空struct对内存更友好
在对数组去重的场景中,可以借助一个map,map的value可以是空struct类型,借此实现Set
123type Set struct { items map[interface{}]struct{}}
The empty struct
7.2 empty interface的使用
所有类型都实现了empty interface,因此empty interface可以用来存储任何类型
类型断言,comma,ok判断
不能直接把一个其他类型的slice赋值给一个empty interface类型的slice 官网wiki
类似泛型,用于可以接受任何类型的函数,如fmt.Println():
12func Println(a ...interface{}) (n int, err error) {...}func Printf(format string, a ...interface{}) (n int, err error) {...}
接口型函数,函数式编程go源码net/http/server.go中:
12345678910111213141516171819202122type Handler interface { ServeHTTP(ResponseWriter, *Request)}// The HandlerFunc type is an adapter to allow the use of// ordinary functions as HTTP handlers. If f is a function// with the appropriate signature, HandlerFunc(f) is a// Handler that calls f.type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)}
从这里可以抽取出这样一种编程模式(有点类似适配器模式的感觉):
1234567type I interface { Method(string) string // 定义入参、返回值}func API(I) {...} // 传入实现了接口的对象func API(func Method(string) string) {...} // 传入与接口中定义的函数签名一样的函数,这样传入的函数的函数名可以与Method的名称不一致
八、nil的优化其实就是对if/else的优化,以前在java中有一些优化null的手段:
工具类校验(Apache Utils、Guava)
Optional.ofNullable()
用map来扭转条件(表驱动)
策略模式 + 工厂(map注入)
…
在go中nil的优化:
利用多返回值,第二个返回值返回bool表示是否成功,根据这个来判断而不是判断nil
//待积累