golang的inferface详解

转载:Golang中的Interface详解_Golang_脚本之家 (jb51.net)

背景:

golang的interface是一种satisfied式的。A类只要实现了IA interface定义的方法,A就satisfied了接口IA。更抽象一层,如果某些设计上需要一些更抽象的共性,比如print各类型,这时需要使用reflect机制,reflect实质上就是将interface的实现暴露了一部分给应用代码。要理解reflect,需要深入了解interface。

go的interface是一种隐式的interface,但golang的类型是编译阶段定的,是static的,如:

1

2

3

type MyInt int

var i int

var j MyInt

虽然MyInt底层就是int,但在编译器角度看,i的类型是int,j的类型是MyInt,是静态、不一致的。两者要赋值必须要进行类型转换。

即使是interface,就语言角度来看也是静态的。如:

1

var r io.Reader

不管r后面用什么来初始化,它的类型总是io.Reader。

更进一步,对于空的interface,也是如此。

记住go语言类型是静态这一点,对于理解interface/reflect很重要。

看一例:

1

2

3

4

5

6

var r io.Reader

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

if err != nil {

    return nil, err

}

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

2

3

4

5

6

7

8

type Binary uint64

func (i Binary) String() string {

    return strconv.Uitob64(i.Get(), 2)

}

  

func (i Binary) Get() uint64 {

    return uint64(i)

}

因为它实现了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

2

3

4

5

6

switch v := any.(type) {

case int:

    return strconv.Itoa(v)

case float:

    return strconv.Ftoa(v, 'g', -1)

}

实际上是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   var any interface{}  // initialized elsewhere

2   s := any.(Stringer)  // dynamic conversion

3   for i := 0; i < 100; i++ {

4       fmt.Println(s.String())

5   }

itable的计算不需要到函数调用时进行,只需要在interface赋值时进行即可,如上第2行,不需要在第4行进行。

最后,看一些实现代码:

以下是上面图中的两个字段。

143 type iface struct {

144     tab  *itab     // 指南itable

145     data unsafe.Pointer     // 指向真实数据

146 }

再看itab的实现:

1

2

3

4

5

6

7

8

617 type itab struct {

618     inter  *interfacetype

619     _type  *_type

620     link   *itab

621     bad    int32

622     unused int32

623     fun    [1]uintptr // variable sized

624 }

  

可见,它使用一个疑似链表的东西,可以猜这是用作hash表的拉链。

前两个字段应该是用来表达具体的interface类型和实际拥有的值的类型的,即一个itable的key。(上文提到的(interface类型, 具体类型) )

1

2

3

4

5

6

7

8

9

10

11

310 type imethod struct {

311     name nameOff

312     ityp typeOff

313 }

314

315 type interfacetype struct {

316     typ     _type

317     pkgpath name

318     mhdr    []imethod

319 }

320

interfacetype如有若干imethod,可以猜想这是表达interface定义的方法数据结构。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

28 type _type struct {

29     size       uintptr

30     ptrdata    uintptr // size of memory prefix holding all pointers

31     hash       uint32

32     tflag      tflag

33     align      uint8

34     fieldalign uint8

35     kind       uint8

36     alg        *typeAlg

37     // gcdata stores the GC type data for the garbage collector.

38     // If the KindGCProg bit is set in kind, gcdata is a GC program.

39     // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.

40     gcdata    *byte

41     str       nameOff

42     ptrToThis typeOff

43 }

对于_type,可见里面有gc的东西,应该就是具体的类型了。这里有个hash字段,itable实现就是挂在一个全局的hash table中。hash时用到了这个字段:

22 func itabhash(inter *interfacetype, typ *_type) uint32 {

23     // compiler has provided some good hash codes for us.

24     h := inter.typ.hash

25     h += 17 * typ.hash

26     // TODO(rsc): h += 23 * x.mhash ?

27     return h % hashSize

28 }

可见,这里有个把interface类型与具体类型之间的信息结合起来做一个hash的过程,这个hash就是上述的itab的存储地点,itab中的link就是hash中的拉链。

回到itab,看取一个itab的逻辑:

如果发生了typeassert或是interface的赋值(强转),需要临时计算一个itab。这时会先在hash表中找,找不到才会真实计算。

44     h := itabhash(inter, typ)

45

46     // look twice - once without lock, once with.

47     // common case will be no lock contention.

48     var m *itab

49     var locked int

50     for locked = 0; locked < 2; locked++ {

51         if locked != 0 {

52             lock(&ifaceLock)

53         }

54         for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {

55             if m.inter == inter && m._type == typ {

71                 return m    // 找到了前面计算过的itab

72             }

73         }

74     }

75    // 没有找到,生成一个,并加入到itab的hash中。

76     m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))

77     m.inter = inter

78     m._type = typ

79     additab(m, true, canfail)

  

这个hash是个全局变量:

13 const (

14     hashSize = 1009

15 )

16

17 var (

18     ifaceLock mutex // lock for accessing hash

19     hash      [hashSize]*itab

20 )

最后,看一下如何生成itab:

92     // both inter and typ have method sorted by name,

 93     // and interface names are unique,

 94     // so can iterate over both in lock step;

 95     // the loop is O(ni+nt) not O(ni*nt).       // 按name排序过的,因此这里的匹配只需要O(ni+nt)

 99     j := 0

100     for k := 0; k < ni; k++ {

101         i := &inter.mhdr[k]

102         itype := inter.typ.typeOff(i.ityp)

103         name := inter.typ.nameOff(i.name)

104         iname := name.name()

109         for ; j < nt; j++ {

110             t := &xmhdr[j]

111             tname := typ.nameOff(t.name)

112             if typ.typeOff(t.mtyp) == itype && tname.name() == iname {

118                     if m != nil {

119                         ifn := typ.textOff(t.ifn)

120                         *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,将实际类型的方法填入itab的fun

121                     }

122                     goto nextimethod

123                 }

124             }

125         }

135     nextimethod:

136     }

140     h := itabhash(inter, typ)             //插入上面的全局hash

141     m.link = hash[h]

142     atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))

143 }

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Go语言的reflect包提供了一种对程序的静态类型进行操作的方法,即可以在程序运行时动态地调用、检查和更改变量、类型和方法。 首先,reflect包提供了两个重要的类型:Type和Value。Type表示变量的类型,Value则表示变量的值。可以通过reflect.TypeOf和reflect.ValueOf来获取一个变量的Type和Value。 使用reflect包,我们可以在运行时获取变量的类型和值的一些基本信息,例如判断一个变量是否是某个特定类型,或者获取一个变量的名称和值。这在某些情况下可能是非常有用的,比如在编写通用的函数时,需要对不同类型的变量做相同的处理。 此外,reflect包还提供了一些函数来获取、设置和调用变量、类型和方法的具体信息。可以使用reflect.Value的相关方法来获取和设置变量的值,也可以使用reflect.Type的相关方法来获取类型的信息。使用reflect包还可以动态地调用某个值的方法。 需要注意的是,使用reflect包可能会导致一些性能上的损失,因为在运行时需要通过反射来获取变量的信息。因此,在性能要求较高的场景下,尽量避免使用反射。 总结而言,reflect包为我们提供了一种在运行时对变量、类型和方法进行操作的方法,可以通过反射来获取、设置和调用它们的信息。但是,需要注意在性能要求较高的情况下,尽量避免使用反射。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值