18

从底层深入Go的基础模型 - interface

 3 years ago
source link: https://studygolang.com/articles/31479
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

interface

In Object-oriented programming, a protocol or interface is a common means for unrelated Object (computer science) to communicate with each other. These are definitions of Method (computer programming) and values which the objects agree upon in order to co-operate. —— wiki

Qv2Mja.png!mobile

0 interface是什么

在wiki中是这样定义的,interface和protocol类似,都是双方为了交流而作出的约定。

直接拿这套定义去理解golang中的interface可能难以理解,那么可以换种说法,即interface就像包饺子。

具体类型就像饺子馅,而预先定义的接口则就是饺子皮,也就是对具体类型进行了一层封装后,放入桌上(itabTable),随需随拿。最后吃的仍然是馅。

(真正使用的仍然是接口中包着的具体类型方法)

01.

为什么要使用interface

nQFrq2n.png!mobile

1.1 写一个通用的函数

在golang中不支持泛型,如果不使用interface的话,需要考虑不同类型的入参,复制粘贴n份,累得慌。

而有了interface之后,各种类型都能封装成interface,因此间接的实现了泛型编程,能够用来写接受多种类型入参的函数。

这一点可以利用来做单元测试 (mock入参) ,以及各服务之间的解偶 (比如只提供一个redis的增删改查接口,实现层可随意替换实现方式不影响业务) 。

1.2 隐藏具体实现

用户只能使用interface提供的方法,而具体的实现细节则不需要暴露。(典型案例context.Context)

1.3 提供插入点

有点像java的静态代理,就是在调用函数的时候,在前面做点别的事情。

举个例子就是在http请求之前加header的实现:

3eQbAvZ.png!mobile

02.

interface的结构

uiUfuqi.png!mobile

interface存在两种interface类型,eface和iface。

2.1 eface

qIju63.png!mobile

eface顾名思义 empty interface,没有定义方法的interface底层结构即为eface。

eface只有_type以及指向数据(拷贝)的指针。

2.2 iface

7fmMNnI.png!mobile

定义了方法的interface底层结构为iface。

iface则还有定义接口方法,因此有有一个tab属性以及指向数据(拷贝)的指针。

fuEFVfM.png!mobile

03.

itabTable的设计

nQFrq2n.png!mobile

YZvA7bm.png!mobile

ZBnmUbm.png!mobile

uiUfuqi.png!mobile

3.1 什么是itabTable

golang有一个itabTable哈希表,即利用空间换时间的思路,存放所有的itab,具体实现方式则通过一个数组(entries)实现。

3.2 如何对itab进行哈希

取itab中的接口类型与实际类型,分别哈希后取异或。

yQZFRnY.png!mobile

3.3 itabTable哈希表的寻址方式

——(二次寻址法)

itabTable作为一个哈希表,插入和读取肯定不可能是每次遍历整个数组,这样非常耗费性能。

因此go中itabTable使用的是quadratic probing。

公式为**h(i) = h0 + i(i+1)/2 mod 2^k

uMNbq2I.png!mobile

h(i):目标位置

h0:起点,也就是一开始 将itab哈希之后的值 。(在这里对itab哈希的实现是通过将interfacetype和itab分别哈希之后异或获得)

i(i+1)/2:用于防止哈希冲突**,其实就是趋向于1+2+3+4+5+6...的函数表达式,在实现中是一个for循环,不断增加偏移量,比如一开始a+1,如果bucket已被占位或不是目标内容,则下一次找a+1+2的位置,还不是就a+1+2+3。为了防止一直递增超过哈希表(数组)的大小,所以加一个mod 2^k(mod数组的长度)

其实也就是一种 二次寻址法的实现

04.

itabTable的增与删

nQFrq2n.png!mobile

4.1 itab的初始化——init方法

itab需要初始化之后才能插入itabTable。

遍历接口类型与具体类型比较具体类型是否实现了所有接口类型, 理论上时间复杂度为O(n^2)。

但是实际上因为接口类型与具体类型的插入都是按照字典序排序的,因此实际上 时间复杂度为O(mn) ,使用双指针遍历即可。

zYr6nu.png!mobile

4.2 插入itab至itabTable(add方法)

itab使用接口类型(interfaceType)以及具体类型(_type)初始化之后,就能将itab放置与itabTable。

使用的插入方法正是之前的 二次寻址法

RF7fQrU.png!mobile

fquueq7.png!mobile

4.3 在itabTable中寻找itab(find方法)

在itabTable中根据接口类型以及具体类型寻找itab。

使用的搜索模式也是 二次寻址法

6JnmEf6.png!mobile

05.

汇编验证

nQFrq2n.png!mobile

代码皆不可信,我们再用汇编来证实一下以上的思路。

j67n6rN.png!mobile

汇编代码:

mE3aimU.png!mobile

nAN3UrA.png!mobile

6ruUJni.png!mobile

iYFN32M.png!mobile

06.

思考

nQFrq2n.png!mobile

全文读至此,应该能读懂runtime.convI2I方法了

有兴趣的可以根据上文的内容,解读一下上面汇编中使用的方法。

yUf2m2b.png!mobile

qm2UVj.png!mobile

ZV7VN3U.png!mobile

END

文|【无线平台】李晨毅

关注我们

分享技术好文

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK