go语言反射机制、reflect.TypeOf、 reflect.ValueOf、字符串处理(详解)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: go语言反射机制、reflect.TypeOf、 reflect.ValueOf、字符串处理(详解)

文章目录


前言

一、反射基本概念

①go语言反射为何而生?

②反射弊端

③怎样使用反射机制?

一、反射使用到的库及常用函数

①用到的库

②常用的字符串处理函数

(1) 将字符串加载为固定的类型strconv.ParseBool()

(2)去除字符串首尾空格strings.TrimSpace()

(3)获取特定子串的下标

(4)将字符串转换为数值strconv.Atoi()

(5)将数值转换为字符串strconv.Itoa()

③reflect包下的常用函数

(1)reflect.TypeOf

(2)reflect.ValueOf

④反射结构体类型变量

以下操作的环境条件

①反射对象可调用的方法

(1)NumField()

(2)Field()

(3)Type()

(4)Name()

(5)Kind()

(6)Elem()

(7)NumMethod()

(8)Method()

②反射对象可调用的属性

(1)name,属性字段名

(2)type,属性类型

(3)Anonymous,属性是否是匿名字段

(4)Offset,包

(5)Index,属性索引

二、使用反射实现配置文件的读取

1.配置文件包含的内容(文件名myconfig.ini,文件名可以自己起)

①配置文件构成

②配置文件中包含的信息

2.配置文件需求分析

①配置文件作用

①配置文件实现步骤

3.代码实现(带详细注释)

总结

GO GO GO !


前言


众所周知,go语言是一门静态编程语言,变量的类型在进行程序的编写时均是写死的,没有办法在运行时进行改变,您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性,今天侧重介绍一下反射机制,带大家学习一下怎样使用反射完成配置文件的读取,期间会介绍一些字符串处理函数


一、反射基本概念


go语言反射为何而生?


  为了我们使用方便,我们可以对数据进行动态赋值,以提高代码的灵活性与可复用性
  为了使我们的程序更加灵活,我们使用反射的方式将数据的类型变为动态的,如果使
  用switch语句进行操作的话会使得代码十分臃肿,并且有用户自定义的数据类型,很难将类型写全


②反射弊端


  使用反射,涉及到很多包,需要调用很对函数,效率会有所下降
  但是使用一丢丢效率换取很大的方便我们非常乐意


③怎样使用反射机制?


一、反射使用到的库及常用函数


有官方写好的第三方包,包内有接口TypeOf与ValueOf,从接口中获取目标对象的信息
  TypeOf:动态的获取从函数接口中传进去的变量的类型,如果为空则返回值为nil(获取类型对象)
    可以从该方法获取的对象中拿到字段的所属类型,字段名,以及该字段是否是匿名字段等信息
    还可以获取到与该字段进行绑定的tag
  ValueOf:获取从函数入口传进去变量的值(获取值对象)
    该方法获取到的对象是对应的数据信息区域(具体的值可以通过Filed(index)直接拿到对应位置的值)
      FieldByName(字段名)
      可以通过该函数拿到成员属性的详细信息(返回值与ValueOf一样)
  注:指针类型经过以上函数拿到的还是指针类型,结构体还是结构体类型
  Elem()方法:
    该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)


①用到的库


import (
  "errors"
  "fmt"
  //读取配置文件用到的库(文件部分后期还会进行详细介绍)
  "io/ioutil"
  //反射用到的东西一般都在这里面
  "reflect"
  //处理字符串用到的库
  "strconv"
  "strings"
)


②常用的字符串处理函数


(1) 将字符串加载为固定的类型strconv.ParseBool()


boolV, err := strconv.ParseBool("true")//这里的ParseBool可以换成ParseInt等,你需要的函数


示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。


(2)去除字符串首尾空格strings.TrimSpace()


  str := " 1234=56789 "
  str=strings.TrimSpace(str)


(3)获取特定子串的下标


  str := " 1234=56789 "
  index:=strings.Index(str, "=5")


(4)将字符串转换为数值strconv.Atoi()


i, _ := strconv.Atoi("1433223")//array to number
• 1


(5)将数值转换为字符串strconv.Itoa()


  s := strconv.Itoa(10)//number to array


③reflect包下的常用函数


(1)reflect.TypeOf


  参数是要进行类型获取的变量,可以是任意类型
  返回值是一个 reflect.Type接口,这个接口实现了一系列方法,可以获取到传入对象的详细类型信息


(2)reflect.ValueOf


  参数是要进行类型获取的变量,可以是任意类型
  返回值是一个 reflect.Value接口,这个接口实现了一系列方法,可以获取到传入对象的详细值信息


代码如下(示例):


//定义一个结构体
 type stu struct {
  id   string
  name string
  age  int
}
//对结构体进行初始化
var stu1 stu
stu1.age = 20
stu1.id = "hahaha"
stu1.name = "小明"
sTructType := reflect.TypeOf(stu1)
sTructValue := reflect.ValueOf(stu1)
// 结构体值
fmt.Println("值是:", sTructValue)
//类型
fmt.Println(sTructType)
//打印结果
//值是: {hahaha 小明 20}
//main.stu


④反射结构体类型变量


以下操作的环境条件


//结构体
type stu struct {
  id   string
  name string
  age  int
}
//方法
func (st stu) Speak() {
}
func (st stu) Run(x int) {
  fmt.Println("跑起来,润润润!")
}
func (st stu) Haha() bool {
  fmt.Println("你是成功的!")
  return true
}
//进行反射
var stu1 stu
stu1.age = 20
stu1.id = "hahaha"
stu1.name = "小明"
sTructType := reflect.TypeOf(stu1)
sTructValue := reflect.ValueOf(stu1)


①反射对象可调用的方法


(1)NumField()


//获取属性个数
fmt.Println(sTructType.NumField())
//打印结果
//3


(2)Field()


//获取指定位置属性的详细类型
fmt.Println(sTructType.Field(0))
//打印结果
//{id main string  0 [0] false}
//以上结果含义分别为:
//Type      Type      // field type字段名
//Tag       StructTag // field tag string所在包
//Offset    uintptr   // offset within struct, in bytes//基类型名称
//Index     []int     // index sequence for Type.FieldByIndex//下标
//Anonymous bool      // is an embedded field//是不是匿名字段


(3)Type()


获取结构体属性的类型


(4)Name()


// 直接获取到结构体名,想要获取包名+结构体名时直接使用sTructType
fmt.Println("类型是:", sTructType.Name())
//打印结果
//类型是: stu


(5)Kind()


// 获取该类型最初的基本
fmt.Println(sTructType.Kind())
//打印结果
//struct


(6)Elem()


该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)


(7)NumMethod()


获取方法个数,也就是结构体实现了哪些接口


(8)Method()


//  获取结构体对应的方法
fmt.Printf("该结构体有方法%d个\n", sTructType.NumMethod())
for i := 0; i < sTructType.NumMethod(); i++ {
  tempMethod := sTructType.Method(i)
  // 获取方法的属性,方法的函数参数以及返回值(仅针对公有方法有效,无法统计私有方法)
  fmt.Println("第", i, "个字段对应的信息为:", tempMethod.Name, tempMethod.Type)
}
//打印结果
/*
该结构体有方法3个
第 0 个字段对应的信息为: Haha func(main.stu) bool
第 1 个字段对应的信息为: Run func(main.stu, int)
第 2 个字段对应的信息为: Speak func(main.stu)
*/


②反射对象可调用的属性


(1)name,属性字段名

(2)type,属性类型

(3)Anonymous,属性是否是匿名字段

(4)Offset,包

(5)Index,属性索引


二、使用反射实现配置文件的读取


1.配置文件包含的内容(文件名myconfig.ini,文件名可以自己起)


①配置文件构成


  配置文件可以进行注释,通过#或者;实现
  配置文件进行分块处理,每一块的模块名用[]包裹起来
  配置文件每一个模块有许多配置,每一个配置信息由键值对组成,中间通过=或者:隔开


②配置文件中包含的信息


; MySQL配置信息
[mysql]
address=127.0.0.1
port=2206
username=root
userpasswd=123456
; []
;  [
#redies配置信息
[redies]
host=localhost
port=6379
password=root
database=100
test=false
go=123
; =false
; test=


2.配置文件需求分析


①配置文件作用


  配置文件记录服务器的参数配置
  比如数据库的密码端口号,用户名密码等,如果不存进配置文件的话后期维护起来十分麻烦
    比如数据库密码泄露,需要更新数据库密码
  所以需要将容易变更的应用配置写进配置文件
  通过反射机制对其进行解析,以得到更加灵活的程序


①配置文件实现步骤


解析配置文件需要的步骤
  0.需要对参数进行校验,因为经过反射需要将文件内的内容进行解析,并读取到结构体对象内
    所以需要改原对象中对应的数据,所以就要传入的是指针类型,否则无法修改原来的值
    检验结果需要传入的参数是结构体指针,否则返回报错
  1.读取文件得到字节类型的数据,并将数据按行划分,去除注释行与空行
    对1得到的结果进行迭代
    ①.去除行首尾的空格,判断本行是否是模块行,是的话进去通过模块名拿到结构体,并记录下来
    ②.如果是非模块名将键值进行分割(分割为key,value),结构体属性中没有该对应字段名进行忽略
      只有键没有值的忽略,只有值没有键的抛异常
      迭代①中记录下来的结构体中的字段,并将字段与本行的key进行比对
      相同的话将对应的value给该字段(key找不到匹配字段的话忽略)


3.代码实现(带详细注释)


 package main
import (
  "errors"
  "fmt"
  "io/ioutil"
  "reflect"
  "strconv"
  "strings"
)
//以下的``中包含的是一种解析数据的格式,是一一对应的映射关系
//比如:Address 对应ini文件内的address字段
//在进行反射的时候将address对应的数据解析到Address字段中。
type MysqlConfig struct {
  Address    string `ini:"address"`
  Port       int    `ini:"port"`
  Username   string `ini:"username"`
  Userpasswd string `ini:"userpasswd"`
}
type RedisConfig struct {
  Host     string `ini:"host"`
  Port     int    `ini:"port"`
  Password string `ini:"password"`
  Database int    `ini:"database"`
  Test     bool   `ini:"test"`
}
type Config struct {
  MysqlConfig `ini:"mysql"`
  RedisConfig `ini:"redies"`
  // int         `ini:"mysql"`
}
func (c Config) Println() {
  mType := reflect.TypeOf(c)
  mValue := reflect.ValueOf(c)
  for i := 0; i < mType.NumField(); i++ {
    fmt.Println("成员结构体属性如下:", mType.Field(i).Name)
    tempValue := mValue.FieldByName(mType.Field(i).Name)
    // tempValue := mValue.FieldByName(mType.Field(i).Name)
    // 获取到的是值域,可以通过该对象进行每个字段的值进行获取
    tempType := tempValue.Type()
    // tempType := tempValue.Type()
    //获取到的是值域的具体类型信息
    // 再次迭代取值
    for j := 0; j < tempType.NumField(); j++ {
      fmt.Println("属性类型:", tempType.Field(j).Type, "属性名:", tempType.Field(j).Name, "属性值:", tempValue.Field(j))
    }
    fmt.Println()
  }
}
func Loading_INI(filename string, data interface{}) error {
  // 获取data的类型
  dataType := reflect.TypeOf(data)
  // 获取data对应的值
  dataValue := reflect.ValueOf(data)
  // 判断传进来的参数是不是指针类型,并且判断一下这个指针类型指向的是不是结构体
  if dataType.Kind() != reflect.Ptr || dataType.Elem().Kind() != reflect.Struct {
    return errors.New("the 2 args must be struct ptr")
  }
  // 读取配置文件
  bytes, err := ioutil.ReadFile("myconfig.ini")
  if err != nil {
    return err
  }
  iniLines := strings.Split(string(bytes), "\r\n")
  // 用于暂时存储ini配置文件内的模块名对应的结构体名
  var ComfigModelStructName string
  // 迭代每一行数据,进行对应的操作
  for index, line := range iniLines {
    // 去除行前面的空格
    line = strings.TrimSpace(line)
    // 判断是不是注释行与空行,是的话直接跳过本次循环
    if len(line) == 0 || (line[0] == '#' || line[0] == ';') { //排除掉空行,以及注释
      continue
    } else if line[0] == '[' {
      // 如果该行没有]
      if line[len(line)-1] != ']' {
        return fmt.Errorf("the '[' has nomatch ']' in %d line", index)
        //如果该行里面有[]却没有字段
      } else if len(strings.TrimSpace(line[1:len(line)-1])) == 0 {
        return fmt.Errorf("the [ ] has none in %d line", index)
        //字段书写规范
      } else {
        // 将配置文件内的模块名拿出来
        for i := 0; i < dataType.Elem().NumField(); i++ {
          if line[1:len(line)-1] == string(dataType.Elem().Field(i).Tag.Get("ini")) {
            ComfigModelStructName = dataType.Elem().Field(i).Name
          }
        }
        fmt.Println("结构体", ComfigModelStructName, "正在解析!")
      }
    } else {
      // 将数据以等号为分隔符进行分割
      tempIndex := strings.Index(line, "=")
      Mkey := line[0:tempIndex]
      Mvalue := line[tempIndex+1:]
      // 如果只有值没有键,那么抛出异常,只有键没有值,那么数据为默认值直跳过本次循环
      if len(strings.TrimSpace(Mkey)) == 0 {
        return fmt.Errorf("the Key is null in %d line", index+1)
      } else if len(strings.TrimSpace(Mvalue)) == 0 {
        continue
      } else {
        // 根据对应的结构体名拿到结构体具体信息
        sValue := dataValue.Elem().FieldByName(ComfigModelStructName)
        sType := sValue.Type()
        if sType.Kind() != reflect.Struct {
          return errors.New("the data members must be Struct")
        }
        for i := 0; i < sType.NumField(); i++ {
          if sType.Field(i).Tag.Get("ini") == Mkey {
            switch sType.Field(i).Type.Kind() {
            case reflect.String:
              sValue.Field(i).SetString(Mvalue)
            case reflect.Bool:
              mbool, err := strconv.ParseBool(Mvalue)
              if err != nil {
                return fmt.Errorf("the value must be true or false in %d line", index+1)
              }
              sValue.Field(i).SetBool(mbool)
            case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
              mint, err := strconv.ParseInt(Mvalue, 10, 64)
              if err != nil {
                return fmt.Errorf("the value must be number in %d line", index+1)
              }
              sValue.Field(i).SetInt(mint)
            case reflect.Float32, reflect.Float64:
              mfloat, err := strconv.ParseInt(Mvalue, 10, 64)
              if err != nil {
                return fmt.Errorf("the value must be number in %d line", index+1)
              }
              sValue.Field(i).SetInt(mfloat)
            }
          }
        }
      }
    }
  }
  fmt.Println("所有配置项解析完毕!")
  return nil
}
/*
  dataType.Elem().Kind()    写法会有很大的隐患,因为如果dataType不是指针类型就会报错,Elem方法只是为了拿到指针指向的元素
  if dataType.Kind() != reflect.Ptr || dataType.Elem().Kind() != reflect.Struct 不报错
  因为go语言判断的时候有阻断机制,在第一个判断条件为为真时不会去判断第二个条件,所以||后面的语句没有执行,不会报错
*/
func main() {
  var cg1 Config
  err := Loading_INI("myconfig.ini", &cg1)
  if err != nil {
    fmt.Println("error : ", err)
  }
  cg1.Println()
}
/*
打印结果
结构体 MysqlConfig 正在解析!
结构体 RedisConfig 正在解析!
所有配置项解析完毕!
成员结构体属性如下: MysqlConfig
属性类型: string 属性名: Address 属性值: 127.0.0.1
属性类型: int 属性名: Port 属性值: 2206
属性类型: string 属性名: Username 属性值: root    
属性类型: string 属性名: Userpasswd 属性值: 123456
成员结构体属性如下: RedisConfig
属性类型: string 属性名: Host 属性值: localhost   
属性类型: int 属性名: Port 属性值: 6379
属性类型: string 属性名: Password 属性值: root    
属性类型: int 属性名: Database 属性值: 100
属性类型: bool 属性名: Test 属性值: false
*/


总结


以上是博主在进行Go语言反射模块学习的时候记下的笔记,Go语言反射可以极大地方便我们完成需求,这些还是远远不够的,还需要细化学习,弄清楚每一个函数是干嘛的。只有熟悉了才能使用的时候得心应手。

目录
相关文章
|
2天前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
6天前
|
JSON 人工智能 Go
go 反射的常见用法
go 反射的常见用法
15 4
|
6天前
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
27 1
|
1天前
|
监控 NoSQL Go
Go语言中高效使用Redis的Pipeline
Redis 是构建高性能应用时常用的内存数据库,通过其 Pipeline 和 Watch 机制可批量执行命令并确保数据安全性。Pipeline 类似于超市购物一次性结账,减少网络交互时间,提升效率。Go 语言示例展示了如何使用 Pipeline 和 Pipelined 方法简化代码,并通过 TxPipeline 保证操作原子性。Watch 机制则通过监控键变化实现乐观锁,防止并发问题导致的数据不一致。这些机制简化了开发流程,提高了应用程序的性能和可靠性。
5 0
|
4天前
|
NoSQL Go Redis
Go语言中如何扫描Redis中大量的key
在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。
13 0
|
5天前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
|
5天前
|
关系型数据库 MySQL 数据库连接
Go语言中使用sqlx来操作事务
在应用中,数据库事务保证操作的ACID特性至关重要。`github.com/jmoiron/sqlx`简化了数据库操作。首先安装SQLX和MySQL驱动:`go get github.com/jmoiron/sqlx`和`go get github.com/go-sql-driver/mysql`。导入所需的包后,创建数据库连接并使用`Beginx()`方法开始事务。通过`tx.Commit()`提交或`tx.Rollback()`回滚事务以确保数据一致性和完整性。
8 0
|
9天前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
6天前
|
SQL 关系型数据库 MySQL
Go语言中使用 sqlx 来操作 MySQL
Go语言因其高效的性能和简洁的语法而受到开发者们的欢迎。在开发过程中,数据库操作不可或缺。虽然Go的标准库提供了`database/sql`包支持数据库操作,但使用起来稍显复杂。为此,`sqlx`应运而生,作为`database/sql`的扩展库,它简化了许多常见的数据库任务。本文介绍如何使用`sqlx`包操作MySQL数据库,包括安装所需的包、连接数据库、创建表、插入/查询/更新/删除数据等操作,并展示了如何利用命名参数来进一步简化代码。通过`sqlx`,开发者可以更加高效且简洁地完成数据库交互任务。
13 1
|
6天前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
go语言后端开发学习(六) ——基于雪花算法生成用户ID