深入Golang的接口interface
来源:脚本之家
时间:2022-12-24 17:07:40 369浏览 收藏
本篇文章向大家介绍《深入Golang的接口interface》,主要包括接口、interface,具有一定的参考价值,需要的朋友可以参考一下。
空接口类型可以接收任意类型的数据:
type eface struct { // _type 指向接口的动态类型元数据 // 描述了实体类型、包括内存对齐方式、大小等 _type *_type // data 指向接口的动态值 data unsafe.Pointer }
空接口在赋值时,_type 和 data 都是nil。赋值后,_type 会指向赋值的数据元类型,data 会指向该值
非空接口是有方法列表的接口类型,一个变量要赋值给非空接口,就要实现该接口里的所有方法
type iface struct { // tab 接口表指针,指向一个itab实体,存储方法列表和接口动态类型信息 tab *itab // data 指向接口的动态值(一般是指向堆内存的) data unsafe.Pointer } // layout of Itab known to compilers // allocated in non-garbage-collected memory // Needs to be in sync with // ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs. type itab struct { // inter 指向interface的类型元数据,描述了接口的类型 inter *interfacetype // _type 描述了实体类型、包括内存对齐方式、大小等 _type *_type // hash 从动态类型元数据中拷贝的hash值,用于快速判断类型是否相等 hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte // fun 记录动态类型实现的那些接口方法的地址 // 存储的是第一个方法的函数指针 // 这些方法是按照函数名称的字典序进行排列的 fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. } // interfacetype 接口的类型元数据 type interfacetype struct { typ _type // 记录定义了接口的包名 pkgpath name // mhdr 标识接口所定义的函数列表 mhdr []imethod }
itab是可复用的,go会将itab缓存起来,构造一个hash表用于查出和查询缓存信息
如果itab缓存中有,可以直接拿来使用,如果没有,则新创建一个itab,并放入缓存中
一个Iface中的具体类型中实现的方法会被拷贝到Itab的fun数组中
// Note: change the formula in the mallocgc call in itabAdd if you change these fields. type itabTableType struct { size uintptr // length of entries array. Always a power of 2. count uintptr // current number of filled entries. entries [itabInitSize]*itab // really [size] large } func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. // 用接口类型hash和动态类型hash进行异或 return uintptr(inter.typ.hash ^ typ.hash) }
接口类型和nil作比较
接口值的零值指接口动态类型和动态值都为nil,当且仅当此时接口值==nil
如何打印出接口的动态类型和值?
定义一个iface结构体,用两个指针来描述itab和data,然后将具体遍历在内存中的内容强行解释为我们定义的iface
type iface struct{ itab, data uintptr } func main() { var a interface{} = nil var b interface{} = (*int)(nil) x := 5 var c interface{} = (*int)(&x) ia := *(*iface)(unsafe.Pointer(&a)) ib := *(*iface)(unsafe.Pointer(&b)) ic := *(*iface)(unsafe.Pointer(&c)) fmt.Println(ia, ib, ic) fmt.Println(*(*int)(unsafe.Pointer(ic.data))) } // 输出 // {0 0} {17426912 0} {17426912 842350714568} // 5
检测类型是否实现了接口:
赋值语句会发生隐式的类型转换,在转换过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数
// 检查 *myWriter 类型是否实现了 io.Writer 接口 var _ io.Writer = (*myWriter)(nil) // 检查 myWriter 类型是否实现了 io.Writer 接口 var _ io.Writer = myWriter{}
接口转换的原理
将一个接口转换为另一个接口:
- 实际上是调用了convI2I
- 如果目标是空接口或和目标接口的inter一样就直接返回
- 否则去获取itab,然后将值data赋入,再返回
// 接口间的赋值实际上是调用了runtime.convI2I // 实际上是要找到新interface的tab和data // convI2I returns the new itab to be used for the destination value // when converting a value with itab src to the dst interface. func convI2I(dst *interfacetype, src *itab) *itab { if src == nil { return nil } if src.inter == dst { return src } return getitab(dst, src._type, false) } // 关键函数,获取itab // getitab根据interfacetype和_type去全局的itab哈希表中查找,如果找到了直接返回 // 否则根据inter和typ新生成一个itab,插入到全局itab哈希表中 // // 查找了两次,第二次上锁了,目的是可能会写入hash表,阻塞其余协程的第二次查找 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab { // 函数列表为空 if len(inter.mhdr) == 0 { throw("internal error - misuse of itab") } // easy case if typ.tflag&tflagUncommon == 0 { if canfail { return nil } name := inter.typ.nameOff(inter.mhdr[0].name) panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()}) } var m *itab // First, look in the existing table to see if we can find the itab we need. // This is by far the most common case, so do it without locks. // Use atomic to ensure we see any previous writes done by the thread // that updates the itabTable field (with atomic.Storep in itabAdd). // 使用原子性去保证我们能看见在该线程之前的任意写操作 // 确保更新全局hash表的字段 t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable))) // 遍历一次,找到了就返回 if m = t.find(inter, typ); m != nil { goto finish } // 没找到就上锁,再试一次 // Not found. Grab the lock and try again. lock(&itabLock) if m = itabTable.find(inter, typ); m != nil { unlock(&itabLock) goto finish } // hash表中没找到itab就新生成一个itab // Entry doesn't exist yet. Make a new entry & add it. m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys)) m.inter = inter m._type = typ // The hash is used in type switches. However, compiler statically generates itab's // for all interface/type pairs used in switches (which are added to itabTable // in itabsinit). The dynamically-generated itab's never participate in type switches, // and thus the hash is irrelevant. // Note: m.hash is _not_ the hash used for the runtime itabTable hash table. m.hash = 0 m.init() // 加到全局的hash表中 itabAdd(m) unlock(&itabLock) finish: if m.fun[0] != 0 { return m } if canfail { return nil } // this can only happen if the conversion // was already done once using the , ok form // and we have a cached negative result. // The cached result doesn't record which // interface function was missing, so initialize // the itab again to get the missing function name. panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()}) } // 查找全局的hash表,有没有itab // find finds the given interface/type pair in t. // Returns nil if the given interface/type pair isn't present. func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab { // Implemented using quadratic probing. // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k. // We're guaranteed to hit all table entries using this probe sequence. mask := t.size - 1 // 根据inter,typ算出hash值 h := itabHashFunc(inter, typ) & mask for i := uintptr(1); ; i++ { p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize)) // Use atomic read here so if we see m != nil, we also see // the initializations of the fields of m. // m := *p m := (*itab)(atomic.Loadp(unsafe.Pointer(p))) if m == nil { return nil } // inter和typ指针都相同 if m.inter == inter && m._type == typ { return m } h += i h &= mask } } // 核心函数。填充itab // 检查_type是否符合interface_type并且创建对应的itab结构体将其放到hash表中 // init fills in the m.fun array with all the code pointers for // the m.inter/m._type pair. If the type does not implement the interface, // it sets m.fun[0] to 0 and returns the name of an interface function that is missing. // It is ok to call this multiple times on the same m, even concurrently. func (m *itab) init() string { inter := m.inter typ := m._type x := typ.uncommon() // both inter and typ have method sorted by name, // and interface names are unique, // so can iterate over both in lock step; // the loop is O(ni+nt) not O(ni*nt). // inter和typ的方法都按方法名称进行排序 // 并且方法名是唯一的,因此循环次数的固定的 // 复杂度为O(ni+nt),而不是O(ni*nt) ni := len(inter.mhdr) nt := int(x.mcount) xmhdr := (*[1 = 3*(t.size/4) { // 75% load factor // Grow hash table. // t2 = new(itabTableType) + some additional entries // We lie and tell malloc we want pointer-free memory because // all the pointed-to values are not in the heap. t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true)) t2.size = t.size * 2 // Copy over entries. // Note: while copying, other threads may look for an itab and // fail to find it. That's ok, they will then try to get the itab lock // and as a consequence wait until this copying is complete. iterate_itabs(t2.add) if t2.count != t.count { throw("mismatched count during itab table copy") } // Publish new hash table. Use an atomic write: see comment in getitab. atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2)) // Adopt the new table as our own. t = itabTable // Note: the old table can be GC'ed here. } // 核心函数 t.add(m) } // 核心函数 // add adds the given itab to itab table t. // itabLock must be held. func (t *itabTableType) add(m *itab) { // See comment in find about the probe sequence. // Insert new itab in the first empty spot in the probe sequence. // 在探针序列第一个空白点插入itab mask := t.size - 1 // 计算hash值 h := itabHashFunc(m.inter, m._type) & mask for i := uintptr(1); ; i++ { p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize)) m2 := *p if m2 == m { // itab已被插入 // A given itab may be used in more than one module // and thanks to the way global symbol resolution works, the // pointed-to itab may already have been inserted into the // global 'hash'. return } if m2 == nil { // Use atomic write here so if a reader sees m, it also // sees the correctly initialized fields of m. // NoWB is ok because m is not in heap memory. // *p = m // 使用原子操作确保其余的goroutine下次查找的时候可以看到他 atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m)) t.count++ return } h += i h &= mask } } // hash函数,编译期间提供较好的hash codes func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. return uintptr(inter.typ.hash ^ typ.hash) }
具体类型转空接口时,_type 字段直接复制源类型的 _type;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。
具体类型转非空接口时,入参 tab 是编译器在编译阶段预先生成好的,新接口 tab 字段直接指向入参 tab 指向的 itab;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。
而对于接口转接口,itab 调用 getitab 函数获取。只用生成一次,之后直接从 hash 表中获取。
实现多态
- 多态的特点
- 一种类型具有多种类型的能力
- 允许不同的对象对同一消息做出灵活的反应
- 以一种通用的方式对待多个使用的对象
- 非动态语言必须通过继承和接口的方式实现
实现函数的内部,接口绑定了实体类型,会直接调用fun里保存的函数,类似于s.tab->fun[0],而fun数组中保存的是实体类型实现的函数,当函数传入不同实体类型时,实际上调用的是不同的函数实现,从而实现了多态
好了,本文到此结束,带大家了解了《深入Golang的接口interface》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多Golang知识!
-
230 收藏
-
250 收藏
-
462 收藏
-
288 收藏
-
394 收藏
-
457 收藏
-
309 收藏
-
225 收藏
-
485 收藏
-
233 收藏
-
322 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 尊敬的秋天
- 这篇技术文章出现的刚刚好,太细致了,感谢大佬分享,码起来,关注作者了!希望作者能多写Golang相关的文章。
- 2023-05-01 11:54:18
-
- 欣慰的雪糕
- 这篇文章真及时,太详细了,很好,码起来,关注博主了!希望博主能多写Golang相关的文章。
- 2023-03-05 05:46:52
-
- 忧虑的大象
- 这篇文章内容真及时,太详细了,真优秀,码起来,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-02-14 04:19:36
-
- 柔弱的含羞草
- 受益颇多,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢老哥分享文章!
- 2023-02-04 07:27:31
-
- 光亮的老鼠
- 这篇博文出现的刚刚好,细节满满,很好,码住,关注作者了!希望作者能多写Golang相关的文章。
- 2023-02-02 06:41:05
-
- 舒适的芹菜
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢up主分享博文!
- 2023-01-14 06:28:17
-
- 忧郁的灯泡
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢师傅分享文章内容!
- 2023-01-06 03:04:20
-
- 俊秀的睫毛
- 太详细了,码起来,感谢大佬的这篇文章,我会继续支持!
- 2023-01-03 23:49:59
-
- 欢呼的曲奇
- 这篇技术贴真及时,很详细,写的不错,码住,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2022-12-28 17:41:20