隐藏具体实现,这个很好理解。比如我设计一个函数给你返回一个 interface,那么你只能通过 interface 里面的方法来做一些操作,但是内部的具体实现是完全不知道的。
例如我们常用的context包,就是这样的,context 最先由 google 提供,现在已经纳入了标准库,而且在原有 context 的基础上增加了:cancelCtx,timerCtx,valueCtx。
刚好前面我们有专门说过context,现在再来回顾一下
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }}
表明上 WithCancel 函数返回的还是一个 Context interface,但是这个 interface 的具体实现是 cancelCtx struct。
// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent,done: make(chan struct{}),}}// A cancelCtx can be canceled. When canceled, it also cancels any children// that implement canceler.type cancelCtx struct {Context //注意一下这个地方done chan struct{} // closed by the first cancel call.mu sync.Mutexchildren map[canceler]struct{} // set to nil by the first cancel callerr error // set to non-nil by the first cancel call}func (c *cancelCtx) Done() <-chan struct{} {return c.done}func (c *cancelCtx) Err() error {c.mu.Lock()defer c.mu.Unlock()return c.err}func (c *cancelCtx) String() string {return fmt.Sprintf("%v.WithCancel", c.Context)}
尽管内部实现上下面三个函数返回的具体 struct &#xff08;都实现了 Context interface&#xff09;不同&#xff0c;但是对于使用者来说是完全无感知的。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) //返回 cancelCtxfunc WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtxfunc WithValue(parent Context, key, val interface{}) Context //返回 valueCtx
providing interception points
暂无更多&#xff0c;待补充
interface 源码分析
说了这么多&#xff0c; 然后可以再来瞧瞧具体源码的实现
interface 底层结构
根据 interface 是否包含有 method&#xff0c;底层实现上用两种 struct 来表示&#xff1a;iface 和 eface。eface表示不含 method 的 interface 结构&#xff0c;或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构&#xff0c;同时针对不同的类型还会有一些其他信息。
type eface struct {_type *_typedata unsafe.Pointer}type _type struct {size uintptr // type sizeptrdata uintptr // size of memory prefix holding all pointershash uint32 // hash of type; avoids computation in hash tablestflag tflag // extra type information flagsalign uint8 // alignment of variable with this typefieldalign uint8 // alignment of struct field with this typekind uint8 // enumeration for Calg *typeAlg // algorithm tablegcdata *byte // garbage collection datastr nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero}
iface 表示 non-empty interface 的底层实现。相比于 empty interface&#xff0c;non-empty 要包含一些 method。method 的具体实现存放在 itab.fun 变量里。
type iface struct {tab *itabdata unsafe.Pointer}// layout of Itab known to compilers// allocated in non-garbage-collected memory// Needs to be in sync with// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.type itab struct {inter *interfacetype_type *_typelink *itabbad int32inhash int32 // has this itab been added to hash?fun [1]uintptr // variable sized}
试想一下&#xff0c;如果 interface 包含多个 method&#xff0c;这里只有一个 fun 变量怎么存呢&#xff1f;
其实&#xff0c;通过反编译汇编是可以看出的&#xff0c;中间过程编译器将根据我们的转换目标类型的 empty interface 还是 non-empty interface&#xff0c;来对原数据类型进行转换&#xff08;转换成 <_type, unsafe.Pointer> 或者 <itab, unsafe.Pointer>&#xff09;。这里对于 struct 满不满足 interface 的类型要求&#xff08;也就是 struct 是否实现了 interface 的所有 method&#xff09;&#xff0c;是由编译器来检测的。
iface 之 itab
iface 结构中最重要的是 itab 结构。itab 可以理解为 pair
type itab struct {inter *interfacetype_type *_typelink *itabbad int32inhash int32 // has this itab been added to hash?fun [1]uintptr // variable sized}
其中 interfacetype 包含了一些关于 interface 本身的信息&#xff0c;比如 package path&#xff0c;包含的 method。上面提到的 iface 和 eface 是数据类型&#xff08;built-in 和 type-define&#xff09;转换成 interface 之后的实体的 struct 结构&#xff0c;而这里的 interfacetype 是我们定义 interface 时候的一种抽象表示。
type interfacetype struct {typ _typepkgpath namemhdr []imethod}type imethod struct { //这里的 method 只是一种函数声明的抽象&#xff0c;比如 func Print() errorname nameOffityp typeOff}
_type 表示 concrete type。fun 表示的 interface 里面的 method 的具体实现。比如 interface type 包含了 method A, B&#xff0c;则通过 fun 就可以找到这两个 method 的具体实现。
interface的内存布局
了解interface的内存结构是非常有必要的&#xff0c;只有了解了这一点&#xff0c;我们才能进一步分析诸如类型断言等情况的效率问题。先看一个例子&#xff1a;
type Stringer interface {String() string}type Binary uint64func (i Binary) String() string {return strconv.Uitob64(i.Get(), 2)}func (i Binary) Get() uint64 {return uint64(i)}func main() {b :&#61; Binary{}s :&#61; Stringer(b)fmt.Print(s.String())}
根据上面interface的源码实现&#xff0c;可以知道&#xff0c;interface在内存上实际由两个成员组成&#xff0c;如下图&#xff0c;tab指向虚表&#xff0c;data则指向实际引用的数据。虚表描绘了实际的类型信息及该接口所需要的方法集
![Uploading interface内存布局_731644.png]
观察itable的结构&#xff0c;首先是描述type信息的一些元数据&#xff0c;然后是满足Stringger接口的函数指针列表&#xff08;注意&#xff0c;这里不是实际类型Binary的函数指针集哦&#xff09;。因此我们如果通过接口进行函数调用&#xff0c;实际的操作其实就是s.tab->fun0。是不是和C&#43;&#43;的虚表很像&#xff1f;接下来我们要看看golang的虚表和C&#43;&#43;的虚表区别在哪里。
先看C&#43;&#43;&#xff0c;它为每种类型创建了一个方法集&#xff0c;而它的虚表实际上就是这个方法集本身或是它的一部分而已&#xff0c;当面临多继承时&#xff08;或者叫实现多个接口时&#xff0c;这是很常见的&#xff09;&#xff0c;C&#43;&#43;对象结构里就会存在多个虚表指针&#xff0c;每个虚表指针指向该方法集的不同部分&#xff0c;因此&#xff0c;C&#43;&#43;方法集里面函数指针有严格的顺序。许多C&#43;&#43;新手在面对多继承时就变得蛋疼菊紧了&#xff0c;因为它的这种设计方式&#xff0c;为了保证其虚表能够正常工作&#xff0c;C&#43;&#43;引入了很多概念&#xff0c;什么虚继承啊&#xff0c;接口函数同名问题啊&#xff0c;同一个接口在不同的层次上被继承多次的问题啊等等……就是老手也很容易因疏忽而写出问题代码出来。
我们再来看golang的实现方式&#xff0c;同C&#43;&#43;一样&#xff0c;golang也为每种类型创建了一个方法集&#xff0c;不同的是接口的虚表是在运行时专门生成的。可能细心的同学能够发现为什么要在运行时生成虚表。因为太多了&#xff0c;每一种接口类型和所有满足其接口的实体类型的组合就是其可能的虚表数量&#xff0c;实际上其中的大部分是不需要的&#xff0c;因此golang选择在运行时生成它&#xff0c;例如&#xff0c;当例子中当首次遇见s :&#61; Stringer(b)这样的语句时&#xff0c;golang会生成Stringer接口对应于Binary类型的虚表&#xff0c;并将其缓存。
理解了golang的内存结构&#xff0c;再来分析诸如类型断言等情况的效率问题就很容易了&#xff0c;当判定一种类型是否满足某个接口时&#xff0c;golang使用类型的方法集和接口所需要的方法集进行匹配&#xff0c;如果类型的方法集完全包含接口的方法集&#xff0c;则可认为该类型满足该接口。例如某类型有m个方法&#xff0c;某接口有n个方法&#xff0c;则很容易知道这种判定的时间复杂度为O(mXn)&#xff0c;不过可以使用预先排序的方式进行优化&#xff0c;实际的时间复杂度为O(m&#43;n)。
interface 与 nil 的比较
引用公司内部同事的讨论议题&#xff0c;觉得之前自己也没有理解明白&#xff0c;为此&#xff0c;单独罗列出来&#xff0c;例子是最好的说明&#xff0c;如下
package mainimport ("fmt""reflect")type State struct{}func testnil1(a, b interface{}) bool {return a &#61;&#61; b}func testnil2(a *State, b interface{}) bool {return a &#61;&#61; b}func testnil3(a interface{}) bool {return a &#61;&#61; nil}func testnil4(a *State) bool {return a &#61;&#61; nil}func testnil5(a interface{}) bool {v :&#61; reflect.ValueOf(a)return !v.IsValid() || v.IsNil()}func main() {var a *Statefmt.Println(testnil1(a, nil))fmt.Println(testnil2(a, nil))fmt.Println(testnil3(a))fmt.Println(testnil4(a))fmt.Println(testnil5(a))}
返回结果如下
falsefalsefalsetruetrue
为啥呢&#xff1f;
一个interface{}类型的变量包含了2个指针&#xff0c;一个指针指向值的类型&#xff0c;另外一个指针指向实际的值
对一个interface{}类型的nil变量来说&#xff0c;它的两个指针都是0&#xff1b;但是var a *State传进去后&#xff0c;指向的类型的指针不为0了&#xff0c;因为有类型了&#xff0c; 所以比较为false。 interface 类型比较&#xff0c; 要是 两个指针都相等&#xff0c; 才能相等。
【"欢迎关注我的微信公众号&#xff1a;Linux 服务端系统研发&#xff0c;后面会大力通过微信公众号发送优质文章"】
image.png
18人点赞
Golang
更多精彩内容&#xff0c;就在简书APP
"欢迎关注我的微信公众号&#xff1a;Linux 服务端系统研发&#xff0c;后面会大力通过微信公众号发送优质文章"
赞赏支持还没有人赞赏&#xff0c;支持一下正在上传…重新上传取消
吴德宝AllenWu服务端开发、C/C&#43;&#43;、Golang、IM、架构、区块链、容器、istio
总资产6共写了6.7W字获得217个赞共223个粉丝
关注
被以下专题收入&#xff0c;发现更多相似内容
正在上传…重新上传取消Go正在上传…重新上传取消golang正在上传…重新上传取消Go知识库正在上传…重新上传取消我爱编程正在上传…重新上传取消Golang
推荐精彩内容
-
无标题文章
转至元数据结尾创建&#xff1a; 董潇伟&#xff0c;最新修改于&#xff1a; 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
正在上传…重新上传取消40c0490e5268阅读 1,035评论 0赞 9
-
百日长红——紫薇&#xfeff;[26]
百花诗 26 (紫薇) 娇弱紫衣百日红&#xff0c;害羞怕痒满身风。 夏秋开放显尊贵&#xff0c;好运来临情爱浓。
正在上传…重新上传取消PikeTalk阅读 52评论 0赞 1
-
checkebox全选反选成功一次后失效
最近在项目中使用jQuery的attr方法获取和设置复选框的”checked”属性&#xff0c;发现第一次全选/取消全选有效&#xff0c;...
正在上传…重新上传取消邢泽川阅读 220评论 0赞 0
-
通电程序
1 . 开总电门&#xff0c;测试警告灯&#xff0c;开 5 灯&#xff0c;开皮托管加温&#xff0c;放襟翼 2 . 收襟翼&#xff0c;关5灯&#xff0c;关皮托管加温&#xff0c;关总电门
正在上传…重新上传取消HugSum阅读 85评论 0赞 0
正在上传…重新上传取消
吴德宝AllenWu
关注
总资产6
程序员基本素养和特质
阅读 139
git workflow 规范
阅读 144
推荐阅读
Go 专栏&#xff5c;接口 interface
阅读 189
java8&#xff08;二&#xff09;lambda表达式手把手学习
阅读 478
成员变量
阅读 247
1、JavaOOP面试题&#xff08;98题&#xff09;
阅读 127
浅谈前端AST的概念与实际应用
阅读 373
评论1
赞18