GO 从 0 到 1 系列:13 反射

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{}) errorDecode从输入中读取下一个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): 通过属性名获取属性

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇