空接口可用于保存任何数据,它可以是一个有用的参数,因为它可以使用任何类型。 要理解空接口如何工作以及如何保存任何类型,我们首先应该理解名称背后的概念。

接口

这是Jordan Oreilli对空接口的一个很好的定义:

接口是两件事:它是一组方法,但它也是一种类型。

interface {}类型是没有方法的接口。 由于没有implements关键字,所有类型都实现至少零个方法,并且自动满足接口,所以所有类型都满足空接口

因此,具有空接口作为参数的方法可以接受任何类型。 Go将转换为接口类型以提供此功能。

Russ Cox撰写了一篇关于接口内部表示的精彩文章,并解释了接口由两方面组成:

  • 指向存储类型信息的指针
  • 指向关联数据的指针

以下是Russ在2009年运行时用C语言编写的表示:

运行时现在用Go编写,但表示仍然相同。 我们可以通过打印空接口来验证:

func main() {var i int8 = 1read(i)}//go:noinlinefunc read(i interface{}) {println(i)}
(0x10591e0,0x10be5c6)

两个地址都代表了类型信息和值的两个指针。

底层结构

空接口的底层表示形式记录在反射包中:

type emptyInterface struct { typ *rtype // word 1 with type description word unsafe.Pointer // word 2 with the value}

如前所述,我们清楚地看到空接口有一个类型描述字段,后面跟着包含数据字段。

rtype结构包含类型描述的基础:

type rtype struct { size uintptr ptrdata uintptr hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 alg *typeAlg gcdata *byte str nameOff ptrToThis typeOff}

在这些字段中,有些字段非常简单且众所周知:

  • size是以字节为单位的大小
  • kind包含类型:int8,int16,bool等。
  • align是有类型变量的对齐方式

根据空接口嵌入的类型,我们可以映射导出的字段或列出方法:

type structType struct { rtype pkgPath name fields []structField}

该结构体还有两个映射,包括字段列表。 它清楚地表明,将内置类型转换为空接口将导致平面转换,其中字段的描述及其值将存储在内存中。

这是我们看到的空接口的表示:

现在让我们看看空接口实际上可以实现哪种转换。

转换

让我们尝试一个使用空接口错误转换的简单程序:

func main() {var i int8 = 1read(i)}//go:noinlinefunc read(i interface{}) {n := i.(int16)println(n)}

虽然从int8到int16的转换是有效的,但程序会panic:

panic: interface conversion: interface {} is int8, not int16goroutine 1 [running]:main.read(0x10592e0, 0x10be5c1)main.go:10 +0x7dmain.main()main.go:5 +0x39exit status 2

让我们生成asm代码,以便查看Go执行的检查:

以下是不同的步骤:

  • 步骤1:比较(指令CMPQ)类型int16(加载指令LEAQ,加载有效地址)到空接口的内部类型(指令MOVQ从空的存储器段读取具有48字节偏移量的存储器) 接口)
  • 步骤2:JNE指令,如果不等于跳转,将跳转到将在步骤3中处理错误的生成指令
  • 步骤3:代码将发生panic并生成我们之前看到的错误消息
  • 步骤3:这是错误指令的结束。 此特定指令由显示指令的错误消息引用:main.go:10 + 0x7d

任何从空接口的内部类型转换都应该在转换原始类型之后进行。 这种转换为空接口然后转换回原始类型会导致程序成本降低。 让我们运行一些基准来粗略了解它。

性能

这是两个基准。 一个使用结构的副本,另一个使用空接口:

package main_testimport ("testing")var x MultipleFieldStructuretype MultipleFieldStructure struct {a intb stringc float32d float64e int32f boolg uint64h *stringi uint16}//go:noinlinefunc emptyInterface(i interface {}) {s := i.(MultipleFieldStructure)x = s}//go:noinlinefunc typed(s MultipleFieldStructure) {x = s}func BenchmarkWithType(b *testing.B) {s := MultipleFieldStructure{a: 1, h: new(string)}for i := 0; i < b.N; i++ {typed(s)}}func BenchmarkWithEmptyInterface(b *testing.B) {s := MultipleFieldStructure{a: 1, h: new(string)}for i := 0; i < b.N; i++ {emptyInterface(s)}}

执行结果:

BenchmarkWithType-8 300000000 4.24 ns/opBenchmarkWithEmptyInterface-8 20000000 60.4 ns/op

类型转换到空接口然后在转换到类型这样双转换比复制结构体要多花费55纳秒。时间会随着结构中的字段数增加而增加:

BenchmarkWithType-8 100000000 17 ns/opBenchmarkWithEmptyInterface-8 10000000 153 ns/op

但是,一个好的解决方案是使用指针并转换回相同的结构体指针。 转换看起来像这样:

func emptyInterface(i interface {}) {s := i.(*MultipleFieldStructure)y = s}

现在结果就完全不同了:

BenchmarkWithType-8 2000000000 2.16 ns/opBenchmarkWithEmptyInterface-8 2000000000 2.02 ns/op

至于像int或string这样的基础类型,性能略有不同:

BenchmarkWithTypeInt-8 2000000000 1.42 ns/opBenchmarkWithEmptyInterfaceInt-8 1000000000 2.02 ns/opBenchmarkWithTypeString-8 1000000000 2.19 ns/opBenchmarkWithEmptyInterfaceString-8 50000000 30.7 ns/op

在大多数情况下,空接口应该对应用程序的性能会产生影响。

golang 接口_「Golang系列」 深入理解Golang Empty Interface (空接口)相关推荐

  1. java 常用 函数式接口_「java8系列」神奇的函数式接口

    前言 在上一篇Lambda的讲解中我们就提到过函数式接口,比如:Consumer consumer = (s) -> System.out.println(s);其中Consumer就是一个函数 ...

  2. java8 lambda maplist排序_「java8系列」流式编程Stream

    前言 「Java8系列」神秘的Lambda 「Java8系列」神奇的函数式接口 继上两篇之后,本文已经java8系列的第三篇了.本篇文章比较长,但我希望大家都能认真读完.读不完可以先收藏,在找时间读. ...

  3. golang 接口_「实战」助力数据库开发之接口篇 - Golang 连接 Greenplum

    Greenplum 作为一款强大的 HTAP 数据库,针对大多数流行语言都有相应的连接库.大部分均是与 PostgreSQL 采用相同的接口,但是也有部分接口是 Greenplum 专门优化后用于自身 ...

  4. java8堆内存模型_「GC系列」JVM堆内存分代模型及常见的垃圾回收器

    1. 内存分代模型 为什么要说JVM的内存分代模型呢,因为内存分代和垃圾回收器的运行是有关系的. 现在大部分用到的垃圾回收器在逻辑上是分代的,除了G1之外的其他垃圾回收器在逻辑上和物理上都是分代的. ...

  5. 直接请求接口_「软件测试教程」基于postman进行接口测试实战

    一:接口测试前准备 接口测试是基于协议的功能黑盒测试,在进行接口测试之前,我们要了解接口的信息,然后才知道怎么来测试一个接口,如何完整的校验接口的响应值. 那么问题来了,那接口信息从哪里获取呢?常用的 ...

  6. flask中文文档_「Flask系列」 初识Flask

    引子 作者有多年的编程打杂经验,之前一直参与基于Java的各种项目以及产品规划与设计,后因自己创业维持一家小公司,有些项目与产品,想降低开发成本,故在公司内部推行基于Python Flask的后端开发 ...

  7. python文件之间的相互调用_「Python 系列」 Python 生成器函数详解

    Python的生成器函数提供了一种强大的机制来管理数据和计算资源,但是对于Python的新手来说,它们不一定直观.在本文中,我将分解生成器的机制,同时还介绍我希望是一个有启发性的示例:用于管理和流传输 ...

  8. mongdb 建立了索引唯一性还能重复插入?_「数据库系列」Postgres性能调优——Index...

    在本文中,我们将探讨如何通过使用Explain和Analyze来分析慢查询,以及使用索引来修改和增强查询时间来解决慢查询. Postgres支持在表上使用各种索引,以加快查询速度. 多列索引 多列B树 ...

  9. arcpy实现空间查询_「实战系列」GP+Roaringbitmap,亿级会员十万级标签毫秒级查询...

    在大数据处理和应用场景中经常需要从亿级甚至十亿级会员中搜索出符合特定标签的会员.很多企业都会使用 HBase 或者 Hive + Hadoop 的方式,这样的方式查询效率非常慢,在标签非常多的情况下计 ...

最新文章

  1. 成员函数的const究竟修饰的是谁
  2. 设计模式总结 (3)创建内存型模式
  3. 如何使用java synchronized进行线程同步 .
  4. 【图的DFS】图的DFS非递归算法
  5. matlab自带kfcm函数,kfcmFun.m
  6. WhqDatabase 我自己用C#开发的列式数据库
  7. Java项目性能监控和调优工具-Javamelody
  8. json and .net
  9. Android应用程序组件Content Provider在应用程序之间共享数据的原理分析
  10. 大一计算机引论知识点,计算机引论知识点2015精选.doc
  11. 抽象代数基础教程(a first course in abstract algebra) 英文版 pdf下载
  12. android 分组柱状图_Android自定义柱状图控件
  13. python列联表分析
  14. 亚马逊Kindle电子书在线管理网站,管理我的内容和设备入口,如何进入
  15. 小程序商城制作一个需要多少钱?
  16. 小米3的卡槽,卡住了
  17. 多路PT100转RS485模块
  18. Xcode 真机调试失败:Errors were encountered while preparing your device for development
  19. 嵌入式Linux应用开发 1.系统编程 文件IO:open close write read lseek 通过文件io实现cp命令
  20. 如何准备机器学习数据集_数据准备技术及其在机器学习中的重要性

热门文章

  1. scss提取 vue_vue 中使用sass实现主体换肤
  2. 热式气体质量流量计检定规程_热式气体质量流量计基于热扩散原理
  3. 数据 3 分钟 | 腾讯云最新研究论文被国际顶级会议收录、员工因删库被法院判刑7年、elastic 宣布将更改开源协议...
  4. 大牛出招|分分钟解决 MySQL 查询速度慢与性能差
  5. MySQL 数据库救火:磁盘爆满了,怎么办?
  6. 从零开始学python | 使用Python映射,过滤和缩减函数:所有您需要知道的
  7. 数据库的两个好帮手:pagehack和pg_xlogdump
  8. 代码也能“杀”虫:此虫,真虫非Bug也
  9. 【华为云技术分享】MongoDB经典故障系列二:如何限制最大连接数?
  10. Python装饰器总结,带你几步跨越此坑!