浅析Go语言的Interface机制
前几日一朋友在学GO,问了我一些interface机制的问题。试着解释发现自己也不是太清楚,所以今天下午特意查了资料和阅读GO的源码(基于go1.4),整理出了此文。如果有错误的地方还望指正。
GO语言的interface是我比较喜欢的特性之一。interface与struct之间可以相互转换,struct不需要像JAVA在源码中显示说明实现了某个接口,可以通过约定的形式,隐式的转换到interface,还可以在运行时查询接口类型,这样有种用动态语言写代码的感觉,但是又可以在编译时进行检查,捕捉一些明显的类型不匹配的错误。
type Stringer interface {
String() string
} type S struct {
i int
} func (s *S) String() string {
return fmt.Sprintf("%d", s.i)
} func Print(s Stringer) {
println(s.String())
} func DynamicPrint(any interface{}) {
if s, ok := any.(Stringer); ok {
Print(s)
}
} func main() {
var s S
s.i = 123456789
Print(&s)
DynamicPrint(&s)
}
如上面的代码所示,类型S没有显示的实现Stringer接口,但是它的方法列表符合Stringer接口,所以可以转换为Stringer接口使用。
那么,GO语言的interface机制到底是如何实现的呢?
interface value
上述代码中函数Print的参数是一个Stringer接口,也就是Stringer的一个对象实例。这个对象实例叫做interface value。它的数据结构如下:
type iface struct {
tab *itab
data unsafe.Pointer
}
其中tab字段类似于C++的vptr,tab中包含了对应的方法数组,除此之外还保存了实现该接口的类型元数据。data是对应的实现该接口的类型的实例指针。
itab数据结构如下:
type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
unused int32
fun [0]unsafe.Pointer
}
其中inter字段表示这个interface value所属的接口元信息,_type字段表示具体实现类型的元信息,fun字段表示该interface的方法数组。link,bad,unused字段暂时不关心。
当我们在GO代码中调用一个接口的方法时,操作类似如下: s.tab->fun[0](s.data)。调用开销还是很小的。
Itab的生成方式
一个自定义的结构体可以实现某个接口,然后可以隐式的转换到对应的接口。这种操作有点像C++的派生类转换为基类一样,这个操作是一个运行时绑定过程。而GO语言的interface机制还有一些其他特性:比如一个具体类型可以实现N多方法,但是只有其中某几个或者全部都满足某个接口,而此时,不可能把所有的方法都放到Itab中,这就意味着需要在绑定过程中剔除某些不需要的方法。
GO编译器会在编译时会为每个自定义结构体和interface类型生成一个类型元数据,用来描述这个类型的名称,类型的HASH值,类型的方法列表,方法列表中还包括了方法的名称。而在一个自定义结构体转换到一个interface类型时,GO编译器会生成代码,使其在运行时计算Itab,完成动态绑定方法的需求。这个计算Itab的过程相对来说比较简单,因为GO编译器生成的类型元数据中包含了所有的方法名称和地址,那么在一个结构体实例转换为interface value时,只需要把interface的方法列表作为基,方法名和方法类型作为KEY,去结构体元数据中查找对应的方法即可。
GO的runtime库中对Itab的查找过程做了优化,由O(ni * nt)复杂度变为O(ni + nt)。依据是一个自定义结构体实现的方法一定是大于或等于某个具体interface的方法集的。所以可以事先把所有的方法按照名字从小到大排序,然后在匹配到一个方法后,可以在下次查找时使用上次的索引值。
除此之外,GO编译器为了减少每次不必要的Itab,还增加了一个对应的itab的缓存。你可以编译一个GO程序,然后反编译后可以查看到一个类似go_itab__main_S_main_Stringer名称的变量。在每次一个结构体转换到一个interface之前都会检查这个缓存是否有效,有效就使用。这个检查也只是一个cmp指令而已。
还有在GO运行时库里,为了减少每次的Itab实现,还做了相应的优化。内部实现了一个HASH表,保存了每个具体结构体到interface转换生成的Itab实例。代码可以在go\src\runtime\iface.go getitab函数中看到。
interface{}的特殊处理
interface{}在GO中是一个特殊的内建类型,类似于C/C++中的void*,但是包含了类型信息。所以你可以把任意的数据转换到interface{},然后通过type assert从interface{}获取原有的数据。但是正如你所见,interface{}没有方法,那么也就是说,它不需要iface中的itab,因为不需要方法绑定。针对此,做了特殊修改,iface中的tab字段类型由itab指针变为了对应的具体实现类型的类型元数据指针。在GO源码中,interface{}对象的类型原型如下:
type eface struct {
_type *_type
data unsafe.Pointer
}
eface是empty interface的缩写。
其他
在GO的源码iface.go中,还可以看到很多函数比如叫assertE2E,assertE2I,assertE2T等,这些函数就是对应的type assert的具体实现函数。E表示eface,I表示iface,T表示自定义的结构体或者基于内建类型创造出的类型。代码都比较简单,不在叙述了。
总结
想理解interface机制的实现,只需要理解类型元数据以及动态绑定过程。其中要还区分interface value,也就是内部的iface结构体。因此引出了Itable的概念。整体来说不是太复杂,数据结构也比较简单,如果你有时间的话,也可以自己看下GO的源码。
参考
GO源码(go\src\runtime\iface.go)
《Go Data Structures: Interfaces》
浅析Go语言的Interface机制的更多相关文章
- 《Java疯狂讲义》(第3版)学习笔记 2 - Java语言的运行机制
内容 1.高级语言的运行机制 2.Java 语言的运行机制 1.高级语言的运行机制 高级语言主要分为编译型语言和解释型语言两类. 编译型语言是指使用专门的编译器.针对特定平台(操作系统)将高级语言源代 ...
- Go语言之Interface(一)
Go语言之Interface(一) 什么是interface 在面向对象语言中接口是:接口定义了一个对象的行为,但在Go中接口就是方法签名的集合,当一个类型提供了这个接口中的所有的方法,就可以说这个类 ...
- Atitit.跨语言异常转换机制 java c# php到js的异常转换
Atitit.跨语言异常转换机制 java c# php到js的异常转换 1. bizEx 直接抓取,然后js catchEX1 2. Chkec runtimeEx1 3. Other异常..J ...
- C语言使用信号量机制实例:
C语言使用信号量机制实例: #include <signal.h> #include <unistd.h> #include <stdio.h> #include ...
- 浅析Java虚拟机结构与机制[转]
本文旨在给所有希望了解JVM(Java Virtual Machine)的同学一个概念性的入门,主要介绍了JVM的组成部分以及它们内部工作的机制和原理.当然本文只是一个简单的入门,不会涉及过多繁杂的参 ...
- 浅析OC语言
学习一门开发语言,首先要掌握的它的基本语法,这可能几天就能学会,但如果要融会贯通,就得去学习这门语言的框架和一些库,再结合一些项目的应用,这可能需要花几年的时间. OC是C语言的一个超集,是一门面向对 ...
- OC:浅析Runtime中消息转发机制
一.介绍 OC是一门动态性语言,其实现的本质是利用runtime机制.在runtime中,对象调用方法,其实就是给对象发送一个消息,也即objc_msgSend().在这个消息发送的过程中,系统会进行 ...
- 浅解 go 语言的 interface(许的博客)
我写了一个 go interface 相关的代码转换为 C 代码的样例.也许有助于大家理解 go 的 interface.不过请注意一点,这里没有完整解析 go 语言 interface 的所有细节. ...
- C语言栈调用机制初探
学习linux离不开c语言,也离不开汇编,二者之间的相互调用在源代码中几乎随处可见.所以必须清楚地理解c语言背后的汇编结果才能更好地读懂linux中相关的代码.否则会有很多疑惑,比如在head.s中会 ...
随机推荐
- 重温Http协议--请求报文和响应报文
http协议是位于应用层的协议,我们在日常浏览网页比如在导航网站请求百度首页的时候,会先通过http协议把请求做一个类似于编码的工作,发送给百度的服务器,然后在百度服务器响应请求时把相应的内容再通过h ...
- PHP搭建大文件切割分块上传功能
背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示"该文件过大".因为一般情况下,我们都需要对上传的文件大小做限制,防止出现 ...
- Js new到底发生了什么
在Js中,我们使用了new关键字来进行实例化 那么在这个new的过程中到底发生了什么? 关于构造函数的return 正常来讲构造函数中是不用写return语句的,因为它会默认返回新创建的对象. 但是, ...
- 02.SQLServer性能优化之---牛逼的OSQL----大数据导入
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 上一篇:01.SQLServer性能优化之----强大的文件组----分盘存储 http ...
- 一步一步教你用CSS画爱心
今天小颖给大家分享一个用CSS画的爱心,底下有代码和制作过程,希望对大家有所帮助. 第一步: 先画一个正方形.如图: <!DOCTYPE html> <html> <he ...
- MVC Core 网站开发(Ninesky) 2.1、栏目的前台显示(补充)
在2.1.栏目的前台显示中因右键没有添加视图把微软给鄙视了一下,后来有仔细研究了一下发现应该鄙视自己,其实这个功能是有的,是自己没搞清楚乱吐糟. 其实只要在NuGet中安装两个包(Microsoft. ...
- 协议森林17 我和你的悄悄话 (SSL/TLS协议)
作者:Vamei 出处:http://www.cnblogs.com/vamei 转载请先与我联系. TLS名为传输层安全协议(Transport Layer Protocol),这个协议是一套加密的 ...
- python开发环境搭建
虽然网上有很多python开发环境搭建的文章,不过重复造轮子还是要的,记录一下过程,方便自己以后配置,也方便正在学习中的同事配置他们的环境. 1.准备好安装包 1)上python官网下载python运 ...
- Java进击C#——前言
本章简言 记得三年前笔者来到现在的公司的时候,公司人口不出十个人.那个时候笔者刚从日本回来,想在福州.厦门.青岛找一个合适自己发展的机会.最后我的一个福州的朋友打电话希望我能过去帮他,跟他一起创业.这 ...
- 【Linux大系】Linux的概念与体系
感谢原作者:Vamei 出处:http://www.cnblogs.com/vamei 我在这一系列文章中阐述Linux的基 本概念.Linux操作系统继承自UNIX.一个操作系统是一套控制和使用计算 ...