背景:
golang的interface是一种satisfied式的。A类只要实现了IA interface定义的方法,A就satisfied了接口IA。更抽象一层,如果某些设计上需要一些更抽象的共性,比如print各类型,这时需要使用reflect机制,reflect实质上就是将interface的实现暴露了一部分给应用代码。要理解reflect,需要深入了解interface。
 
go的interface是一种隐式的interface,但golang的类型是编译阶段定的,是static的,如:
  1. type MyInt int
  2. var i int
  3. var j MyInt
虽然MyInt底层就是int,但在编译器角度看,i的类型是int,j的类型是MyInt,是静态、不一致的。两者要赋值必须要进行类型转换。
即使是interface,就语言角度来看也是静态的。如:
 
  1. var r io.Reader
不管r后面用什么来初始化,它的类型总是io.Reader。
更进一步,对于空的interface,也是如此。
 
记住go语言类型是静态这一点,对于理解interface/reflect很重要。
 
看一例:
  1. var r io.Reader
  2. tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
  3. if err != nil {
  4. return nil, err
  5. }
  6. r = tty

到这里,r的类型是什么?r的类型仍然是interface io.Reader,只是r = tty这一句,隐含了一个类型转换,将tty转成了io.Reader。

 
interface的实现:
作为一门编程语言,对方法的处理一般分为两种类型:一是将所有方法组织在一个表格里,静态地调用(C++, java);二是调用时动态查找方法(python, smalltalk, js)。
而go语言是两者的结合:虽然有table,但是是需要在运行时计算的table。
 
如下例:
Binary类实现了两个方法,String()和Get()
 
  1. type Binary uint64
  2. func (i Binary) String() string {
  3. return strconv.Uitob64(i.Get(), 2)
  4. }
  5.  
  6. func (i Binary) Get() uint64 {
  7. return uint64(i)
  8. }
因为它实现了String(),按照golang的隐式方法实现来看,Binary satisfied了Stringer接口。因此它可以赋值: s:=Stringer(b)。
以此为例来说明下interface的实现:
 
interface的内存组织如图:
一个interface值由两个指针组成,第一个指向一个interface table,叫 itable。itable开头是一些描述类型的元字段,后面是一串方法。注意这个方法是interface本身的方法,并非其dynamic value(Binary)的方法。即这里只有String()方法,而没有Get方法。但这个方法的实现肯定是具体类的方法,这里就是Binary的方法。
 
当这个interface无方法时,itable可以省略,直接指向一个type即可。
 
另一个指针data指向dynamic value的一个拷贝,这里则是b的一份拷贝。也就是,给interface赋值时,会在堆上分配内存,用于存放拷贝的值。
同样,当值本身只有一个字长时,这个指针也可以省略。
 
 
一个interface的初始值是两个nil。比如,
  1. var w io.Writer
这时,tab和data都是nil。interface是否为nil取决于itable字段。所以不一定data为nil就是nil,判断时要额外注意。
 
这样,像这样的代码:
  1. switch v := any.(type) {
  2. case int:
  3. return strconv.Itoa(v)
  4. case float:
  5. return strconv.Ftoa(v, 'g', -1)
  6. }
 
实际上是any这个interface取了  any. tab->type。
 
而interface的函数调用实际上就变成了:
s.tab->fun[0](s.data)。第一个参数即自身类型指针。
 
itable的生成:
itable的生成是理解interface的关键。
如刚开始处提的,为了支持go语言这种接口间仅通过方法来联系的特性,是没有办法像C++一样,在编译时预先生成一个method table的,只能在运行时生成。因此,自然的,所有的实体类型都必须有一个包含此类型所有方法的“类型描述符”(type description structure);而interface类型也同样有一个类似的描述符,包含了所有的方法。
这样,interface赋值时,计算interface对象的itable时,需要对两种类型的方法列表进行遍历对比。如后面代码所示,这种计算只需要进行一次,而且优化成了O(m+n)。
 
可见,interface与itable之间的关系不是独立的,而是与interface具体的value类型有关。即(interface类型, 具体类型)->itable。
  1. 1 var any interface{} // initialized elsewhere
  2. 2 s := any.(Stringer) // dynamic conversion
  3. 3 for i := 0; i < 100; i++ {
  4. 4 fmt.Println(s.String())
  5. 5 }
itable的计算不需要到函数调用时进行,只需要在interface赋值时进行即可,如上第2行,不需要在第4行进行。
 
最后,看一些实现代码:
以下是上面图中的两个字段。
  1. 143 type iface struct {
  2. 144 tab *itab // 指南itable
  3. 145 data unsafe.Pointer // 指向真实数据
  4. 146 }
再看itab的实现:
  1. 617 type itab struct {
  2. 618 inter *interfacetype
  3. 619 _type *_type
  4. 620 link *itab
  5. 621 bad int32
  6. 622 unused int32
  7. 623 fun [1]uintptr // variable sized
  8. 624 }

  

可见,它使用一个疑似链表的东西,可以猜这是用作hash表的拉链。
前两个字段应该是用来表达具体的interface类型和实际拥有的值的类型的,即一个itable的key。(上文提到的(interface类型, 具体类型) )
  1. 310 type imethod struct {
  2. 311 name nameOff
  3. 312 ityp typeOff
  4. 313 }
  5. 314
  6. 315 type interfacetype struct {
  7. 316 typ _type
  8. 317 pkgpath name
  9. 318 mhdr []imethod
  10. 319 }
  11. 320
interfacetype如有若干imethod,可以猜想这是表达interface定义的方法数据结构。
  1. 28 type _type struct {
  2. 29 size uintptr
  3. 30 ptrdata uintptr // size of memory prefix holding all pointers
  4. 31 hash uint32
  5. 32 tflag tflag
  6. 33 align uint8
  7. 34 fieldalign uint8
  8. 35 kind uint8
  9. 36 alg *typeAlg
  10. 37 // gcdata stores the GC type data for the garbage collector.
  11. 38 // If the KindGCProg bit is set in kind, gcdata is a GC program.
  12. 39 // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
  13. 40 gcdata *byte
  14. 41 str nameOff
  15. 42 ptrToThis typeOff
  16. 43 }
 
对于_type,可见里面有gc的东西,应该就是具体的类型了。这里有个hash字段,itable实现就是挂在一个全局的hash table中。hash时用到了这个字段:
  1. 22 func itabhash(inter *interfacetype, typ *_type) uint32 {
  2. 23 // compiler has provided some good hash codes for us.
  3. 24 h := inter.typ.hash
  4. 25 h += 17 * typ.hash
  5. 26 // TODO(rsc): h += 23 * x.mhash ?
  6. 27 return h % hashSize
  7. 28 }
可见,这里有个把interface类型与具体类型之间的信息结合起来做一个hash的过程,这个hash就是上述的itab的存储地点,itab中的link就是hash中的拉链。
 
回到itab,看取一个itab的逻辑:
如果发生了typeassert或是interface的赋值(强转),需要临时计算一个itab。这时会先在hash表中找,找不到才会真实计算。
  1. 44 h := itabhash(inter, typ)
  2. 45
  3. 46 // look twice - once without lock, once with.
  4. 47 // common case will be no lock contention.
  5. 48 var m *itab
  6. 49 var locked int
  7. 50 for locked = 0; locked < 2; locked++ {
  8. 51 if locked != 0 {
  9. 52 lock(&ifaceLock)
  10. 53 }
  11. 54 for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
  12. 55 if m.inter == inter && m._type == typ {
  13. 71 return m // 找到了前面计算过的itab
  14. 72 }
  15. 73 }
  16. 74 }
  17. 75 // 没有找到,生成一个,并加入到itab的hash中。
  18. 76 m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
  19. 77 m.inter = inter
  20. 78 m._type = typ
  21. 79 additab(m, true, canfail)

  

 
这个hash是个全局变量:
  1. 13 const (
  2. 14 hashSize = 1009
  3. 15 )
  4. 16
  5. 17 var (
  6. 18 ifaceLock mutex // lock for accessing hash
  7. 19 hash [hashSize]*itab
  8. 20 )
最后,看一下如何生成itab:
  1. 92 // both inter and typ have method sorted by name,
  2. 93 // and interface names are unique,
  3. 94 // so can iterate over both in lock step;
  4. 95 // the loop is O(ni+nt) not O(ni*nt). // 按name排序过的,因此这里的匹配只需要O(ni+nt)
  5. 99 j := 0
  6. 100 for k := 0; k < ni; k++ {
  7. 101 i := &inter.mhdr[k]
  8. 102 itype := inter.typ.typeOff(i.ityp)
  9. 103 name := inter.typ.nameOff(i.name)
  10. 104 iname := name.name()
  11. 109 for ; j < nt; j++ {
  12. 110 t := &xmhdr[j]
  13. 111 tname := typ.nameOff(t.name)
  14. 112 if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
  15. 118 if m != nil {
  16. 119 ifn := typ.textOff(t.ifn)
  17. 120 *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,将实际类型的方法填入itab的fun
  18. 121 }
  19. 122 goto nextimethod
  20. 123 }
  21. 124 }
  22. 125 }
  23. 135 nextimethod:
  24. 136 }
  25. 140 h := itabhash(inter, typ) //插入上面的全局hash
  26. 141 m.link = hash[h]
  27. 142 atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
  28. 143 }

到这里,interface的数据结构的框架。

reflection实质上是将interface背后的实现暴露了一部分给应用代码,使应用程序可以使用interface实现的一些内容。只要理解了interface的实现,reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value.
 
参考:

golang的interface剖析的更多相关文章

  1. Golang 源码剖析:log 标准库

    Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...

  2. Golang的Interface是个什么鬼

    问题概述 Golang的interface,和别的语言是不同的.它不需要显式的implements,只要某个struct实现了interface里的所有函数,编译器会自动认为它实现了这个interfa ...

  3. golang 关于 interface 的学习整理

    Golang-interface(四 反射) go语言学习-reflect反射理解和简单使用 为什么在Go语言中要慎用interface{} golang将interface{}转换为struct g ...

  4. Golang 的 `[]interface{}` 类型

    Golang 的 []interface{} 类型 我其实不太喜欢使用 Go 语言的 interface{} 类型,一般情况下我宁愿多写几个函数:XxxInt, XxxFloat, XxxString ...

  5. Golang接口(interface)三个特性(译文)

    The Laws of Reflection 原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍. go是一个静态性语言,每个变量都 ...

  6. Golang-interface(四 反射)

    github:https://github.com/ZhangzheBJUT/blog/blob/master/reflect.md 一 反射的规则 反射是程序执行时检查其所拥有的结构.尤其是类型的一 ...

  7. golang之interface

    一.概述 接口类型是对 "其他类型行为" 的抽象和概况:因为接口类型不会和特定的实现细节绑定在一起:很多面向对象都有类似接口概念,但Golang语言中interface的独特之处在 ...

  8. golang将interface{}转换为struct

    项目中需要用到golang的队列,container/list,需要放入的元素是struct,但是因为golang中list的设计,从list中取出时的类型为interface{},所以需要想办法把i ...

  9. Golang-interface(二 接口与nil)

    github: https://github.com/ZhangzheBJUT/blog/blob/master/nil.md 一 接口与nil 前面解说了go语言中接口的基本用法,以下将说一说nil ...

随机推荐

  1. python 经典博客链接

    1, 从文件的读取与输出: http://www.cnblogs.com/xuxn/archive/2011/07/27/read-a-file-with-python.html http://www ...

  2. LeetCode112:Path Sum

    正常写法 bool HasPathSum(TreeNode root, int sum) { bool ret=false; if(root==null)return false; if(root.l ...

  3. 使用Windows API进行串口编程

    使用Windows API进行串口编程   串口通信一般分为四大步:打开串口->配置串口->读写串口->关闭串口,还可以在串口上监听读写等事件.1.打开和关闭串口Windows中串口 ...

  4. pycharm shortcut

    Alt+F12 is a shortcut to open/hide Terminal panel

  5. 阿里云ubuntu 16.04搭建odoo11服务器

    ubuntu 16.04 具体如何搭建odoo11网站的具体步骤可以参考这一篇文章 按上面的文章配置环境后,自己网站的启动具体步骤如下: 1.登录阿里云 [远程连接],进入命令行界面1 2.cd到目录 ...

  6. React + js-xlsx构建Excel文件上传预览功能

    首先要准备react开发环境以及js-xlsx插件 1. 此处省略安装react安装步骤 2.下载js-xlsx插件 yarn add xlsx 或者 npm install xlsx 在项目中引入 ...

  7. 通用漏洞评估方法CVSS3.0简表

    CVSS3.0计算分值共有三种维度: 1. 基础度量. 分为 可利用性 及 影响度 两个子项,是漏洞评估的静态分值. 2. 时间度量. 基础维度之上结合受时间影响的三个动态分值,进而评估该漏洞的动态分 ...

  8. openhtmltopdf 支持自定义字体、粗体

    一.支持自定义字体 private static void renderPDF(String html, OutputStream outputStream) throws Exception { t ...

  9. RSA公钥文件解密密文的原理分析

    前言 最近在学习RSA加解密过程中遇到一个这样的难题:假设已知publickey公钥文件和加密后的密文flag,如何对其密文进行解密,转换成明文~~ 分析 对于rsa算法的公钥与私钥的产生,我们可以了 ...

  10. 开启C语言的学习之门

    本人是一枚工业界的码农,为了职业道路越来越宽广决定向上位机方面进军,C语言曾经在大学里面学过点皮毛但是离应用远远不够,尽量每天在工作之余更新自己学习的进度,同时也希望有大神能给予在编程道路上的指导,话 ...