分析接口的赋值,反射,断言的实现原理

版本:golang v1.12

interface底层使用2个struct表示的:efaceiface

一:接口类型分为2个

1. 空接口

//比如
var i interface{}

2. 带方法的接口

//比如
type studenter interface {
GetName() string
GetAge() int
}

二:eface 空接口定义

空接口通过eface结构体定义实现,位于src/runtime/runtime2.go

type eface struct {
_type *_type //类型信息
data unsafe.Pointer //数据信息,指向数据指针
}

可以看到上面eface包含了2个元素,一个是_type,指向对象的类型信息,一个 data,数据指针

三:_type 结构体

_type 位于 src/runtime/type.go


_type 是go里面所有类型的一个抽象,里面包含GC,反射,大小等需要的细节,它也决定了data如何解释和操作。
里面包含了非常多信息 类型的大小、哈希、对齐以及种类等自动。


所以不论是空eface和非空iface都包含 _type 数据类型

type _type struct {
size uintptr //数据类型共占用的空间大小
ptrdata uintptr //含有所有指针类型前缀大小
hash uint32 //类型hash值;避免在哈希表中计算
tflag tflag //额外类型信息标志
align uint8 //该类型变量对齐方式
fieldalign uint8 //该类型结构字段对齐方式
kind uint8 //类型编号
alg *typeAlg //算法表 存储hash和equal两个操作。map key便使用key的_type.alg.hash(k)获取hash值
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte //gc数据
str nameOff // 类型名字的偏移
ptrToThis typeOff
}

_type 中的一些数据类型如下:

// typeAlg is 总是 在 reflect/type.go 中 copy或使用.
// 并保持他们同步.
type typeAlg struct {
// 算出该类型的Hash
// (ptr to object, seed) -> hash
hash func(unsafe.Pointer, uintptr) uintptr
// 比较该类型对象
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
}
type nameOff int32
type typeOff int32

但是各个类型需要的类型描叙是不一样的,比如chan,除了chan本身外,还需要描述其元素类型,而map则需要key类型信息和value类型信息等:

//src/runtime/type.go

type ptrtype struct {
typ _type
elem *_type
} type chantype struct {
typ _type
elem *_type
dir uintptr
} type maptype struct {
typ _type
key *_type
elem *_type
bucket *_type // internal type representing a hash bucket
keysize uint8 // size of key slot
valuesize uint8 // size of value slot
bucketsize uint16 // size of bucket
flags uint32
}

看上面的类型信息,第一个自动都是 _type,接下来也定义了一堆类型所需要的信息(如子类信息),这样在进行类型相关操作时,可通过一个字(typ *_type)即可表述所有类型,然后再通过_type.kind可解析出其具体类型,最后通过地址转换即可得到类型完整的”_type树”,参考reflect.Type.Elem()函数:

// reflect/type.go
// reflect.rtype结构体定义和runtime._type一致 type.kind定义也一致(为了分包而重复定义)
// Elem()获取rtype中的元素类型,只针对复合类型(Array, Chan, Map, Ptr, Slice)有效
func (t *rtype) Elem() Type {
switch t.Kind() {
case Array:
tt := (*arrayType)(unsafe.Pointer(t))
return toType(tt.elem)
case Chan:
tt := (*chanType)(unsafe.Pointer(t))
return toType(tt.elem)
case Map:
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.elem)
case Ptr:
tt := (*ptrType)(unsafe.Pointer(t))
return toType(tt.elem)
case Slice:
tt := (*sliceType)(unsafe.Pointer(t))
return toType(tt.elem)
}
panic("reflect: Elem of invalid type")
}

四:没有方法的interface赋值后内部结构

对于没有方法的interface赋值后的内部结构是怎样的呢?
可以先看段代码:

import (
"fmt"
"strconv"
) type Binary uint64 func main() {
b := Binary(200)
any := (interface{})(b)
fmt.Println(any)
}

输出200,赋值后的结构图是这样的:

图片来自:https://blog.csdn.net/i6448038/article/details/82916330

对于将不同类型转化成type万能结构的方法,是运行时的convT2E方法,在runtime包中。
以上,是对于没有方法的接口说明。
对于包含方法的函数,用到的是另外的一种结构,叫iface

五:iface 非空接口

iface结构体表示非空接口:

iface

// runtime/runtime2.go
// 非空接口
type iface struct {
tab *itab
data unsafe.Pointer //指向原始数据指针
}

itab

itab结构体是iface不同于eface,比较关键的数据结构

// runtime/runtime2.go
// 非空接口的类型信息
type itab struct {
//inter 和 _type 确定唯一的 _type类型
inter *interfacetype // 接口自身定义的类型信息,用于定位到具体interface类型
_type *_type // 接口实际指向值的类型信息-实际对象类型,用于定义具体interface类型
hash int32 //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
_ [4]byte
fun [1]uintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序
//如果数组中的内容为空表示 _type 没有实现 inter 接口 }

属性interfacetype类似于_type,其作用就是interface的公共描述,类似的还有maptypearraytypechantype…其都是各个结构的公共描述,可以理解为一种外在的表现信息。interfacetype源码如下:

// runtime/type.go
// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod // 接口方法声明列表,按字典序排序
} // 接口的方法声明,一种函数声明的抽象
// 比如:func Print() error
type imethod struct {
name nameOff // 方法名
ityp typeOff // 描述方法参数返回值等细节
} type nameOff int32
type typeOff int32

method 存的是func 的声明抽象,而 itab 中的 fun 字段才是存储 func 的真实切片。

非空接口(iface)本身除了可以容纳满足其接口的对象之外,还需要保存其接口的方法,因此除了data字段,iface通过tab字段描述非空接口的细节,包括接口方法定义,接口方法实现地址,接口所指类型等。iface是非空接口的实现,而不是类型定义,iface的真正类型为interfacetype,其第一个字段仍然为描述其自身类型的_type字段。

六:iface整体结构图

图片来自:https://blog.csdn.net/i6448038/article/details/82916330

七:含有方法的interface赋值后的内部结构

含有方法的interface赋值后的内部结构是怎样的呢?

package main

import (
"fmt"
"strconv"
) type Binary uint64
func (i Binary) String() string {
return strconv.FormatUint(i.Get(), 10)
} func (i Binary) Get() uint64 {
return uint64(i)
} func main() {
b := Binary(200)
any := fmt.Stringer(b)
fmt.Println(any)
}

首先,要知道代码运行结果为:200。
其次,了解到fmt.Stringer是一个包含String方法的接口。

type Stringer interface {
String() string
}

最后,赋值后接口Stringer的内部结构为:

八:参考:

https://wudaijun.com/2018/01/go-interface-implement/
https://blog.csdn.net/i6448038/article/details/82916330#comments

深入理解Go语言(01): interface源码分析的更多相关文章

  1. 《深入理解Spark-核心思想与源码分析》(一)总体规划和第一章环境准备

    <深入理解Spark 核心思想与源码分析> 耿嘉安著 本书共计486页,计划每天读书20页,计划25天完成. 2018-12-20   1-20页 凡事豫则立,不豫则废:言前定,则不跲:事 ...

  2. Vue系列---理解Vue.nextTick使用及源码分析(五)

    _ 阅读目录 一. 什么是Vue.nextTick()? 二. Vue.nextTick()方法的应用场景有哪些? 2.1 更改数据后,进行节点DOM操作. 2.2 在created生命周期中进行DO ...

  3. 深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)

    events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中 ...

  4. SpringBoot与Mybatis整合方式01(源码分析)

    前言:入职新公司,SpringBoot和Mybatis都被封装了一次,光用而不知道原理实在受不了,于是开始恶补源码,由于刚开始比较浅,存属娱乐,大神勿喷. 就如网上的流传的SpringBoot与Myb ...

  5. 《深入理解Spark-核心思想与源码分析》(四)第四章存储体系

    天行健,君子以自强不息:地势坤,君子以厚德载物.——<易经> 本章导读 Spark的初始化阶段.任务提交阶段.执行阶段,始终离不开存储体系. Spark为了避免Hadoop读写磁盘的I/O ...

  6. 《深入理解Spark-核心思想与源码分析》(六)第六章计算引擎

    RDD是Spark对各类数据计算模型的统一抽象,被用于迭代计算过程以及任务输出结果的缓存读写. 在所有MapReduce框架中,shuffle是连接map任务和reduce任务的桥梁.shuffle性 ...

  7. 《深入理解Spark-核心思想与源码分析》(二)第二章Spark设计理念和基本架构

    若夫乘天地之正,而御六气之辩解,以游无穷者,彼且恶乎待哉? ——<庄子.逍遥游> 翻译:至于遵循宇宙万物的规律,把握“六气”的变化,遨游于无穷无尽的境域,他还仰赖什么呢! 2.1 初始Sp ...

  8. Go 语言 HTTP Server 源码分析

    http://www.codeceo.com/go-http-server-code.html

  9. 《深入理解Spark-核心思想与源码分析》(五)第五章任务提交与执行

    即欲捭之贵周,即欲阖之贵密.周密之贵,微而与道相随.---<鬼谷子> 解释:译文:如果要分析问题,关键在于周详,如果要综合归纳问题,关键在于严密.周详严密的关键在于精深而与道相随. 解词: ...

  10. 《深入理解Spark-核心思想与源码分析》(三)第三章SparkContext的初始化

    3.1 SparkContext概述 SparkConf负责配置参数,主要通过ConcurrentHaspMap来维护各种Spark的配置属性. class SparkConf(loadDefault ...

随机推荐

  1. [转帖]linux下 进程io队列,IO队列和IO调度

    IO体系概览 先看看本文主题IO调度和IO队列处于整个IO体系的哪个位置,这个IO体系是非常重要的,了解IO体系我们可以对整个IO过程有个全面的认识.虽然一下两下并不清楚IO体系各个部分的细节,但是我 ...

  2. [转帖]kvm web管理 webvirtmgr

    https://www.jianshu.com/p/8fd2ddadebe9 reference https://blog.csdn.net/yangshihuz/article/details/10 ...

  3. 【转】Java类加载器:类加载原理解析

    [转]Java类加载器:类加载原理解析 https://www.jianshu.com/p/1f704ad4196e 摘要: 每个开发人员对java.lang.ClassNotFoundExcetpi ...

  4. 麒麟信安V3.4 安装PG15的过程

    麒麟信安V3.4 安装PG15的过程 背景 发现基于OpenEuler的几个系统使用CentOS的rpm包 安装PG数据库时有问题. 会提示缺少依赖的so文件. 今天想着解决一下, 就百度了一下并且进 ...

  5. prometheus告警规则分发服务

    Prometheus告警规则分发服务,根据一致性哈希将规则分发到多个节点,使用多个goroutine处理应用告警,在服务增加时可以增加goroutine,服务减少时降低goroutine数目. 规则下 ...

  6. MySQL知识点总结(完整版)

    MySQL学习笔记 登录和退出MySQL服务器 # 登录MySQL $ mysql -u root -p12345612 # 退出MySQL数据库服务器 exit; 基本语法 -- 显示所有数据库 s ...

  7. Docker获取Let`s Encrypt SSL 证书

    文中的操作都是在CentOS Stream release 9下执行的,使用的是root用户. 1. 安装docker # 卸载原有的docker yum remove docker docker-c ...

  8. .net Core IsDefined、GetValues、HasFlag 使用

    IsDefined可以用于判断传入的单个值是否属于该枚举 GetValues检索指定枚举中常量值的数组 HasFlag 可以用于判断传入的多个值是否属于该枚举 先来个例子: public enum B ...

  9. GO中的GC

    go中的垃圾回收 前言 垃圾回收 go中的垃圾回收方式 三色标记法 根对象 STW 屏障技术 插入屏障 删除屏障 混合写屏障 GO中GC的流程 GC的触发时机 如果内存分配速度超过了标记清除的速度怎么 ...

  10. 小白学k8s(5)k8s中的service

    k8s中的service service存在的意义 Pod与Service的关系 Port port targetPort nodePort IP Node IP Pod IP Cluster IP ...