Go反射底层原理及数据结构解析

目录
  • 1. 反射的引入与介绍
  • 2. 反射的数据结构
  • 3. 如何通过反射对象来修改原数据对象的值?

1. 反射的引入与介绍

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

需要反射的 2 个常见场景:

  • 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

Java中动态代理与AOP的实现,就要借助这种操作。而对于我们Go语言来说,也提供了反射的机制。

有了前面对于interface{}底层数据结构的了解,Go中的每个实例对象可以分为两快,类型信息与数值信息。而我们Go中提供的反射机制,能分别拿到这两块信息。

数据interface中保存有结构数据,拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了,其实以上就是Go反射通俗的原理。

2. 反射的数据结构

type Type interface {
    // 所有的类型都可以调用下面这些函数
	// 此类型的变量对齐后所占用的字节数
	Align() int
	// 如果是 struct 的字段,对齐后占用的字节数
	FieldAlign() int
	// 返回类型方法集里的第 `i` (传入的参数)个方法
	Method(int) Method
	// 通过名称获取方法
	MethodByName(string) (Method, bool)
	// 获取类型方法集里导出的方法个数
	NumMethod() int
	// 类型名称
	Name() string
	// 返回类型所在的路径,如:encoding/base64
	PkgPath() string
	// 返回类型的大小,和 unsafe.Sizeof 功能类似
	Size() uintptr
	// 返回类型的字符串表示形式
	String() string
	// 返回类型的类型值
	Kind() Kind
	// 类型是否实现了接口 u
	Implements(u Type) bool
	// 是否可以赋值给 u
	AssignableTo(u Type) bool
	// 是否可以类型转换成 u
	ConvertibleTo(u Type) bool
	// 类型是否可以比较
	Comparable() bool
	// 下面这些函数只有特定类型可以调用
	// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
	// 类型所占据的位数
	Bits() int
	// 返回通道的方向,只能是 chan 类型调用
	ChanDir() ChanDir
	// 返回类型是否是可变参数,只能是 func 类型调用
	// 比如 t 是类型 func(x int, y ... float64)
	// 那么 t.IsVariadic() == true
	IsVariadic() bool
	// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
	Elem() Type
	// 返回结构体类型的第 i 个字段,只能是结构体类型调用
	// 如果 i 超过了总字段数,就会 panic
	Field(i int) StructField
	// 返回嵌套的结构体的字段
	FieldByIndex(index []int) StructField
	// 通过字段名称获取字段
	FieldByName(name string) (StructField, bool)
	// FieldByNameFunc returns the struct field with a name
	// 返回名称符合 func 函数的字段
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	// 获取函数类型的第 i 个参数的类型
	In(i int) Type
	// 返回 map 的 key 类型,只能由类型 map 调用
	Key() Type
	// 返回 Array 的长度,只能由类型 Array 调用
	Len() int
	// 返回类型字段的数量,只能由类型 Struct 调用
	NumField() int
	// 返回函数类型的输入参数个数
	NumIn() int
	// 返回函数类型的返回值个数
	NumOut() int
	// 返回函数类型的第 i 个值的类型
	Out(i int) Type
        // 返回类型结构体的相同部分
	common() *rtype
	// 返回类型结构体的不同部分
	uncommon() *uncommonType
}
type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,但是值得注意的是:interface的“类型”和“数据值”是在“一起的”,而反射的“类型”和“数据值”是分开的。

TypeValue提供了非常多的方法:例如获取对象的属性列表、获取和修改某个属性的值、对象所属结构体的名字、对象的底层类型(underlying type)等等

Go中的反射,在使用中最核心的就两个函数:

  • reflect.TypeOf(x)
  • reflect.ValueOf(x)

这两个函数可以分别将给定的数据对象转化为以上的TypeValue。这两个都叫做反射对象

3. 如何通过反射对象来修改原数据对象的值?

在Go中,任何函数的参数都是值的拷贝,而非原数据。

反射函数reflect.ValueOf()也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象.

那如何修改呢?

首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。

var x float64 = 5.7
v := reflect.ValueOf(&x)

时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v)。 Go提供了另外一个方法Elem()

p := v.Elem()
fmt.Println(p.CanSet()) // true

p.SetFloat(6.6)
fmt.Println(x) // 6.6

经过以上操作,就可以修改原数据了,完整过程如下:

var x float64 = 5.7
v := reflect.ValueOf(&x)
p := v.Elem()
fmt.Println(p.CanSet()) // true
p.SetFloat(6.6)
fmt.Println(x) // 6.6

到此这篇关于Go反射底层原理及数据结构解析的文章就介绍到这了,更多相关 Go反射原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • go语言通过反射获取和设置结构体字段值的方法

    本文实例讲述了go语言通过反射获取和设置结构体字段值的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: type MyStruct struct {         N int } n := MyStruct{ 1 } // get immutable := reflect.ValueOf(n) val := immutable.FieldByName("N").Int() fmt.Printf("N=%d\n", val) // prints

  • 浅谈Go语言中的结构体struct & 接口Interface & 反射

    结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struct类型理解为类,可以定义方法,和函数定义有些许区别: struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User var user1 *User = &User{} var user2 *User = new(User) struct使用 下面示例中user1和

  • Golang 利用反射对结构体优雅排序的操作方法

    最近开始实习,工作技术栈主要Python和Golang,目前的任务把Python模块重构为GO模块,然后出现了一个问题,就是要将一个结构体按结构体中各个字段进行排序,然后写入Redis,对于Pyhon来说for循环就能解决,但是对于Go语言来说,每一次排序都要写一个比较函数,写出来的代码太丑,非常长,代码结构是一致,只是比较字段不一样而已,个人无法接受啊,网上搜索也没搜索到合适解决方法,所以自己想了一个解决方法来优雅排序. 比较函数: func reflectCmp(i, j interface

  • Go easyjson使用及反射原理

    目录 安装easyjson 使用easyjson 使用示例 如果使用go语言自带的json库,使用的是反射,而go语言中反射性能较低.easyjson就是一个比较好的替代方案. esayjson安装(https://gitcode.net/mirrors/mailru/easyjson?utm_source=csdn_github_accelerator) go get -u github.com/mailru/easyjson go install github.com/mailru/easy

  • golang 如何用反射reflect操作结构体

    背景 需要遍历结构体的所有field 对于exported的field, 动态set这个field的value 对于unexported的field, 通过强行取址的方法来获取该值(tricky?) 思路 下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多 simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针.然后解引用. 接下来遍历结构体的每个field, exported字段是

  • go语言通过反射创建结构体、赋值、并调用对应的操作

    我就废话不多说了,大家还是直接看代码吧~ package main import ( "fmt" "reflect" "testing" ) type Call struct { Num1 int Num2 int } func (call Call) GetSub(name string){ fmt.Printf("%v 完成了减法运算,%v - %v = %v \n", name, call.Num1, call.Num2

  • 图文详解go语言反射实现原理

    Go反射的实现和 interface 和 unsafe.Pointer 密切相关.如果对golang的 interface 底层实现还没有理解,可以去看我之前的文章: Go语言interface底层实现 , unsafe.Pointer 会在后续的文章中做介绍. (本文目前使用的Go环境是Go 1.12.9) interface回顾 首先我们简单的回顾一下interface的结构,总体上是: 细分下来分为有函数的 iface 和无函数的 eface (就是 interface{} ); 无函数的

  • golang通过反射设置结构体变量的值

    如果需要动态设置struct变量field的情况下, 可以利用reflect来完成. 代码如下: package main import ( "fmt" "reflect" ) // 定义结构体Person type Person struct { Name string Age int } func main() { person := Person{} fmt.Println(person) // 修改前 { 0} pp := reflect.ValueOf(&

  • Go反射底层原理及数据结构解析

    目录 1. 反射的引入与介绍 2. 反射的数据结构 3. 如何通过反射对象来修改原数据对象的值? 1. 反射的引入与介绍 在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问.检测和修改它本身状态或行为的一种能力.用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为. 需要反射的 2 个常见场景: 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好:也可能是传入的类型很多,这些类型并不能统一表示.这时反射就会用的上了. 有时候需要根据某些条

  • Go select使用与底层原理讲解

    目录 1. select的使用 2. 底层原理 3. 数据结构 4. 几种常见 case case 1 case2 case3 case4 1. select的使用 select 是 Go 提供的 IO 多路复用机制,可以用多个 case 同时监听多个 channl 的读写状态: case: 可以监听 channl 的读写信号 default:声明默认操作,有该字段的 select 不会阻塞 select { case chan <-: // TODO case <- chan: // TOD

  • Tomcat解析XML和反射创建对象原理

    下面通过实例代码给大家介绍Tomcat解析XML和反射创建对象原理,具体代码如下所示: import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import or

  • 通过实例解析JMM和Volatile底层原理

    这篇文章主要介绍了通过实例解析JMM和Volatile底层原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它是标准化的,用于屏蔽硬件和操作系统对内存访问的差异性. 2.JMM和8大原子操作结合 3.volatile的应用及底层原理探究 volat

  • SpringBoot整合log4j日志与HashMap的底层原理解析

    一,SpringBoot与日志 1.springboot整合log4j日志记录 1.在resources目录下面创建日志文件,并引入: 代码如下(示例): #log4j.rootLogger=CONSOLE,info,error,DEBUG log4j.rootLogger=info,error,CONSOLE,DEBUG log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=o

  • Activiti工作流学习笔记之自动生成28张数据库表的底层原理解析

    网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理. 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地方,例如,框架用到以命令模式.责任链模式.模板模式等优秀的设计模式来进行框架的设计. 故而,是值得好好研究下Activiti这个框架的底层实现. 我在工作当中现

  • Python matplotlib底层原理解析

    目录 1. matplotlib 框架组成 2. 脚本层(scripting) 3. 美工层(artist) 4. 后端层(backend) 复习回顾: 前期,我们已经学习了matplotlib模块相关的基础知识,对 matplotlib 模块折线图.饼图.柱状图进行操作. 我们都知道matplotlib 是偏向底层用于可视化数据处理的库,我们在绘制图表的时候主要步骤主要有四大步骤: 导入 matplotlib.pplot库 使用pandas/numpy模块对数据进行整分析理 调用pyplot中

  • Golang底层原理解析String使用实例

    目录 引言 String底层 stringStruct结构 引言 本人因为种种原因(说来听听),放弃大学学的java,走上了golang这条路,本着干一行爱一行的情怀,做开发嘛,不能只会使用这门语言,所以打算开一个底层原理系列,深挖一下,狠狠的掌握一下这门语言 废话不多说,上货 String底层 既然研究底层,那就得全方面覆盖,必须先搞一下基础的东西,那必须直接基本数据类型走起啊, 字符串String的底层我看就很基础 string大家应该都不陌生,go中的string是所有8位字节字符串的集合

  • Docker基本概念和底层原理解析

    目录 1.Docker的底层原理 2.Docker中常用的基本概念 3.run命令的运行流程 4.为什么Docker比VM快 Docker架构图: 我们依照Docker架构图进行Docker基础概念的说明. 1.Docker的底层原理 Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器.容器是一个运行时环境,就好比是我们前面说到的集装箱. 例如架构图中的客户端(Clien

  • Spring Boot 底层原理基础深度解析

    目录 1. 底层注解@Configuration 2. 底层注解@Import 3. 底层注解@Conditional 1. 底层注解@Configuration @Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法: 项目基本结构: 两个Bean组件: User.java package com.menergy.boot.bean; /** * 用户 */ public class User { private String name; private Inte

随机推荐