Go 인터페이스의 기본 원칙에 대한 심층적인 이해{}

계속 만들고 성장을 가속화하십시오! "너겟 데일리 뉴플랜 · 6월 업데이트 챌린지" 참여 11일차입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다.

1. 인터페이스에 대한 예비 연구{}

Go는 강력한 유형의 언어이며, 각 인스턴스 변수의 유형 정보는 인터페이스{}에 저장되며 Go에서의 리플렉션은 기본 구조와도 관련이 있습니다.

ifaceeface그리고 Go에서 인터페이스를 설명하는 기본 구조입니다. 차이점은 iface설명된 인터페이스에는 메서드가 포함되어 있는 eface반면 에 빈 인터페이스에는 메서드가 포함되어 있지 않다는 점 interface{}입니다.

다음으로 iface 기본 데이터 구조를 자세히 분석합니다.eface

2. 이페이스

eface이것은 비교적 _type간단 하며 빈 인터페이스가 전달하는 특정 엔터티 유형을 나타내고 data특정 값을 설명하는 필드만 유지합니다.

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
复制代码

data필드는 iface 가 있는 구조입니다. 이것은 인터페이스 인스턴스 객체 정보의 저장 주소를 가리키는 메모리 포인터입니다.{}여기에서 객체의 특정 속성에 대한 숫자 정보를 얻을 수 있습니다.eface

인터페이스{}의 유형 정보는 _type아래와 같이 구조체에 저장되며 eface, 에서 직접 저장된 _type포인터 iface에는 캡슐화 계층이 추가됩니다. 이 섹션에서는 주로 eface정렬에 중점을 두므로 _type구조체를 소개합니다.

type _type struct {
    // 类型大小
    size       uintptr
    ptrdata    uintptr
    // 类型的 hash 值
    hash       uint32
    // 类型的 flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldalign uint8
    // 类型的编号,有bool, slice, struct 等等等等
    kind       uint8
    alg        *typeAlg
    // gc 相关
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
复制代码

我们可以看到size,ptrdata等表示interface{}对象的类型信息,hash是其对应的哈希值,用于map等的哈希算法,tflag与反射相关,而alignfieldalign是用来内存对齐的,这与Go底层的内存管理机制有关,Go的内存管理机制类似于Linux中的伙伴系统,是以固定大小的内存块进行内存分配的,与这个大小进行对齐消除外碎片,提高内存利用率。另外还有一些和gc相关的参数,大家有一个初步的理解与认识就可以了,如果想深入掌握可以专门学习和查看源码。

3. iface

eface不同,iface结构体中要同时储存方法信息,其数据结构如下图所示。正如前面所说的,itab结构体封装了_type结构体,同样利用_type储存类型信息,另外,其还有一些其他的属性。hash是对_type结构体中hash的拷贝,提高类型断言的效率。badinhash都是标记位,提高gc以及其他活动的效率。fun指向方法信息的具体地址。

另外,interfacetype,他描述的是接口静态类型信息。

fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。这里只会列出实体类型和接口相关的方法,实体类型的其他方法并不会出现在这里。如果你学过 C++ 的话,这里可以类比虚函数的概念,至于静态函数,并不存放在这里。

C++ 和 Go 在定义接口方式上的不同,也导致了底层实现上的不同。C++ 通过虚函数表来实现基类调用派生类的函数;而 Go 通过 itab 中的 fun 字段来实现接口变量调用实体类型的函数。C++ 中的虚函数表是在编译期生成的;而 Go 的 itab 中的 fun 字段是在运行期间动态生成的。原因在于,Go 中实体类型可能会无意中实现 N 多接口,很多接口并不是本来需要的,所以不能为类型实现的所有接口都生成一个 itab, 这也是“非侵入式”带来的影响;这在 C++ 中是不存在的,因为派生需要显示声明它继承自哪个基类。

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    hash   uint32 // copy of _type.hash. Used for type switches.
    bad    bool   // type does not implement interface
    inhash bool   // has this itab been added to hash?
    unused [2]byte
    fun    [1]uintptr // variable sized
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}
复制代码

综合上面的分析,我们可以梳理出,iface对应的几个重要数据结构的关系如下图所示。

이미지.png

4. 接口转化

通过前面提到的 iface 的源码可以看到,实际上它包含接口的类型 interfacetype 和 实体类型的类型 _type,这两者都是 iface 的字段 itab 的成员。也就是说生成一个 itab 同时需要接口的类型和实体的类型。

->itable

当判定一种类型是否满足某个接口时,Go 使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。

例如某类型有 m 个方法,某接口有 n 个方法,则很容易知道这种判定的时间复杂度为 O(mn),Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为 O(m+n)

Go的接口实现是非侵入式的,而是鸭子模式:如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。

因此,只要我们实现了接口对应的方法,也就实现了对应的接口,不需要单独申明。

추천

출처juejin.im/post/7105423957565636639