背景:
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。html
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的数据结构的框架。java
reflection实质上是将interface背后的实现暴露了一部分给应用代码,使应用程序可使用interface实现的一些内容。只要理解了interface的实现,reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value.
参考:
摘抄自他人博客