2 反射
反射是指在运行时动态的访问和修改任意类型对象的结构和成员,在 go 语言中提供 reflect 包提供反射的功能,每一个变量都有两个属性:类型(Type)和值(Value)
1.相关功能
reflect
2.反射技术应用
3.相关功能底层技术使用了反射技术
2.1 json
json 其实就是格式化的字符串。
我们在做文件操作的时候呢,不可避免的要去讨论 json
概述:
JSON(Java Script Object Notation) java 对象的一个标记法,是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。
最经典的用法就是 key-val
JSON 在 2001 年开始推广使用的数据格式,目前已经成为主流的数据格式。
JSON 易于机器解析和生成,并有效地提升网络传输效率 , 通常程序在网络传输时会先将数据(结构体、map等)序列化成 json 字符串,到接收方得到 json 字符串时,在反序列化恢复成原来的数据类型(结构体、map等)。这种方式已然成为各个语言的标准。
json 的数据类型
json 是跨语言的
基本数据类型
数值类型: 1,2,1.1
字符串类型: "xxxxx"
布尔类型: true , fals
列表:列表类型其实就是一个中括号里面表示的
["x","y","z"]
映射:其实就是一个大括号,就是 key(一般都是字符串) / value(任意类型)
{
"key" : value
}
介绍
在内存数据进行持久化存储或网络交换时常可采用 json 格式字符串,go 语言提供 json包进行 json 序列化和反序列化
对于 Go 提供的基本类型和符合类型可以直接使用 json 包进行序列化和反序列化操作,针对结构体可通过标签声明属性和 json 字符串的转换关系,标签名为 json,常用格式为:
-
json:
默认形式,可省略,json 键名使用属性名,类型通过属性对应的类型
-
json:"key"
json 键名使用指定名称 key
-
json:"-"
忽略该属性
-
json:"key,type"
json 键名使用指定名称 key,类型使用指定类型 type
-
json:"key,omitempty"
json 键名使用指定名称 key,当值为零值时省略该属性
-
json:"key,type,omitempty"
json 键名使用指定名称 key,类型使用指定类型 type,当值为零值时省略该属性
-
json:",type"
类型使用指定类型 type
-
json:",omitempty"
值为零值时省略该属性
2.2 json 应用场景
web 编程中的应用:
左边有一个 web 服务器是通过 go 语言写的,然后下面有一个浏览器,
比如我们要在 web 服务器上给浏览器返回一个数组,首先需要给他进行 json 序列化,序列化之后就得到了一个 json 字符串。
这个时候我们将 json 字符串返回给浏览器(传输)
浏览器接收到 json 字符串过后呢,一般会进行反序列化,也就是说我们的浏览器接收到了 json 字符串。
我们往往还需要将 json 字符串反序列成一个数组或者其他的数据。
反序列化目的:又还原成数组或者其他数据类型
通过这个数组在进行其他的 dom 编程显式操作
tcp 编程中的应用:
这块用 go 语言写了一个聊天系统,因为是聊天系统数据要从 A 客户端发送到 B 客户端。
这个时候也可以通过 json 来进行数据传输
如图,这里有客户端 A 和 客户端 B,A 和 B 通讯往往需要进行中转服务器,这时候这个后台中转服务器是通过 go 语言写的。这个时候我们假设要把客户端 A 的信息发送给 B ,也会通常选用 json 的格式来进行处理。
比如客户端 A 中有一个数组,我们先将其通过序列化转换为 json 字符串,一旦拿到这个字符串过后呢,我们就将这个字符串传给 go 后台服务器。
这个 go 后台服务器拿到 json 字符串以后呢,在原封不动的把这个 json 字符串在传回给 B 客户端。
B 客户端拿到数据之后,其实 B 客户端拿到的是一个 json 字符串,往往会将其进行反序列化。
此时 B 客户端有会得到一个对应的数组,然后在显示
好处:
这样的好处是,大家如果都遵守 json 这种数据格式的话,我们的程序就相对比较好控制了
也就是说大家都遵守 json 格式,我们在转换的时候都比较方便
2.3 json 数据格式说明
在Js语言中,一切都是对象。因此,任何数据类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组、map、结构体 等.
JSON 键值对是用来保存数据一种方式,也就是说在描述一个信息的时候总是通过 键值对来描述
键:我们一般称为属性
值:就是具体对应的数据
键/值对组合中的键名写在前面并用双引号 ""
包裹,使用冒号∶
分隔,然后紧接着值 :
{"key1":val1,"key2":val2,"key3":val3,"key4":[val4,val5]}
比如:
{"firstName":"Json"}
# firstName 这个就是键
# json 就是值
{"name":"tom","age":18,"address":["北京","上海"]}
# name 就是键
# tom 为值
# age 键
# 18 值
# address 键
# 北京 上海 为值
[{"name":"tom","age":18,"address":["北京","上海"]},
{"name":"mary","age":28,"address":["广州","重庆"]}]
# 多个属性信息,可以用 [] 包起来,然后再里面单个信息用 {} 分开
好处:
json 的扩展性特别好,而且特别灵活,因为这个键值对可以无限的增加
2.4 json 数据在线解析
http://www.json.cn/ 这个网站可以验证一个 json 格式的数据是否正确。尤其是在我们编写比较复杂的 json 格式数据的时候,非常有用。
也就是说,我们在写一个 json 格式的时候可以通过这个网站来看数据是否正确
在下面这个图中,我们编写多了 json 数据,并且右边为我们的解析之后的信息
2.5 json 编码示例
介绍:
json序列化是指,将有 key-value 结构的数据类型(比如结构体、map、切片)序列化成json字符串的操作。
应用案例:
这里我们介绍一下结构体、map和切片的序列化,其它数据类型的序列化类似。
需要引入 encoding/json
包
func json.Marshal(v interface{}) ([]byte, error)
func json.MarshalIndent(v interface{}, prefix string, indent string) ([]byte, error)
在 json 中没有结构体字段,所以在转换的时候只能够转为映射结构
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Id int
Name string
Password string
age int // 首字母小写,所以 json.Marshal 包不能访问当转换的时候就没有该字段
Tel string
Addr string
}
func main() {
user := User{1, "zz", "123", 24, "177xxxx", "重庆"}
// 内存结构 => []byte (编码)
// encod/json
// Marshal()
// 转换之后为映射 {} key = 属性名(字符串类型) value = 属性值
// 在生产环境一般使用 json.Marshal() 压缩的 json 格式进行传输,这样比较节省网络资源
if jsonUser, err := json.Marshal(user); err == nil {
fmt.Printf("%v\n\n", string(jsonUser))
}
// 这是直接转成了 json 格式,一般用于调试时候使用,生产不推荐
if jsonUser, err := json.MarshalIndent(user, "", "\t"); err == nil {
fmt.Println(string(jsonUser))
}
}
执行
[19:41:21 root@go codes]#go run json.go
{"Id":1,"Name":"zz","Password":"123","Tel":"177xxxx","Addr":"重庆"}
{
"Id": 1,
"Name": "zz",
"Password": "123",
"Tel": "177xxxx",
"Addr": "重庆"
}
# 输出我们会发现没有 age 字段以及属性对应的内容
2.6 json 解码示例
我们拿到了编码的字符串数据之后,如何将其转换为内存结构体,我们需要使用到 json.Unmarshal()
func json.Unmarshal(data []byte, v interface{}) error
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Id int
Name string
Password string
age int // 首字母小写,所以 json.Marshal 包不能访问当转换的时候就没有该字段
Tel string
Addr string
}
func main() {
// []byte => 内存结构(解码)
jsonUser := `{"Id":1,"Name":"zz","Password":"123","Tel":"177xxxx","Addr":"重庆"}`
var user2 User
// 将拿到编码后的数据是一个 string 类型需要转为 []byte
// 第二个参数解码以后需要存入到一个内存结构体中,所以需要放一个同样类型的地址进来
if err := json.Unmarshal([]byte(jsonUser), &user2); err != nil {
fmt.Println("json err = ", err)
} else {
fmt.Printf("%#v\n", user2) // 全格式输出
}
}
执行
[20:01:29 root@go codes]#go run json.go
main.User{Id:1, Name:"zz", Password:"123", age:0, Tel:"177xxxx", Addr:"重庆"}
# 由于我们在转换的过程中没有 age 的值,所以输出是 0
2.7 结构体属性 tag
tag 其实就是对属性的说明,在结构体里面的各个属性打上 tag 标签,让他们输出的时候是小写。
这样的好处是,以后再序列化之后,返回给客户端的时候 浏览器可以用 key 是小写的方式来进行反序列
每一个 tag 一般有标签名和标签值,标签名一般是用来定义给那种格式使用的,标签值一般需要使用 ""
引起来
package main
import (
"encoding/json"
"fmt"
)
type User struct {
// json 格式使用,标签名为 json 值为对应的小写字母
Id int `json:"id"`
Name string `json:"name"`
Password string `json:"password"`
Age int `json:"age"`
Addr string `json:"addr"`
}
func main() {
// []byte => 内存结构(编码)
fmt.Println("编码:")
user1 := User{1, "gg", "12345", 123, "北京"}
jsonUser, err := json.Marshal(user1)
if err == nil {
fmt.Println(string(jsonUser))
}
// 解码
fmt.Println("解码:")
var user2 User
// 拿到上面编码后的 jsonUser 变量进行解码操作
if err := json.Unmarshal(jsonUser, &user2); err == nil {
fmt.Println(user2)
} else {
fmt.Println("json err", err)
}
}
执行
[20:18:31 root@go codes]#go run json.go
编码:
{"id":1,"name":"gg","password":"12345","age":123,"addr":"北京"}
解码:
{1 gg 12345 123 北京}
# 通过输出可以发现编码后就是对应的小写字母
当然我们在网络传输的时候不想把我们的 password 密码进行传输,可以在打 tag 的时候使用 -
忽略这个值
package main
import (
"encoding/json"
"fmt"
)
type P struct {
// 打 tag 使用 -
Password string `json:"-"`
}
func main() {
p := P{"123"}
// 解码
if p, err := json.Marshal(p); err == nil {
fmt.Println(string(p))
}
}
执行
[20:26:32 root@go codes]#go run json2.go
{}
# 通过输出我们会发现并没有将 password 的值进行输出
2.8 json 解码到文件(持久化)
在 json 中也可以把将数据序列化到文件中
func json.NewEncoder(w io.Writer) *json.Encoder
:NewEncoder 返回写入 w 的新编码器。
func (*json.Encoder).Encode(v interface{}) error
:Encode将v的json
编码写入流,后跟换行符。
package main
import (
"encoding/json"
"fmt"
"os"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
}
func main() {
// 创建一个 user 结构体切片
user := []User{
{1, "zz", 21, "重庆"},
{2, "gg", 23, "重庆"},
{3, "yy", 24, "重庆"},
}
// 创建一个文件
filePath := "user.json"
file, err := os.Create(filePath)
if err != nil {
fmt.Println(err)
}
defer file.Close()
// 创建一个 json 编码器,存放位置是 file 变量
encoder := json.NewEncoder(file)
// 将 user 进行 json 编码后写入
encoder.Encode(user)
}
执行
[20:26:46 root@go codes]#go run jsonfile.go
# 已经写入到文件中
[20:53:25 root@go codes]#cat user.json
[{"id":1,"name":"zz","age":21,"addr":"重庆"},{"id":2,"name":"gg","age":23,"addr":"重庆"},{"id":3,"name":"yy","age":24,"addr":"重庆"}]
2.9 对 json 文件进行解码操作
func json.NewDecoder(r io.Reader) *json.Decoder
:NewDecoder
返回从 r 读取的新解码器。
func (*json.Decoder).Decode(v interface{}) error
:Decode
从输入中读取下一个JSON编码的值,并将其存储在v所指向的值中。
package main
import (
"encoding/json"
"fmt"
"os"
)
// 定义一个和需要解码的结构体一样的数据类型
type User struct {
Id int
Name string
Age int
Addr string
}
func main() {
// 创建一个 user 结构体切片,因为我们解码的对象是一个 user 结构体切片
user := []User{}
// 打开 json 文件
file, err := os.Open("user.json")
if err != nil {
fmt.Println(err)
}
defer file.Close()
// 创建一个 json 解码器
decode := json.NewDecoder(file)
if err := decode.Decode(&user); err != nil {
fmt.Println(err)
}
fmt.Println(user)
}
执行
[20:56:39 root@go codes]#go run jsonfile.go
[{1 zz 21 重庆} {2 gg 23 重庆} {3 yy 24 重庆}]
2.10 反射原理
反射是在运行时的,他的性能比较差
反射是指在运行时动态的访问和修改任意类型对象的结构和成员,在 go 语言中提供 reflect
包提供反射的功能,每一个变量都有两个属性:类型(Type)
和值 (Value)
2.10.1 Type
reflect.Type
是一个接口类型,用于获取变量类型的信息,可通过 reflect.TypeOf
函数获取某个变量的类型信息
2.10.1.1 通用方法
-
Name(): 类型名
-
PkgPath(): 包路径
-
Kind(): 类型枚举值
-
String(): Type 字符串
-
Comparable(): 是否可进行比较
-
Implements(Type): 是否实现某接口
-
AssignableTo(Type): 是否可赋值给某类型
-
ConvertibleTo(Type): 是否可转换为某类型
-
NumMethod(): 方法个数
-
Method(int): 通过索引获取方法类型
Method 结构体常用属性:
- Name:方法名
- Type:函数类型
- Func:方法值(Value)
-
MethodByName(string): 通过方法名字获取方法类型
2.10.1.2 特定类型方法
-
reflect.Int, reflect.UInt,reflect.Float,reflect. Complex
-
Bits(): 获取占用字节位数
-
reflact.Array
- Len(): 获取数组长度
- Elem(): 获取数据元素类型
-
reflect.Slice
- Elem(): 获取切片元素类型
- reflect.Map
- Key(): 获取映射键类型
- Elem(): 获取映射值类型
-
reflect.Ptr
-
Elem(): 获取指向值类型
-
reflect.Func
- IsVariadic(): 是否具有可变参数
- NumIn(): 参数个数
- In(int): 通过索引获取参数类型
- NumOut: 返回值个数
- Out(int): 通过索引获取返回值类型
-
reflect.Struct
-
NumField: 属性个数
-
Field(int):通过索引获取属性
-
StructField 结构体常用属性:
-
Name:属性名
-
Anonymous:是否为匿名
-
Tag:标签
StructTag 常用方法:
➢ Get(string)
➢ Lookup(string)
-
-
FieldByName(string): 通过属性名获取属性
2.10.2 Value
reflect.Value
是一个结构体类型,用于获取变量值的信息,可通过 reflect.ValueOf
函数获取某个变量的值信息
2.10.2.1 通用方法
- Type(): 获取值类型
- CanAddr():是否可获取地址
- Addr(): 获取地址
- CanInterface(): 是否可以获取接口的
- InterfaceData():
- Interface(): 将变量转换为 interface{}
- CanSet(): 是否可更新
- isValid(): 是否初始化为零值
- Kind():获取值类型枚举值
- NumMethod(): 方法个数
- Method(int): 通过索引获取方法值
- MethodByName(string): 通过方法名字获取方法值
- Convert(Type):转换为对应类型的值
2.10.2.2 修改方法
- Set/Set*:设置变量值
2.10.2.3 调用方法
- Call
- CallSlice
2.10.2.3 特定类型方法
-
reflect.Int, reflect.Uint,
- Int(): 获取对应类型值
- Unit():获取对应类型值
-
reflect.Float*
- Float():获取对应类型值
-
reflect. Complex*
- Complex():获取对应类型值
-
reflact.Array
- Len(): 获取数组长度
- Index(int): 根据索引获取元素
- Slice(int, int): 获取切片
- Slice3(int, int, int): 获取切片
-
reflect.Slice
- IsNil(): 判断是否为
- Len(): 获取元素数量
- Cap(): 获取容量
- Index(int): 根据索引获取元素
- Slice(int, int): 获取切片
- Slice3(int, int, int): 获取切片
-
reflect.Map
- IsNil(): 判断是否为
- Len(): 获取元素数量
- MapKeys(): 获取所有键
- MapIndex(Value): 根据键获取值
- MapRange():获取键值组成的可迭代对象
-
reflect.Ptr
- Elem(): 获取指向值类型(解引用)
-
reflect.Func
- IsVariadic(): 是否具有可变参数
- NumIn(): 参数个数
- In(int): 通过索引获取参数类型
- NumOut: 返回值个数
- Out(int): 通过索引获取返回值类型
-
reflect.Struct
-
NumField: 属性个数
-
Field(int):通过索引获取属性
-
StructField 结构体常用属性:
-
Name:属性名
-
Anonymous:是否为匿名
-
Tag:标签
-
StructTag 常用方法:
➢ Get(string)
➢ Lookup(string)
-
-
FieldByName(string): 通过属性名获取属性