go的反射reflect和文件操作

发布时间 2023-04-09 03:05:30作者: 喵喵队立大功

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("重命名成功")
}