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

版本: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. 一键部署Docker中间件简单方法-redis为例

    一键部署Docker中间件简单方法-redis为例 背景 想能够快速部署一些中间件. 写文档虽然可以, 但是总会有人问, 能够一键部署应该最好不过. 下载以及导出镜像 docker pull redi ...

  2. [转帖]一文读懂容器存储接口 CSI

    https://zhuanlan.zhihu.com/p/470093908 作者 | 惠志来源 | 阿里巴巴云原生公众号 导读:在<一文读懂 K8s 持久化存储流程>一文我们重点介绍了 ...

  3. [转帖]VMware ESXi 各版本号对照表

    本博文转自以下链接: VMware ESXi Release and Build Number History | virten.net vSphere ESXi 7.0 Name Patch Dat ...

  4. [转帖]Linux Page cache和Buffer cache

    https://www.cnblogs.com/hongdada/p/16926655.html free 命令常用参数 free 命令用来查看内存使用状况,常用参数如下: -h human-read ...

  5. [转帖]CPU结构对Redis性能的影响

    文章系转载,便于分类和归纳,源文地址:https://wangkai.blog.csdn.net/article/details/111571446 CPU的多核架构和多CPU架构都会影响到Redis ...

  6. 使用 inotifywait的方式监控文件夹发生变化后自动执行脚本的方法

    0. 先安装inotify 最简单的方法为: yum install epel-release 安装扩展包源 yum install inotify-tools 1. 从网上抄了一下脚本 简单记录一下 ...

  7. 阿里云ECS虚拟机磁盘扩容过程

    阿里云ECS虚拟机磁盘扩容过程 背景 公司同事将很早之前的一个虚拟机重新开机. 就好将一套demo环境安装进这个ECS虚拟机里面 这个机器系统盘只有40G的空间. 导致磁盘空间不足. 其实一开始我不知 ...

  8. TypeScript接口的讲解-强制约束-可选属性-任意多个属性-只读属性

    接口 接口:可以描述类的一部分抽象行为, 也可以描述数据的结构形状 接口一般首字母大写, 接口中 可以定义为 强制约束 可选属性 只读属性 任意属性 # 强制约束 // 定义接口 interface ...

  9. 【JS 逆向百例】网洛者反爬练习平台第二题:JJEncode 加密

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...

  10. kettle系统列文章02---如何建立一个转换

    1.连接mysql 主对象树---->DB连接---->新建 2.连接sqlserver 主对象树--->DB连接----->新建 3.设置数据库为共享:在db上右键---&g ...