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和文件操作的主要内容,如果未能解决你的问题,请参考以下文章