go的反射reflect和文件操作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go的反射reflect和文件操作相关的知识,希望对你有一定的参考价值。

1.反射

Go语言的变量分两部分,类型信息和值信息
在Go的反射机制中,任何接口值都是由一个具体类型和具体类型的值两部分组成
reflect.TypeOf和reflect.ValueOf两个重要的函数来获取任意对象的type和value

v:=reflect.TypeOf(x)
v.Name()    // 类型名称
v.Kind()    // 类型种类

指针类型的类型名称为空,类型种类为ptr,对象的类型种类是struct
数组类型的类型名称是空,类型种类是array
切片的类型名称是空,类型种类是slice

// 反射获取变量的原始值
v := reflect.ValueOf(x)
var m = v.Int() + 12


如果传入的是一个地址,想要修改值的话,就需要加一个Elem()
v.Elem().Kind() // 获取值

func printStruct(s interface) 
	fmt.Println(reflect.TypeOf(s).Kind())   // 返回string
	fmt.Println(reflect.ValueOf(s).Kind())  // 返回string


func main() 
	a := "hello"
	printStruct(a)

获取结构体字段:
type Student struct 
	Name  string `json:"name" form:"username"`
	Age   int    `json:"age"`
	Score int    `json:"score"`


func main() 
	stu := Student
		Name:  "Hello",
		Age:   3,
		Score: 100,
	
	t := reflect.TypeOf(stu)
	v := reflect.ValueOf(stu)
	//1.通过类型变量里面的Field可以获取结构体的字段,安装索引下标
	field0 := t.Field(0)
	fmt.Printf("%#v \\n", field0)
	fmt.Println("字段名称", field0.Name)
	fmt.Println("字段类型", field0.Type)
	fmt.Println("字段Tag:", field0.Tag.Get("json"))
	fmt.Println("字段Tag:", field0.Tag.Get("form"))
	// 2.通过类型变量里面的FieldByName可以获取结构体的字段,根据名称获取
	field1, ok := t.FieldByName("Age")
	if ok 
		fmt.Printf("%#v \\n", field1)
		fmt.Println("字段名称", field1.Name)
		fmt.Println("字段类型", field1.Type)
		fmt.Println("字段Tag:", field1.Tag.Get("json"))
	

	// 3.通过类型变量里面的NumField获取到该结构体有几个字段
	fieldCount := t.NumField()
	fmt.Println("结构体有多少个", fieldCount, "个属性")

	// 4.通过值变量获取结构体属性对应的值
	fmt.Println(v.FieldByName("Name"))
	fmt.Println(v.FieldByName("Age"))

	for i := 0; i < fieldCount; i++ 
		fmt.Printf("属性名称:%v 属性值:%v 属性类型:%v 属性Tag:%v\\n", t.Field(i).Name, v.Field(i), t.Field(i).Type,
			t.Field(i).Tag.Get("json"))
	

获取结构体方法:
/*
reflect.Value.Method已经被废弃了。从Go 1.17开始,
它已被替换为reflect.Value.MethodByName。
建议使用新的方法以确保代码的兼容性和可移植性
*/
package main

import (
	"fmt"
	"reflect"
)

//	func printStruct(s interface) 
//		fmt.Println(reflect.TypeOf(s).Kind())
//		fmt.Println(reflect.ValueOf(s).Kind())
//	

type Student struct 
	Name  string `json:"name" form:"username"`
	Age   int    `json:"age"`
	Score int    `json:"score"`


func (s Student) SayHello() 
	fmt.Println("123456")

func (s Student) SayWorld(addr string, time string) 
	fmt.Println(addr + time)


func reflectChange(s interface) 
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s) // 修改结构体的值
	if t.Kind() != reflect.Ptr 
		fmt.Println("传入的不是结构体指针类型")
		return
	 else if t.Elem().Kind() != reflect.Struct 
		fmt.Println("传入的不是结构体指针类型")
		return
	
	// 修改结构体属性的值
	name := v.Elem().FieldByName("Name")
	name.SetString("小李")

	age := v.Elem().FieldByName("Age")
	age.SetInt(22)


func main() 
	v := Student
		Name:  "Hello",
		Age:   3,
		Score: 100,
	
	a := reflect.ValueOf(v)
	b := a.MethodByName("SayHello")
	b.Call(nil)

	// 4.执行方法传入参数(注意需要使用《值变量》,并且要注意参数,接收的参数是[]reflect.Value的切片)
	var params []reflect.Value
	params = append(params, reflect.ValueOf("新西兰"))
	params = append(params, reflect.ValueOf("今年"))
	a.MethodByName("SayWorld").Call(params) // 执行方法传入参数

	// 5.获取方法数量
	fmt.Println("方法数量", a.NumMethod())

	// 6.修改反射体的属性
	reflectChange(&v)
	fmt.Printf("%#v\\n", v)

反射是一个强大的工具,可以写出更灵活的代码,但是反射不应该被滥用
因为:

  • 基于反射的代码是极其脆弱,反射中的类型错误会在真正运行的时候才会引发panic,哪可能是在写完代码很长时间之后
  • 大量使用反射的代码通常难以理解

2.文件操作

文件读操作:
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() 
	// 一、读取文件
	// 法1:
	// 只读方式打开当前目录下的main.go文件
	//os.Open("D:/go_demo/demo01/main.go") // 绝对路径
	file, err := os.Open("./main.go") // 想对路径
	defer file.Close()                //文件打开之后必须关闭
	if err != nil 
		fmt.Println(err)
		return
	
	// 读取文件里面的内容
	var strSlice []byte
	var tempSlice = make([]byte, 128)
	for 
		n, err := file.Read(tempSlice)
		if err == io.EOF 
			fmt.Println("读取完毕")
			break
		
		if err != nil 
			fmt.Println("读取失败")
			return
		
		//fmt.Printf("读取到了%v个字节", n)
		//做为形参的参数前的三个点意思是可以传0到多个参数,变量后三个点意思是将一个切片或数组变成一个一个的元素,俗称将数组打散
		// 因为是byte切片,所以想看到的话,需要先转换成字符串
		strSlice = append(strSlice, tempSlice[:n]...) //注意这个写法
		fmt.Println(string(tempSlice))
	

	// 法2:bufio 读取文件
	file, err = os.Open("./main.go") // 想对路径
	defer file.Close()               //文件打开之后必须关闭
	if err != nil 
		fmt.Println(err)
		return
	
	// bufio读取文件
	var fileStr string
	reader := bufio.NewReader(file)
	for 
		str, err := reader.ReadString(\'\\n\') // 表示一次读取一行,注意单引号
		if err == io.EOF 
			fileStr += str // 如果有空行会出错,不完整,所以加上这一条
			break
		
		if err != nil 
			fmt.Println(err)
			return
		
		fmt.Println(str)
		fileStr += str
	
	fmt.Println(fileStr)
	// 法3:ioutil 读取文件
	// 上述两种都是通过流读取,第三种是一次性读取
	// 但是这种方法在最新版本的go里面已经废除了

文件写操作:
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

func main() 
	// 写入文件
	// 一、写入文件
	//	法1:
	// 1、打开文件 file,err := os.OpemFile("",os.O_CREATE|os.O_RDWR,0666)
	file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) // 第二个参数表示模式,最后一个参数表示在linux下的权限
	defer file.Close()
	if err != nil 
		fmt.Println(err)
		return
	
	// 写入文件
	for i := 0; i < 10; i++ 
		file.WriteString("直接写入的字符串数据" + strconv.Itoa(i) + "\\r\\n") // 在记事本里面只写\\n不能识别到,必须写\\r\\n
	

	// file.Write写入,但是需要传入切片
	var str = "直接写入byte"
	file.Write([]byte(str))

	// 法2:通过 bufio写入
	Writer := bufio.NewWriter(file)
	for i := 0; i < 10; i++ 
		Writer.WriteString("你好golang") // 在记事本里面只写\\n不能识别到,必须写\\r\\n
	
	Writer.Flush() // 把缓存数据写入文件

文件复制、删除、重命名操作:
package main

import (
	"fmt"
	"io"
	"os"
)

func CopyFile(srcFileName string, dstFileName string) (err error) 
	sFile, err1 := os.Open(srcFileName)
	defer sFile.Close()
	dFile, err2 := os.OpenFile(dstFileName, os.O_CREATE|os.O_WRONLY, 0666)
	defer dFile.Close()
	if err1 != nil 
		return err1
	
	if err2 != nil 
		return err2
	
	var tempSlice = make([]byte, 128)
	for 
		// 读取数据
		n1, e1 := sFile.Read(tempSlice)
		if err == io.EOF 
			break
		
		if e1 != nil 
			return e1
		
		// 写入数据
		_, e2 := dFile.Write(tempSlice[:n1])
		if e2 != nil 
			return e2
		
	
	return nil


func main() 
	// 以文件流的方式复制文件
	srcFile := "./test.txt"
	dstFile := "./target.txt"
	err := CopyFile(srcFile, dstFile)
	if err != nil 
		fmt.Printf("拷贝完成\\n")
	 else 
		fmt.Printf("拷贝错误 err=%v\\n", err)
	

	// 创建目录
	err1 := os.Mkdir("./abc", 0666)
	//err1 := os.MkdirAll("./abc/def/jkl", 0666) // 也可以创建多层目录
	if err1 != nil 
		fmt.Println(err)
	

	// 删除文件和目录Remove可以删除文件也可以删除目录
	//err = os.Remove("target.txt")
	/*
		err = os.Remove("./abc") // 删除目录
		if err != nil 
			fmt.Println(err)
		
		// 一次删除多个文件removeAll,包括这个目录下的所有文件
		err = os.RemoveAll("./abc") // 删除目录
		if err != nil 
			fmt.Println(err)
		

	*/

	// 重命名rename
	err = os.Rename("./test.txt", "./hello.txt")
	if err != nil 
		fmt.Println(err)
	
	fmt.Println("重命名成功")

Go反射中的type和kind比较

前言

Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 。任意值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。

在Go语言程序中,使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

Type 和 Kind 的区别

Type 是类型。Kind 是类别。Type 和 Kind 可能相同,也可能不同。通常基础数据类型的 Type 和 Kind 相同,自定义数据类型则不同。

基础类型

输出结果为 int  int 

func main() 
    var a int
    typeA := reflect.TypeOf(a)
    fmt.Println(typeA.Name(), typeA.Kind())

自定义类型

输出结果为cat  struct

	type cat struct 
		name string
	 
	typeCat := reflect.TypeOf(cat) 
	fmt.Println(typeCat.Name(), typeCat.Kind())

对于反射中的 kind 我们既可以通过 reflect.Type 来获取,也可以通过 reflect.Value 来获取。他们得到的值和类型均是相同的。

种类(Kind)

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

type Kind uint

const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

总结

 相较于 Type 而言,Kind 所表示的范畴更大。可以理解为数学中的包含关系。类似于家用电器(Kind)和电视机(Type)之间的对应关系。类似于电商系统中的spu和sku的关系一样。

 

以上是关于go的反射reflect和文件操作的主要内容,如果未能解决你的问题,请参考以下文章

Go反射编程

一文初探 Go reflect 包

Go语言的反射机制

[Go关于反射] reflect包

[Go关于反射] reflect包

[Go关于反射] reflect包