Go (一) 基础部分4 -- 文件处理,命令行参数,json序列化,反射(reflect),连接redis

一、文件基本介绍

1.1、打开一个文件

基本介绍:打开一个文件用于读取,如果操作成功,返回的文件对象的方法可用于读取文件数据。如果出错,错误底层类型是"*.PathError"

func Open(name string) (*File, error)

name string:打开的文件路径

*File:返回值1,文件对象

error:返回值2,错误err


1.2、关闭一个文件

基本介绍:语法如下

func (f *File) Close() error

(f *File) Close():文件对象的Close方法

error:返回值1,错误err

打开文件,关闭文件,快速入门案例:

package main

import (
	"fmt"
	"os"
)

func main() {
	// 打开一个文件(默认有2个返回值:文件对象,错误)
	file, err:=os.Open("sudada.log")
	// 如果err有值,则输出错误
	if err != nil {
		fmt.Println("打开文件失败,错误:",err)
	}

	// 通过文件对象file的值
	fmt.Println(file) // 返回值:&{0xc000100a00}
	fmt.Println(file.Name()) // 返回值:sudada.log

	// 关闭文件(默认有1个返回值:错误)
	close_err := file.Close()
	if close_err != nil {
		fmt.Println("打开文件失败,错误:",close_err)
	}
}

1.3、读文件内容

1.读取文件内容并显示在终端(带缓冲区的方式):使用bufio.NewReader(),reader.ReadString函数和方法。

步骤:先打开文件,然后读文件,最后关闭文件

package main

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

func main() {
	// 打开一个文件(有2个返回值:文件对象,错误)
	file, err:=os.Open("sudada.log")
	// 如果err有值,则输出错误
	if err != nil {
		fmt.Println("打开文件失败,错误:",err)
	}

	// 在函数要退出时,关闭文件
	defer file.Close()

	// 创建一个 *Reader,是带缓冲的(默认4096字节)
	reader := bufio.NewReader(file)
	// 循环读取文件的内容: reader.ReadString (有2个返回值:文件内容,错误)
	for {
		str,err := reader.ReadString('\n') // 读到"换行"就结束
		// 读到文件结尾时,就break
		if err == io.EOF {
			break
		}
		// 打印读取到的文件内容
		fmt.Print(str)
		// 返回值:hello world
		// 返回值:sudada
		// 返回值:beijing
	}
}

2.读取文件内容并显示在终端(使用ioutil一次将整个文件读入到内存中),这种方式适合小文件:使用ioutil.ReadFile函数

步骤:一次将文件读取到位

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	// 使用ioutil.ReadFile一次性将文件读取到位
	file := "sudada.log"
	content,err := ioutil.ReadFile(file)
	if err != nil {
		fmt.Println(err)
	}
	// 显示读取的内容
	fmt.Printf("%v",string(content))
	// hello world
	// sudada
	// beijing
	// shanghai
}

1.4、常用的4种写文件方式

基本介绍:语法如下

func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

name string:打开的文件

flag int:文件打开的模式

os.O_WRONLY 只写模式

os.O_RDWR 读写模式

os.O_RDONLY 只读模式

os.O_CREATE 如果文件不存在则创建

os.O_EXCL 和os.O_CREATE配合使用,文件必须不存在
os.O_SYNC 打开文件用于同步IO
os.O_TRUNC 如果可能,打开是清空文件

os.O_APPEND 追加内容

perm FileMode:文件的权限控制(linux)

file *File:返回值1,文件对象

err error:返回值2,错误err

快速入门案例:

1.创建一个新文件,写入内容:hello world(模式 os.O_WRONLY | os.O_CREATE

package main

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

func main() {
	// 创建一个新文件,写入内容:hello world
	// 1.文件名"test.log"
	filename := "test.log"
	// 打开文件"test.log"
	file,err :=os.OpenFile(filename, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 2.写入到文件的内容(\r\n表示换行)
	str:="hello world sudada \r\n"
	// 写入文件时,使用带缓存的 *Writer
	writer:=bufio.NewWriter(file)
	writer.WriteString(str)
	// writer带缓存,在调用WriteString方法写入时,需要flush到磁盘。
	writer.Flush()

	// 3.关闭文件句柄
	defer file.Close()
}

2.打开一个存在的文件,覆盖文件的内容(模式 os.O_WRONLY | os.O_TRUNC

package main

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

func main() {
	// 1.文件名"test.log"
	filename := "test.log"
	// 打开文件"test.log"
	file,err :=os.OpenFile(filename, os.O_WRONLY | os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 2.写入到文件的内容(\r\n表示换行)
	str:="hello world sudada \r\n"
	// 写入文件时,使用带缓存的 *Writer
	writer:=bufio.NewWriter(file)
	writer.WriteString(str)
	// writer带缓存,在调用WriteString方法写入时,需要flush到磁盘。
	writer.Flush()

	// 3.关闭文件句柄
	defer file.Close()
}

3.打开一个存在的文件,追加内容(模式 os.O_WRONLY | os.O_APPEND

package main

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

func main() {
	// 1.文件名"test.log"
	filename := "test.log"
	// 打开文件"test.log"
	file,err :=os.OpenFile(filename, os.O_WRONLY | os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 2.写入到文件的内容(\r\n表示换行)
	str:="hello world new \r\n"
	// 写入文件时,使用带缓存的 *Writer
	writer:=bufio.NewWriter(file)
	writer.WriteString(str)
	// writer带缓存,在调用WriteString方法写入时,需要flush到磁盘。
	writer.Flush()

	// 3.关闭文件句柄
	defer file.Close()
}

4.打开一个存在的文件,读取原来的内容,然后在追加内容(模式 os.O_RDWR | os.O_APPEND

package main

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

func main() {
	// 1.文件名"test.log"
	filename := "test.log"
	// 打开文件"test.log"
	file,err :=os.OpenFile(filename, os.O_RDWR | os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 2.先读文件
	reader:=bufio.NewReader(file)
	for{
		str,err := reader.ReadString('\n')
		// 如果读取到文件的末尾,就break
		if err == io.EOF {
			break
		}
		// 显示文件已有的内容
		fmt.Print(str)
	}
	// 3.然后再追加文件内容
	str:="hello shanghai \r\n"
	// 写入文件时,使用带缓存的 *Writer
	writer:=bufio.NewWriter(file)
	writer.WriteString(str)
	// writer带缓存,在调用WriteString方法写入时,需要flush到磁盘。
	writer.Flush()
	// 4.关闭文件句柄
	defer file.Close()
}

5.将文件a复制到文件b(使用ioutil.ReadFile() 和 ioutil.WriteFile()方法)

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	file_test := "test.log"
	file_new_test := "new_test.log"

	// 1.读文件"test.log"的内容
	data, read_err := ioutil.ReadFile(file_test)
	if read_err != nil {
		fmt.Println("read file test.log error: ",read_err)
		return
	}
	// 2.将"test.log"的文件内容写到"new_test.log"
	write_err := ioutil.WriteFile(file_new_test, data, 0666)
	if write_err != nil {
		fmt.Println("writer file new_test.log error: ",write_err)
		return
	}
}

1.5、判断文件或目录是否存在

os.Stat(file_path) 方法的返回值说明:
1.返回的错误为nil,说明文件或文件夹存在
2.返回的错误使用os.IsNotExist()为true,说明文件或文件夹不存在
3.返回的错误为其他类型,则不确定是否存在

package main

import (
	"fmt"
	"os"
)

func main() {
	file_path := "test.log"
	_, err := os.Stat(file_path)
	// os.Stat(file_test) 的用法:
	// 1.返回的错误为nil,说明文件或文件夹存在
	// 2.返回的错误使用os.IsNotExist()为true,说明文件或文件夹不存在
	// 3.返回的错误为其他类型,则不确定是否存在

	// err == nil 时,文件存在
	if err == nil {
		fmt.Printf("file %v exist !", file_path)
	}

	// os.IsNotExist()为true时,文件不存在
	if os.IsNotExist(err) {
		fmt.Printf("file %v is not exist !", file_path)
	}
}

// 封装的一个函数判断文件是否存在
func PathExist(path string) (bool, error) {
	_, err := os.Stat(path)
	// os.Stat(file_test) 的用法
	// 1.返回的错误为nil,说明文件或文件夹存在
	// 2.返回的错误使用os.IsNotExist()为true,说明文件或文件夹不存在
	// 3.返回的错误为其他类型,则不确定是否存在

	// err == nil 时,文件存在
	if err == nil {
		return true, nil
	}
	// os.IsNotExist()为true时,文件不存在
	if os.IsNotExist(err) {
		return false, err
	}
	return false, err
}

func main() {
	file_path := "1test.log"
	flg, msg:=PathExist(file_path)
	if flg {
		fmt.Printf("file %v exist !", file_path)
	} else {
		fmt.Printf("file %v is not exist, msg: %v\n", file_path, msg)
	}
}

1.6、文件拷贝

Copy函数是io包提供的

package main

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

func CopyFile(dstFileName string, srcFileName string)(written int64, err error)  {
	// 获取源文件
	srcFile,err := os.Open(srcFileName)
	if err != nil {
		fmt.Println("open file error: ",err)
	}
	// 关闭文件句柄
	defer srcFile.Close()
	// 通过srcFile,获取到Reader
	reader := bufio.NewReader(srcFile)

	// 打开目标文件
	dstFile, err := os.OpenFile(dstFileName,os.O_WRONLY | os.O_CREATE,0666)
	if err != nil {
		fmt.Println("open file error: ",err)
		return
	}
	writer := bufio.NewWriter(dstFile)
	// 关闭文件句柄
	defer dstFile.Close()
	// 通过io.Copy将源文件copy到目标文件
	return io.Copy(writer, reader)
}

func main() {
	// 将文件funingla.png, copy到fufu.png
	srcFileName := "funingla.png"
	dstFileName := "fufu.png"
	_,err := CopyFile(dstFileName, srcFileName)
	if err == nil {
		fmt.Println("copy success")
	} else {
		fmt.Println("error: ",err)
	}
}

二、命令行参数

2.1、获取命令行参数

方法1:os.Args是一个string切片,用来存储所有的命令行参数。

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("命令行的参数有",len(os.Args))
	// 编译os.Args切片就拿到所有的命令行参数
	for i,v := range os.Args {
		fmt.Printf("args[%v]=%v\n",i,v)
	}
	// 执行命令(空格分隔):go run main.go test aa abc
	// 第1个参数(文件名):main.exe
	// 第2个参数(实际的参数1):test
	// 第3个参数(实际的参数2):aa
	// 第4个参数(实际的参数3):abc
}

方法2:flag包解析命令行参数

package main

import (
	"flag"
	"fmt"
)

func main() {
	var user string
	var port int
	var pwd string
	var host string

	// &user:接收输入的"-u"的值,默认值为"",参数解释:"用户名"
	flag.StringVar(&user,"u","","用户名")
	// &port:接收输入的"-port"的值,默认值为3306,参数解释:"端口,默认为3306"
	flag.IntVar(&port,"port",3306,"端口,默认为3306")
	// &pwd:接收输入的"-p"的值,默认值为"",参数解释:"端口,默认为空"
	flag.StringVar(&pwd,"p","","密码,默认为空")
	// &host:接收输入的"-h"的值,默认值为"localhost",参数解释:"主机地址,默认为localhost"
	flag.StringVar(&host,"h","localhost","主机地址,默认为localhost")

	// 必须转换
	flag.Parse()

	// 输出结果
	fmt.Printf("user=%v,port=%v,pwd=%v,host=%v\n",user,port,pwd,host)

	// 执行命令(必须以空格分隔):go run main.go -u root -p asd!@#%^&( -port 3306 -h 127.0.0.1
	// 返回结果:user=root,port=3306,pwd=asd!@#%&(,host=127.0.0.1
}

三、Json序列化

序列化快速入门:data, err := json.Marshal(monster)

data == 拿到序列化后的json格式数据

monster == 被序列化的格式(结构体,map,切片)

3.1、结构体-序列化

package main

import (
	"encoding/json"
	"fmt"
)

type Monster struct {
	Name string
	Age int
	Birthday string
	Skill string
}

// 结构体Json序列化
func testStruct()  {
	var monster = Monster{
		Name:"sudada",
		Age:18,
		Birthday:"2000-06-11",
		Skill:"牛牛冲击",
	}
	data, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("序列化失败:",err)
	}
	// struct序列化结果
	fmt.Println(string(data))
	// 返回值(key的值被大写了,因为结构体的字段是大写的):{"Name":"sudada","Age":18,"Birthday":"2000-06-11","Skill":"牛牛冲击"}
}

func main() {
	testStruct()
}

结构体json序列化时tag的使用(可以将结构体的字段小写)

package main

import (
	"encoding/json"
	"fmt"
)

type Monster struct {
	name string `json:"name"`  // 反射机制
	Age int `json:"age"`  // 反射机制
	Birthday string `json:"birthday"`  // 反射机制
	Skill string `json:"skill"`  // 反射机制
}

// 结构体Json序列化
func testStruct()  {
	var monster = Monster{
		name: "sudada",
		Age:18,
		Birthday:"2000-06-11",
		Skill:"牛牛冲击",
	}

	// 结构体字段不能小写,因为在json包内无法识别小写的"结构体的字段名"
	// 如果"结构体字段小写"且进行序列化,那么"序列化后的数据"不包含这个"小写字段"的值
	// 这里小写name字段为例,序列化后得到的值:{"age":18,"birthday":"2000-06-11","skill":"牛牛冲击"}
	data, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("序列化失败:",err)
	}
	// struct序列化结果
	fmt.Println(string(data))
	// 返回值(字段小写):{"name":"sudada","age":18,"birthday":"2000-06-11","skill":"牛牛冲击"}
}

func main() {
	testStruct()
}

3.2、map-序列化

package main

import (
	"encoding/json"
	"fmt"
)

// map序列化
func mapTest()  {
	var a map[string]interface{}
	a = make(map[string]interface{})
	a["name"]="sudada"
	a["age"]="18"
	a["skill"]="丢丢"

	// map序列化
	data, err := json.Marshal(a)
	if err != nil {
		fmt.Println("序列化失败:",err)
	}
	fmt.Println(string(data))
	// 返回值:{"age":"18","name":"sudada","skill":"丢丢"}
}

func main() {
	mapTest()
}

3.3、切片-序列化

package main

import (
	"encoding/json"
	"fmt"
)

// 切片序列化
func testSlice()  {
	// 切片内有多个map
	var slice []map[string]interface{}
	var m1 map[string]interface{}
	m1 = make(map[string]interface{})
	m1["name"]="sudada"
	m1["age"]="18"
	m1["skill"]="丢丢"
	slice = append(slice, m1)
	fmt.Println(slice)
	// 返回值:[map[age:18 name:sudada skill:丢丢]]

	// 切片序列化
	data, err := json.Marshal(slice)
	if err != nil {
		fmt.Println("序列化失败:",err)
	}
	fmt.Println(string(data))
	// 返回值:[{"age":"18","name":"sudada","skill":"丢丢"}]
}

func main() {
	testSlice()
}

3.4、反序列化(将json格式的数据,转换为"结构体,map,切片")

在反序列化一个json数据时,要确保反序列化后的数据类型原来序列化前的数据类型一致。

反序列化快速入门:err := json.Unmarshal([]byte(str),&slice)

str == json格式的数据

slice == 被反序列化的格式(结构体,map,切片)

3.4.1、结构体-反序列化

json数据的key,需要和结构体字段保持完全一致

package main

import (
	"encoding/json"
	"fmt"
)

type Monster struct {
	Name string
	Age int
	Birthday string
	Skill string
}

func test()  {
    // 结构体变量
	var monster Monster
	// 将json格式的数据,反序列化为struct
	str := "{\"name\":\"sudada\",\"age\":18,\"birthday\":\"2000-06-11\",\"skill\":\"牛牛冲击\"}"
	
    // 反序列化
	err := json.Unmarshal([]byte(str), &monster)
	if err != nil {
		fmt.Println("反序列化失败",err)
	}
	fmt.Println(monster) // 返回值:{sudada 18 2000-06-11 牛牛冲击}
}

func main() {
	test()
}

3.4.2、map-反序列化

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 将json格式的数据,反序列化为map
	str := "{\"age\":\"18\",\"name\":\"sudada\",\"skill\":\"丢丢\"}"

	// 定义map
	var a map[string]interface{}

	// 反序列化map时,不需要make,因为make操作在Unmarshal函数内已经自动执行了
	err := json.Unmarshal([]byte(str),&a)
	if err != nil {
		fmt.Println("反序列化失败",err)
	}
	fmt.Println(a) // 返回值:map[age:18 name:sudada skill:丢丢]
}

3.4.2、切片-反序列化

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 将json格式的数据,反序列化为切片
	str := "[{\"age\":\"18\",\"name\":\"sudada\",\"skill\":\"丢丢\"}]"

	// 定义切片
	var slice []map[string]interface{}

	// 反序列化切片
	err := json.Unmarshal([]byte(str),&slice)
	if err != nil {
		fmt.Println("反序列化失败",err)
	}
	fmt.Println(slice) // 返回值:[map[age:18 name:sudada skill:丢丢]]
}

四、反射

4.1、什么是反射?

1.反射可以在运行时,动态获取变量的各种信息,比如变量的类型type,类别kind;
2.如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段方法);
3.通过反射,可以修改变量的值,可以调用关联的方法
4.使用反射,需要import("reflect");

4.2、反射重要的函数和概念

1.reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
2.reflect.ValueOf(变量名),获取变量的,返回reflect.Value类型,reflect.Value是一个结构体类型。
3.变量,interface{}和reflect.Value是可以相互转换的
,实际开发会经常用到。

3.1、如何将interface{}转成reflect.Value
rValue
:=reflect.ValueOf(变量名)

3.2、如何将reflect.Value转成interface{}
rVal
:=rValue.Interface()

3.3、如何将interface{}转成原来的变量类型
v:=rVal.(Stu)

4.3、反射的快速入门

4.3.1、基本数据类型,interface{},reflect.Value进行反射的基本操作

package main

import (
	"fmt"
	"reflect"
)

func test(b interface{})  {
	// 通过"反射"获取变量b的reflect.Type 类型
	rType := reflect.TypeOf(b)
	fmt.Println(rType) // 返回值:string
	// 查看通过反射获取到的数据"rType"本身的类型
	fmt.Printf("%T\n",rType) // 返回值:*reflect.rtype

	// 通过"反射"获取变量b的reflect.Value 类型
	rValue := reflect.ValueOf(b)
	fmt.Println(rValue) // 返回值:sudada
	// 查看通过反射获取到的数据"rValue"本身的类型
	fmt.Printf("%T\n",rValue) // 返回值:reflect.Value

	// 通过"反射"获取变量b的reflect.Kind 类别
	rKind:=rType.Kind()  // rValue.Kind() == rType.Kind()
	fmt.Println(rKind)  // 返回值:string
	// 查看通过反射获取到的数据"rKind"本身的类型
	fmt.Printf("%T\n",rKind)  // 返回值:reflect.Kind

	// rValue.String()拿到的是,形参"b"原本的值(把name:="sudada"传入给b时,rValue.String()拿到的就是"sudada"这个值)
	newName:=rValue.String()
	fmt.Printf("%T\n",rValue.String())  // 返回值:string
	fmt.Println(newName) // 返回值:sudada

	// 将rValue转成interface{}
	iv:=rValue.Interface()
	fmt.Printf("%T\n",iv)  // 返回值:string
	fmt.Println("iv",iv)  // 返回值:iv sudada
	// 将interface{}通过断言转成变量原本的类型
	name2:=iv.(string)
	fmt.Printf("%T\n",name2)  // 返回值:string
	fmt.Println("name2",name2)  // 返回值:name2 sudada
}

func main() {
	name := "sudada"
	test(name)
}

4.3.2、结构体类型,interface{},reflect.Value进行反射的基本操作

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age int
}

func test(b interface{})  {
	// 通过"反射"获取变量b的reflect.Type 类型
	rType := reflect.TypeOf(b)
	fmt.Println(rType) // 返回值:main.Student
	// 查看通过反射获取到的数据"rType"本身的类型
	fmt.Printf("%T\n",rType) // 返回值:*reflect.rtype

	// 通过"反射"获取变量b的reflect.Value 类型
	rValue := reflect.ValueOf(b)
	fmt.Println(rValue) // 返回值:{sudada 18}
	// 查看通过反射获取到的数据"rValue"本身的类型
	fmt.Printf("%T\n",rValue) // 返回值:reflect.Value

	// 通过"反射"获取变量b的reflect.Kind 类别
	rKind:=rType.Kind()  // rValue.Kind() == rType.Kind()
	fmt.Println(rKind)  // 返回值:struct
	// 查看通过反射获取到的数据"rKind"本身的类型
	fmt.Printf("%T\n",rKind)  // 返回值:reflect.Kind

	// 将rValue转成interface{}
	iv:=rValue.Interface()
	fmt.Printf("%T\n",iv)  // 返回值:main.Student
	fmt.Println(iv)  // 返回值:{sudada 18}  // 这里不能通过iv.Age,iv.Name取到值
	// 将interface{}通过断言转成变量原本的类型
	stu:=iv.(Student)
	fmt.Printf("%T\n",stu)  // 返回值:main.Student
	fmt.Println(stu.Age)  // 返回值:18
	fmt.Println(stu.Name)  // 返回值:sudada
}

func main() {
	stu := Student{
		Name: "sudada",
		Age: 18,
	}
	test(stu)
}

4.4、反射的注意事项

1.reflect.Value.Kind,获取变量的类型,获取到的是一个常量;
2.Type是类型,Kind是类别,Type和Kind可能是相同的,也可能是不同的;
3.通过反射,可以让变量在interface{}和reflect.Value之间相互转换(变量<-->interface{}<-->reflect.Value)

4.使用反射的方式获取变量的值(并返回对应的类型),要求数据类型匹配比如xxx的类型是string,那么就应该使用reflect.ValueOf(xxx).String(),使用其他类型时会报错

package main

import (
	"fmt"
	"reflect"
)

func test(b interface{})  {
	// 通过"反射"获取变量b的reflect.Value 类型,对应的值
	rValue := reflect.ValueOf(b).String()
	fmt.Println(rValue)
}

func main() {
	name := "sudada"
	test(name)
}

5.通过反射修改变量的值

package main

import (
	"fmt"
	"reflect"
)

func test(b interface{})  {
	// 通过"反射"获取变量b的reflect.Value 类型
	rValue := reflect.ValueOf(b)
	// 通过"反射"修改"变量"的值。(先获取到指针对应的值,然后修改)
	rValue.Elem().SetString("sss")
}

func main() {
	name := "sudada"
	// 传入变量的指针
	test(&name)
	fmt.Println("main", name)  // 返回值:main sss
}

4.5、反射的最佳实践

4.5.1、使用反射,(1)来"遍历"结构体的字段,(2)"调用"结构体的方法,(3)"获取"结构体标签的值;
Method方法:
获取结构体的方法,传入'索引值'[0 1 2](这里取值0 1 2并不是按照"方法的定义顺序",而是"方法名称的排序顺序",也就是ASCII码);
Call方法:获取到方法后,调用该方法;

package main

import (
	"fmt"
	"reflect"
)

type Monster struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Score float64
	Sex string
}
// 结构体方法Print
func (this Monster)Print()  {
	fmt.Println("--start--")
	fmt.Println(this)
	fmt.Println("--stop--")
}
// 结构体方法GetSum(求和)
func (this Monster)GetSum(num1,num2 int) int {
	return num1 + num2
}
// 结构体赋值
func (this Monster)Set(name string,age int,score float64,sex string)  {
	this.Name = name
	this.Age = age
	this.Score = score
	this.Sex = sex
}
//
func test(b interface{})  {
	// 通过"反射"获取reflect.Type 类型
	vType:=reflect.TypeOf(b)
	fmt.Println(vType)
	// 通过"反射"获取reflect.Value 类型
	vValue:=reflect.ValueOf(b)
	// 通过"反射"获取reflect.Kind 类别
	vKind:=vValue.Kind()
	// 如果"类别"不为"结构体",那么就报错退出
	if vKind != reflect.Struct{
		fmt.Println("b is not struct")
		return
	}

	// 获取到"结构体Monster"的字段数量
	num:=vValue.NumField()
	fmt.Println(num) // 返回值:4

	// 遍历结构体的所有字段的值(传入的值是几个就是几个)
	for i:=0; i<num; i++{
		fmt.Println(vValue.Field(i))  // 返回值:niuniu 500 30.8
		// 获取到所有结构体标签,需要通过reflect.Type来获取tag标签的值(使用reflect.Value获取不到)
		tagVal:=vType.Field(i).Tag.Get("json")
		// 如果该字段有tag标签就显示名称,否则不显示
		if tagVal != "" {
			fmt.Println(tagVal)  // 返回值:name age (对应的就是Monster内定义的标签名称)
		}
	}

	// 获取该结构体的所有方法
	numOfMethod:=vValue.NumMethod()
	fmt.Println("结构体的方法数量为:",numOfMethod) // 返回值:3

	// 调用结构体的方法,不传参(第2个方法Print),这里取值(0 1 2)并不是按照"方法的定义顺序",而是"方法名称的排序顺序",也就是ASCII码
	vValue.Method(1).Call(nil)
	// 返回值:
	// --start--
	// {niuniu 500 30.8 }
	// --stop--

	// 调用结构体的方法,传参(第1个方法GetSum)
	// 定义一个切片,并传入2个值
	var params []reflect.Value
	params=append(params,reflect.ValueOf(10))
	params=append(params,reflect.ValueOf(20))
	// 把值传入给方法
	res:=vValue.Method(0).Call(params)  // 这里的返回值,还是一个[]reflect.Value
	fmt.Println(res[0]) // 返回值:30
}

func main() {
	mon:=Monster{
		Name: "niuniu",
		Age: 500,
		Score: 30.8,
	}
	test(mon)
}

五、go连接到redis

5.1、redis包的选择(redigo为例)

golang操作redis主要有两个库,go-redis(github.com/redis/go-redis)和redigo(github.com/gomodule/redigo)。
两者操作都比较简单,区别上redigo更像一个client执行各种操作都是通过Do函数去做的,redis-go对函数的封装更好;

5.2、redis操作string类型的key:value(使用redigo包为例)

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
)

func main() {

	// 1、连接到redis
	conn, err := redis.Dial("tcp","127.0.0.1:6379")
	if err != nil {
		fmt.Println("connect redis failed",err)
	}
	// 4、最后关闭redis
	defer conn.Close()

	// 2、往redis写数据 string key:value
	_, err = conn.Do("set","name","sudada")
	if err!=nil{
		fmt.Println("set key error")
		return
	}

	// 3、获取redis的值 string key:value
	values, err := redis.String(conn.Do("get","name"))
	if err!=nil{
		fmt.Println("get key error")
		return
	}
	fmt.Println("get key success, keys is: ",values) // 返回值:sudada
}

5.3、redis操作Hash类型的key:value(使用redigo包为例)

5.3.1、一次set一个数据

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
)

func main() {

	// 1、连接到redis
	conn, err := redis.Dial("tcp","127.0.0.1:6379")
	if err != nil {
		fmt.Println("connect redis failed",err)
	}
	// 4、最后关闭redis
	defer conn.Close()

	// 2、往redis写数据 hash key:value
	_, err = conn.Do("hset","user01","name","jake")  // "user01"是hash值
	if err!=nil{
		fmt.Println("hset key error")
		return
	}
	_, err = conn.Do("hset","user01","age",18)  // "user01"是hash值
	if err!=nil{
		fmt.Println("hset key error")
		return
	}

	// 3、获取redis的值 hash key:value
	values1, err1 := redis.String(conn.Do("hget","user01","name"))  // "user01"是hash值
	if err1!=nil{
		fmt.Println("hget key error")
		return
	}
	fmt.Println("hget key success, keys is: ",values1) // 返回值:jake
	values2, err2 := redis.Int(conn.Do("hget","user01","age"))  // "user01"是hash值
	if err2!=nil{
		fmt.Println("hget key error")
		return
	}
	fmt.Println("hget key success, keys is: ",values2) // 返回值:18
}

5.3.2、一次set多个数据

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
)

func main() {

	// 1、连接到redis
	conn, err := redis.Dial("tcp","127.0.0.1:6379")
	if err != nil {
		fmt.Println("connect redis failed",err)
	}
	// 4、最后关闭redis
	defer conn.Close()

	// 2、往redis写数据 hash key:value
	_, err = conn.Do("hmset","user02","name","jake","age",19)  // "user01"是hash值
	if err!=nil{
		fmt.Println("hmset key error")
		return
	}

	// 3、获取redis的值 hash key:value
	values1, err1 := redis.Strings(conn.Do("hmget","user02","name","age"))  // "user01"是hash值
	if err1!=nil{
		fmt.Println("hmget key error")
		return
	}
	fmt.Println("hget key success, keys is: ",values1) // 返回值:[jake 19]
	for _,v := range values1{
		fmt.Println(v)  // 返回值:jake 19
	}
}

5.4、给key设置缓存时间(使用redigo包为例)

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
)

func main() {
	// 1、连接到redis
	conn, err := redis.Dial("tcp","127.0.0.1:6379")
	if err != nil {
		fmt.Println("connect redis failed",err)
	}
	// 4、最后关闭redis
	defer conn.Close()

	// 2、给redis的key设置缓存时间(秒)
	_, err = conn.Do("expire","name", 10)
	if err!=nil{
		fmt.Println("expire time error")
		return
	}
}

5.5、go-redis包的使用

package main

import (
	"fmt"
	"github.com/go-redis/redis"
	"time"
)

func main() {
	// 1、连接redis
	conn := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // 密码
		DB:       0,  // 数据库
		PoolSize: 20, // 连接池大小
	})
	// 4、关闭redis
	defer conn.Close()

	// 2、设置一个key:value以及过期时间
	res1 := conn.Set("name","sudada",10*time.Second)
	fmt.Println(res1)  // set name sudada ex 10: OK

	// 3、获取key对应的value
	res, err :=conn.Get("name").Result()
	if err!=nil {
		fmt.Println("get key error",err)
		return
	}
	fmt.Println(res) // 返回值:sudada
}

5.6、redis连接池

1.实现初始化一定数量的连接,放到连接池;
2.当go需要操作redis时,直接从redis连接池取出连接即可;
3.节省临时获取redis连接的事件,提高效率;

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
)

var pool *redis.Pool

// 程序启动时,就初始化连接处(这里使用"init函数(主函数执行之前,就已经执行了init函数)"实现)
func init()  {
	pool = &redis.Pool{
		MaxIdle: 10,  // 最大空闲连接数
		MaxActive: 0,  // 和数据库的最大连接数,0表示没有限制
		IdleTimeout: 100,  // 最大空闲时间
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp","127.0.0.1:6379")
		},
	}
}

func main() {
	// 使用连接池
	// 1、先从pool池,取出一个连接
	coon := pool.Get()
	// 从pool池,取出一个连接,使用完毕后,关闭该连接
	defer coon.Close()
	// 2、set key
	_,err := coon.Do("set","name", "sudada")
	if err != nil {
		fmt.Println("set key error: ",err)
	}
	// 3、get key
	values,err2 := redis.String(coon.Do("get","name"))
	if err2 != nil {
		fmt.Println("set key error: ",err2)
	}
	fmt.Println(values)  // 返回值:sudada

	// 关闭连接池(关闭后,redis连接池就不能使用了)
	pool.Close()
}

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值