广告
返回顶部
首页 > 资讯 > 后端开发 > GO >深入Golang的接口interface
  • 1136
分享到

深入Golang的接口interface

Golang接口interfaceGo接口interface 2022-11-13 09:11:28 1136人浏览 独家记忆
摘要

目录前言接口转换的原理实现多态前言 Go不要求类型显示地声明实现了哪个接口,只要实现了相关的方法即可,编译器就能检测到 空接口类型可以接收任意类型的数据: type eface st

前言

Go不要求类型显示地声明实现了哪个接口,只要实现了相关的方法即可,编译器就能检测到

空接口类型可以接收任意类型的数据:

type eface struct {
    // _type 指向接口的动态类型元数据
    // 描述了实体类型、包括内存对齐方式、大小等
	_type *_type
    // data 指向接口的动态值
	data  unsafe.Pointer
}

空接口在赋值时,_type 和 data 都是nil。赋值后,_type 会指向赋值的数据元类型,data 会指向该值

非空接口是有方法列表的接口类型,一个变量要赋值给非空接口,就要实现该接口里的所有方法

type iface struct {
    // tab 接口表指针,指向一个itab实体,存储方法列表和接口动态类型信息
	tab  *itab
    // data 指向接口的动态值(一般是指向堆内存的)
	data 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.dumptabs.
type itab struct {
    // inter 指向interface的类型元数据,描述了接口的类型
	inter *interfacetype
    // _type 描述了实体类型、包括内存对齐方式、大小等
	_type *_type
    // hash 从动态类型元数据中拷贝的hash值,用于快速判断类型是否相等
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
    // fun 记录动态类型实现的那些接口方法的地址
    // 存储的是第一个方法的函数指针
    // 这些方法是按照函数名称的字典序进行排列的
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

// interfacetype 接口的类型元数据
type interfacetype struct {
	typ     _type
    // 记录定义了接口的包名
	pkgpath name
    // mhdr 标识接口所定义的函数列表
	mhdr    []imethod
}

itab是可复用的,go会将itab缓存起来,构造一个hash表用于查出和查询缓存信息<接口类型, 动态类型>

如果itab缓存中有,可以直接拿来使用,如果没有,则新创建一个itab,并放入缓存中

一个Iface中的具体类型中实现的方法会被拷贝到Itab的fun数组

// Note: change the fORMula in the mallocgc call in itabAdd if you change these fields.
type itabTableType struct {
	size    uintptr             // length of entries array. Always a power of 2.
	count   uintptr             // current number of filled entries.
	entries [itabInitSize]*itab // really [size] large
}
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
    // 用接口类型hash和动态类型hash进行异或
	return uintptr(inter.typ.hash ^ typ.hash)
}

接口类型和nil作比较

接口值的零值指接口动态类型和动态值都为nil,当且仅当此时接口值==nil

如何打印出接口的动态类型和值?

定义一个iface结构体,用两个指针来描述itab和data,然后将具体遍历在内存中的内容强行解释为我们定义的iface

type iface struct{
    itab, data uintptr
}
func main() {
	var a interface{} = nil
	var b interface{} = (*int)(nil)
	x := 5
	var c interface{} = (*int)(&x)
	ia := *(*iface)(unsafe.Pointer(&a))
	ib := *(*iface)(unsafe.Pointer(&b))
	ic := *(*iface)(unsafe.Pointer(&c))
	fmt.Println(ia, ib, ic)
	fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}
// 输出
// {0 0} {17426912 0} {17426912 842350714568}
// 5

检测类型是否实现了接口:

赋值语句会发生隐式的类型转换,在转换过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数

// 检查 *myWriter 类型是否实现了 io.Writer 接口
var _ io.Writer = (*myWriter)(nil)
// 检查 myWriter 类型是否实现了 io.Writer 接口
var _ io.Writer = myWriter{}

接口转换的原理

将一个接口转换为另一个接口:

  • 实际上是调用了convI2I
  • 如果目标是空接口或和目标接口的inter一样就直接返回
  • 否则去获取itab,然后将值data赋入,再返回
// 接口间的赋值实际上是调用了runtime.convI2I
// 实际上是要找到新interface的tab和data
// convI2I returns the new itab to be used for the destination value
// when converting a value with itab src to the dst interface.
func convI2I(dst *interfacetype, src *itab) *itab {
	if src == nil {
		return nil
	}
	if src.inter == dst {
		return src
	}
	return getitab(dst, src._type, false)
}

// 关键函数,获取itab
// getitab根据interfacetype和_type去全局的itab哈希表中查找,如果找到了直接返回
// 否则根据inter和typ新生成一个itab,插入到全局itab哈希表中
// 
// 查找了两次,第二次上了,目的是可能会写入hash表,阻塞其余协程的第二次查找
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	// 函数列表为空
    if len(inter.mhdr) == 0 {
		throw("internal error - misuse of itab")
	}

	// easy case
	if typ.tflag&tflagUncommon == 0 {
		if canfail {
			return nil
		}
		name := inter.typ.nameOff(inter.mhdr[0].name)
		panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
	}

	var m *itab

	// First, look in the existing table to see if we can find the itab we need.
	// This is by far the most common case, so do it without locks.
	// Use atomic to ensure we see any previous writes done by the thread
	// that updates the itabTable field (with atomic.Storep in itabAdd).
    // 使用原子性去保证我们能看见在该线程之前的任意写操作
    // 确保更新全局hash表的字段
	t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    // 遍历一次,找到了就返回
	if m = t.find(inter, typ); m != nil {
		goto finish
	}
    // 没找到就上锁,再试一次
	// Not found.  Grab the lock and try again.
	lock(&itabLock)
	if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}
    // hash表中没找到itab就新生成一个itab
	// Entry doesn't exist yet. Make a new entry & add it.
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
	m._type = typ
	// The hash is used in type switches. However, compiler statically generates itab's
	// for all interface/type pairs used in switches (which are added to itabTable
	// in itabsinit). The dynamically-generated itab's never participate in type switches,
	// and thus the hash is irrelevant.
	// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
	m.hash = 0
	m.init()
    // 加到全局的hash表中
	itabAdd(m)
	unlock(&itabLock)
finish:
	if m.fun[0] != 0 {
		return m
	}
	if canfail {
		return nil
	}
	// this can only happen if the conversion
	// was already done once using the , ok form
	// and we have a cached negative result.
	// The cached result doesn't record which
	// interface function was missing, so initialize
	// the itab again to get the missing function name.
	panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

// 查找全局的hash表,有没有itab
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
	// Implemented using quadratic probing.
	// Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
	// We're guaranteed to hit all table entries using this probe sequence.
	mask := t.size - 1
    // 根据inter,typ算出hash值
	h := itabHashFunc(inter, typ) & mask
	for i := uintptr(1); ; i++ {
		p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
		// Use atomic read here so if we see m != nil, we also see
		// the initializations of the fields of m.
		// m := *p
		m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
		if m == nil {
			return nil
		}
        // inter和typ指针都相同
		if m.inter == inter && m._type == typ {
			return m
		}
		h += i
		h &= mask
	}
}
// 核心函数。填充itab
// 检查_type是否符合interface_type并且创建对应的itab结构体将其放到hash表中
// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
	inter := m.inter
	typ := m._type
	x := typ.uncommon()

	// both inter and typ have method sorted by name,
	// and interface names are unique,
	// so can iterate over both in lock step;
	// the loop is O(ni+nt) not O(ni*nt).
    // inter和typ的方法都按方法名称进行排序
    // 并且方法名是唯一的,因此循环次数的固定的
    // 复杂度为O(ni+nt),而不是O(ni*nt)
	ni := len(inter.mhdr)
	nt := int(x.mcount)
	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
	j := 0
	methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
	var fun0 unsafe.Pointer
imethods:
	for k := 0; k < ni; k++ {
		i := &inter.mhdr[k]
		itype := inter.typ.typeOff(i.ityp)
		name := inter.typ.nameOff(i.name)
		iname := name.name()
		ipkg := name.pkgPath()
		if ipkg == "" {
			ipkg = inter.pkgpath.name()
		}
        // 第二层循环是从上一次遍历到的位置开始的
		for ; j < nt; j++ {
			t := &xmhdr[j]
			tname := typ.nameOff(t.name)
            // 检查方法名字是否一致
			if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
				pkgPath := tname.pkgPath()
				if pkgPath == "" {
					pkgPath = typ.nameOff(x.pkgpath).name()
				}
				if tname.isExported() || pkgPath == ipkg {
					if m != nil {
                        // 获取函数地址,放入itab.func数组中
						ifn := typ.textOff(t.ifn)
						if k == 0 {
							fun0 = ifn // we'll set m.fun[0] at the end
						} else {
							methods[k] = ifn
						}
					}
					continue imethods
				}
			}
		}
		// didn't find method
		m.fun[0] = 0
		return iname
	}
	m.fun[0] = uintptr(fun0)
	return ""
}

// 检查是否需要扩容,并调用方法将itab存入
// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {
	// Bugs can lead to calling this while mallocing is set,
	// typically because this is called while panicing.
	// Crash reliably, rather than only when we need to grow
	// the hash table.
	if getg().m.mallocing != 0 {
		throw("malloc deadlock")
	}

	t := itabTable
    // 检查是否需要扩容
	if t.count >= 3*(t.size/4) { // 75% load factor
		// Grow hash table.
		// t2 = new(itabTableType) + some additional entries
		// We lie and tell malloc we want pointer-free memory because
		// all the pointed-to values are not in the heap.
		t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true))
		t2.size = t.size * 2

		// Copy over entries.
		// Note: while copying, other threads may look for an itab and
		// fail to find it. That's ok, they will then try to get the itab lock
		// and as a consequence wait until this copying is complete.
		iterate_itabs(t2.add)
		if t2.count != t.count {
			throw("mismatched count during itab table copy")
		}
		// Publish new hash table. Use an atomic write: see comment in getitab.
		atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
		// Adopt the new table as our own.
		t = itabTable
		// Note: the old table can be GC'ed here.
	}
    // 核心函数
	t.add(m)
}

// 核心函数
// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
	// See comment in find about the probe sequence.
	// Insert new itab in the first empty spot in the probe sequence.
    // 在探针序列第一个空白点插入itab
	mask := t.size - 1
    // 计算hash值
	h := itabHashFunc(m.inter, m._type) & mask
	for i := uintptr(1); ; i++ {
		p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
		m2 := *p
		if m2 == m {
            // itab已被插入
			// A given itab may be used in more than one module
			// and thanks to the way global symbol resolution works, the
			// pointed-to itab may already have been inserted into the
			// global 'hash'.
			return
		}
		if m2 == nil {
			// Use atomic write here so if a reader sees m, it also
			// sees the correctly initialized fields of m.
			// NoWB is ok because m is not in heap memory.
			// *p = m
            // 使用原子操作确保其余的goroutine下次查找的时候可以看到他
			atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
			t.count++
			return
		}
		h += i
		h &= mask
	}
}

// hash函数,编译期间提供较好的hash codes
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
	return uintptr(inter.typ.hash ^ typ.hash)
}

具体类型转空接口时,_type 字段直接复制源类型的 _type;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。

具体类型转非空接口时,入参 tab 是编译器在编译阶段预先生成好的,新接口 tab 字段直接指向入参 tab 指向的 itab;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。

而对于接口转接口,itab 调用 getitab 函数获取。只用生成一次,之后直接从 hash 表中获取。

实现多态

  • 多态的特点
  • 一种类型具有多种类型的能力
  • 允许不同的对象对同一消息做出灵活的反应
  • 以一种通用的方式对待多个使用的对象
  • 非动态语言必须通过继承和接口的方式实现

实现函数的内部,接口绑定了实体类型,会直接调用fun里保存的函数,类似于s.tab->fun[0],而fun数组中保存的是实体类型实现的函数,当函数传入不同实体类型时,实际上调用的是不同的函数实现,从而实现了多态

到此这篇关于深入golang的接口interface的文章就介绍到这了,更多相关Go 接口 interface内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: 深入Golang的接口interface

本文链接: https://www.lsjlt.com/news/150890.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 深入Golang的接口interface
    目录前言接口转换的原理实现多态前言 go不要求类型显示地声明实现了哪个接口,只要实现了相关的方法即可,编译器就能检测到 空接口类型可以接收任意类型的数据: type eface st...
    99+
    2022-11-13
    Golang接口interface Go接口interface
  • Java深入解析接口interface
    目录1.接口定义示例2.接口细节01示例3.接口细节024.接口细节035.接口多态特性1.接口定义 基本介绍 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根...
    99+
    2022-11-13
    Java 接口 Java interface
  • 深入探讨 Golang 接口的实现
    Golang(又称Go语言)是一门现代化的编程语言,它是由 Google 设计和维护的。Golang 是一种静态语言,它通过强类型和严格类型检查的方式来提高代码的可维护性和健壮性。其中一个最有趣的特性就是接口,本文将会深入探讨 Golang...
    99+
    2023-05-14
    Golang go语言 接口
  • Golang中Interface接口的三个特性
    原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍。go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编...
    99+
    2022-11-13
    go Golang Interface接口 三个特性
  • 深入了解Golang interface{}的底层原理实现
    目录前言interface数据结构ifaceeface总结前言 在 Go 语言没有泛型之前,接口可以作为一种替代实现,也就是万物皆为的 interface。那到底 interface...
    99+
    2022-11-11
    Golang interface{}原理 Golang interface{} Golang interface
  • Java中接口的深入详解
    目录一、前言二、接口接口的格式三、接口的特点接口的使用四、类与接口的关系接口多重继承的好处练习总结一、前言 前面我们说了抽象类的概述,我们对抽象类也有个认识和理解了,现在我们学习十分...
    99+
    2022-11-12
    java接口的用法 java中接口定义的方法 java接口代码
  • GoLangnil与interface的空指针深入分析
    目录nilslicemapinterface指针是否为空nil Go中,每个指针都有2个基本信息,指针的类型和指针的值(type,value);当执行==时,需要比较类型与值(只有类...
    99+
    2022-12-23
    GoLang nil GoLang interface空指针
  • 深入理解C#之接口
    目录C#之接口接口的特性:接口的继承:接口的覆盖:接口和抽象类的区别。C#中的接口和类有什么异同。总结C#之接口 在编程中,我们经常会用到接口,那什么是接口呢? 接口描述的是可属于任...
    99+
    2022-11-12
    C#接口 理解C#
  • Java接口interface的概念及使用
    本篇内容介绍了“Java接口interface的概念及使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在抽象类中,可以包含一个或多个抽象方...
    99+
    2023-06-03
  • java中的interface接口实例详解
     java中的interface接口实例详解接口:Java接口是一些方法表征的集合,但是却不会在接口里实现具体的方法。java接口的特点如下:java接口不能被实例化2、java接口中声明的成员自动被设置为public,所以不存在...
    99+
    2023-05-31
    java interface ava
  • 如何深入理解Java中的接口
    今天就跟大家聊聊有关如何深入理解Java中的接口,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、前言前面我们说了抽象类的概述,我们对抽象类也有个认识和理解了,现在我们学习十分重要的...
    99+
    2023-06-21
  • typeScript的interface接口怎么定义使用
    这篇“typeScript的interface接口怎么定义使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“typeScri...
    99+
    2023-06-29
  • 深入了解Go的interface{}底层原理实现
    目录1. interface{}初探2. eface3. iface4. 接口转化1. interface{}初探 Go是强类型语言,各个实例变量的类型信息正是存放在inter...
    99+
    2022-06-07
    GO interface
  • java并发之Lock接口的深入讲解
    目录Juc中各种各样锁信息synchronized面临缺点Lock接口Lock最佳实践:对比 Lock和tryLock的区别总结Juc中各种各样锁信息 在java的juc包为我们提...
    99+
    2022-11-12
    java的lock java中lock的使用 java lock接口
  • 关于Java Interface接口的简单练习题
    目录一、问题描述二、解决方案三、代码清单本文转自微信公众号:"算法与编程之美" 一、问题描述 1) 定义接口Printx,其中包括一个方法printMyWay() ,这个方法没有形参...
    99+
    2022-11-12
    Java Interface接口 Java接口练习题 Interface接口
  • Java面向对象中接口interface的使用
    这篇文章主要介绍“Java面向对象中接口interface的使用”,在日常操作中,相信很多人在Java面向对象中接口interface的使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java面向对象中接口...
    99+
    2023-06-02
  • 深入浅析Android接口回调机制
    在使用接口回调的时候发现了一个经常犯的错误,就是回调函数里面的实现有可能是用多线程或者是异步任务去做的,这就会导致我们期望函数回调完毕去返回一个主函数的结果,实际发现是行不通的...
    99+
    2022-06-06
    回调 android接口回调 Android
  • Spring深入分析容器接口作用
    目录1.容器接口有哪些2.BeanFactory能干嘛3.ApplicationContext有哪些扩展功能3.1 MessageSource3.2 ResourcePatternR...
    99+
    2022-11-13
    Spring容器接口 Spring容器框架
  • 深入浅析Java 抽象类和接口
    目录一、抽象类1.抽象类1.1抽象类的定义1.2抽象方法的定义方式1.3抽象类的定义方式2.抽象类和实例类的区别3.抽象类示例4.抽象类的特征二、接口1.接口1.1接口的定义1.1定...
    99+
    2022-11-12
    Java 抽象类和接口 Java 抽象类
  • 深入Golang中的sync.Pool详解
    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 ...
    99+
    2022-11-12
    Golang sync.Pool Golang sync.Pool源码
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作