Go语言学习篇07
反射
- 序列化和反序列化使用tag标签,利用的是反射机制
- 使用反射机制,编制适配器,桥连接
反射的基本介绍
1)反射可以在运行时动态的获取变量的各种信息,比如变量的类型、类别
2)如果是结构体变量,还可以获取到结构体本身的信息(比如:结构体字段、方法)
3)通过反射,可以修改变量的值,可以调用关联的方法
4)使用反射,需要import “reflect”
package reflect
import "reflect"
reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。
reflect示意图
reflect的相关函数和转换
1)reflect.TypeOf(变量名), 获取变量的类型,返回reflect.Type类型
2)reflect.ValueOf(变量名),获取变量值,返回reflect.Value类型(结构体)
案例
- 变量、空接口、reflect.value之间的相互转换
reflect快速入门案例
基本数据类型、interface、reflect.Value的相互转化
基本数据类型的转化
package main
import (
"fmt"
"reflect"
)
// 基本数据类型、interface{}、 reflect.Value的相互转换
func test01(x interface{}) {
// 通过反射获取 type、kind、value
// 1、先获取到 reflect.Type
rType := reflect.TypeOf(x)
fmt.Println("reflectType:", rType)
// 获取到 reflect.value
rValue := reflect.ValueOf(x)
fmt.Println("reflectValue:", rValue)
fmt.Println("reflectValue type is:", reflect.TypeOf(rValue))
// 获取真正的值
n := 99 + rValue.Int()
fmt.Println("n:", n)
// 下面将 reflect.Value 转成 interface{}
iV := rValue.Interface()
fmt.Println("interface{}类型iV:", iV)
// 将 interface{} 转化成 int基本类型
a := iV.(int)
fmt.Println("int数据类型a:", a)
}
func main() {
var num int = 100
test01(num)
}
结果
reflectType: int
reflectValue: 100
reflectValue type is: reflect.Value
n: 199
interface{}类型iV: 100
int数据类型a: 100
结构体数据类型
代码
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func (this *Student) Study() {
fmt.Printf("%v学习中...\n", this.Name)
}
func ReflectStruct(i interface{}) {
rType := reflect.TypeOf(i)
fmt.Println("rType:", rType)
rValue := reflect.ValueOf(i)
fmt.Println("rValue:", rValue)
kind01 := rValue.Kind()
kind02 := rType.Kind()
fmt.Printf("类别kind01:%v,kind02:%v\n", kind01, kind02)
iV := rValue.Interface()
fmt.Printf("Interface方法后是%T类型iV:%v\n", iV, iV)
stu, ok := iV.(Student)
if ok {
fmt.Println("Student结构体类型stu:", stu)
}
}
func main() {
var student Student = Student{
Name: "Carter",
Age: 18,
}
ReflectStruct(student)
}
结果
rType: main.Student
rValue: {Carter 18}
类别kind01:struct,kind02:struct
Interface方法后是main.Student类型iV:{Carter 18}
Student结构体类型stu: {Carter 18}
反射的注意事项
1)reflect.Value.Kind,获取变量的类别,返回的是一个常量
2)Type是类型,Kind是类别,Type和Kind可能是相同的,也可能是不同的
比如:var num int = 10 num的type是int kind也是int
比如:var stu Student stu的 type是 包名.Student
,kind是struct
3)使用反射的方式获取变量的值(并返回对应的类型),要求数据类型匹配,
比如 var x int = 10 那么应该使用reflect.ValueOf(x).Int()
,而不能使用其他的,否则会报panic
reflect 修改变量
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
代码
package main
import (
"fmt"
"reflect"
)
func reflectValue(i interface{}) {
rValue := reflect.ValueOf(i)
fmt.Printf("rValue kind is %v\n", rValue.Kind())
// 注意使用Elem()方法
rValue.Elem().SetInt(64)
}
func main() {
var num int = 20
reflectValue(&num)
fmt.Println("num:", num)
}
结果
rValue kind is ptr
num: 64
reflect 基础练习题
1)使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
基础练习
package main
import (
"fmt"
"reflect"
)
func reflectValue(i interface{}) {
rValue := reflect.ValueOf(i)
fmt.Printf("rValue kind is %v\n", rValue.Kind())
fmt.Printf("rValue type is %v\n", reflect.TypeOf(i))
rValInterface := rValue.Interface()
fmt.Println("rValInterface:", rValInterface)
value, ok := rValInterface.(float64)
if ok {
fmt.Printf("value=%v\n", value)
}
}
func main() {
var num float64 = 20.1
reflectValue(num)
var str string = "tom"
// 必须传入地址
rValue := reflect.ValueOf(&str)
rValue.Elem().SetString("java")
fmt.Println(str)
}
结果
rValue kind is float64
rValue type is float64
rValInterface: 20.1
value=20.1
java
升级练习题
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Gender string
}
func (stu Student) Study() {
fmt.Println("学习中...")
}
func (stu Student) GetSum(x int, y int) int {
return x + y
}
func (stu Student) GetSub(x int, y int) int {
return x - y
}
func (stu Student) getSub(x int, y int) int {
return x - y
}
func (stu *Student) String() string {
return fmt.Sprintf("name: %v sex: %v gender: %v", stu.Name, stu.Age, stu.Gender)
}
func reflectStu(stu interface{}) {
rType := reflect.TypeOf(stu)
fmt.Println("rType:", rType)
rValue := reflect.ValueOf(stu)
fmt.Println("rValue:", rValue)
// 获取类别
kd := rValue.Kind()
if kd != reflect.Struct {
fmt.Println("参数不是结构体!")
return
}
//获取字段数目 【注意:字段首字母小写也可以获取】
nums := rType.NumField()
fmt.Println("nums:", nums)
numFields := rValue.NumField()
fmt.Println("字段个数:", numFields)
// 获取字段对应的值和tag标签
for i := 0; i < nums; i++ {
tagVal := rType.Field(i).Tag.Get("json")
if tagVal != "" {
fmt.Print(tagVal,":")
}
fmt.Println(rValue.Field(i))
}
// 获取方法数目【注意:方法首字母必须是大写的才能获取】
numMethods := rValue.NumMethod()
fmt.Println("方法个数:", numMethods)
numMethods1 := rType.NumMethod()
fmt.Println("方法个数:", numMethods1)
// 通过反射执行方法
rValue.MethodByName("Study").Call(nil)
fmt.Println("获取所有的方法名...")
var i int = 0
fmt.Println(rType.Method(i).Index)
fmt.Println(rType.Method(i).Name)
fmt.Println(rType.Method(i).Type)
fmt.Println()
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(20))
//返回一个reflect.Value切片
sum := rValue.Method(1).Call(params)
for _, value := range sum {
sum , ok := value.Interface().(int)
if !ok {
break
}
fmt.Println("sum:", sum)
}
}
func main() {
var stu Student = Student{
Name : "Carter",
Age : 18,
Gender : "男",
}
reflectStu(stu)
var stu2 *Student = &Student{
Name:"lsx",
Age:18,
Gender:"男",
}
fmt.Printf("修改前: %v\n", stu2)
rValue := reflect.ValueOf(stu2)
rValue = rValue.Elem()
rValue.FieldByName("Name").SetString("廖述幸")
fmt.Printf("修改后: %v\n", stu2)
}
结果
rType: main.Student
rValue: {Carter 18 男}
nums: 3
字段个数: 3
name:Carter
age:18
男
方法个数: 3
方法个数: 3
学习中...
获取所有的方法名...
0
GetSub
func(main.Student, int, int) int
sum: 30
修改前: name: lsx sex: 18 gender: 男
修改后: name: 廖述幸 sex: 18 gender: 男
常量
常量的介绍
- 常量使用const修改
- 常量在定义的时候必须初始化
- 常量不能修改
- 常量还能用bool、数值类型(int、float系列)、string类型
- 语法:
const identifier [type] = value
- 举例说明,看看下面的写法是否正确【右侧的值是否固定】
const name = “tom”
const tax float64 = 0.8
const a int error
const b = 9/3
const c = getValue() error
常量的注意事项
1)Go语言中,常量可以不大写
2)Go语言中,常量仍有访问范围机制
面试题 1
package main
import "fmt"
const (
a = iota //默认从0开始,后面的如果都没赋值,就在0的基础上+1
b
c
d = 99
e
f
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
fmt.Println(e)
fmt.Println(f)
}
结果
0
1
2
99
99
99
面试题 2
代码
package main
import "fmt"
const (
a = iota
b = iota
c, d = iota, iota
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
结果
0
1
2
2 //没有改变
TCP 编程
TCP 基本介绍
- 应用案例:
- CS结构
- QQ、微信
- A->Server->B
- B/S【浏览器】
TCP socket
基于TCP/IP,如:QQ
B/S http
web编程
有三本书:网络底层的书本,硬件必学
TCP/IP QQ通信原理图
Bash 命令追踪路由包
trace [追踪]
rt [router简写]
- 请求超时不是没通过,而是它不给你返回通过的包数据
cmd > tracert www.baidu.com
C:\Users\海角天涯S>tracert www.baidu.com
通过最多 30 个跃点跟踪
到 www.a.shifen.com [110.242.68.4] 的路由:
1 5 ms 5 ms 4 ms 100.88.96.1
2 6 ms 5 ms 6 ms 61.187.3.1
3 5 ms * 10 ms 61.137.12.237
4 * * * 请求超时。
5 37 ms 39 ms 41 ms 202.97.98.5
6 * * * 请求超时。
7 44 ms 41 ms 35 ms 219.158.44.125
8 * * * 请求超时。
9 * * * 请求超时。
10 95 ms 42 ms 55 ms 110.242.66.178
11 * * * 请求超时。
12 * * * 请求超时。
13 * * * 请求超时。
14 * * * 请求超时。
15 * * * 请求超时。
16 244 ms 239 ms 236 ms 110.242.68.4
跟踪完成。
IPV4 和IPV6
最早只有IPV4(32位)、IPV6(128位)! 2的128次方台电脑,一台电脑一个IP
端口
端口分类
TCP Socket编程快速入门
-
服务器端的处理流程
- 监听端口 8888
- 接收客户端的tcp连接,建立客户端和服务器端的链接
- 创建goroutine,处理该链接的请求(通常客户端会通过连接发送请求包)
-
客户端的处理流程
- 建立与服务端的链接
- 发送请求数据,就收服务器端返回的结果数据
- 关闭链接
聊天系统
思路
服务器
package main
import (
"fmt"
"net"
)
const (
host = "localhost"
port = "8888"
addr = host + ":" + port
)
func process(conn net.Conn) {
defer conn.Close()
// 循环接收客户端发送的数据
for {
// 创建一个new切片
buf := make([]byte, 1024)
// 1、等待客户端通过conn发送信息
// 2、如果客户端没有write([]byte),那么协程就阻塞在这里
n, error := conn.Read(buf)
if error != nil {
fmt.Printf("%v已退出...\n", conn.RemoteAddr().String())
return
}
// 客户端早在尾部追加了\n回车符
fmt.Printf("接收到%v的数据:%v", conn.RemoteAddr().String(), string(buf[:n]))
}
}
func main() {
// 1、tcp协议
// 2、监听8888端口
listen, error := net.Listen("tcp", addr)
if error != nil {
fmt.Println("Server监听失败,", error)
return
}
// 3、延时等待
defer listen.Close()
// 4、等待客户端连接
for true {
fmt.Println("等待客户端连接")
conn , error := listen.Accept()
if error != nil {
fmt.Println("客户端连接错误,", error)
} else {
fmt.Printf("%v成功连接\n", conn.RemoteAddr().String())
}
// 4.1、这里启动一个协程,为客户端服务
go process(conn)
}
}
客户端
package main
import (
"bufio"
"flag"
"fmt"
"net"
"os"
"strings"
)
func main() {
var host string
var port string
flag.StringVar(&host, "h", "localhost", "获取host,默认是175.0.211.19")
flag.StringVar(&port, "p", "8888", "获取ip地址,默认为8888")
flag.Parse()
var address = fmt.Sprintf("%v:%v", host, port)
conn, connError := net.Dial("tcp", address)
if connError != nil {
fmt.Println("连接服务器错误,", connError)
return
}
defer conn.Close()
fmt.Println("连接服务器成功,", conn.RemoteAddr().String())
// 客户端发送单行数据 【终端标准输入,Socket的本质是文件】
// 磁盘满了,无法进行Socket传输-->网络不通
reader := bufio.NewReader(os.Stdin)
for {
// 从终端读取一行用户的输入
fmt.Print("请输入内容:")
data, readerError := reader.ReadString('\n')
if readerError != nil {
fmt.Println("读取终端输入错误,error:", readerError)
}
// 如果用户输入exit,就退出程序
if strings.TrimSpace(data) == "exit" {
fmt.Println("客户端退出OK!")
return
}
// 再将data 发送给Server
_, error := conn.Write([]byte(data))
if error != nil {
fmt.Println("发送给Server错误,", error)
}
}
}
Redis
Redis 基本使用
说明:Redis安装默认16个数据库,默认使用0号数据库
-
添加 key-value
[set]
-
查看当前Redis所有的 key
[keys *]
-
获取 key 对应的值
[get key]
-
切换Redis数据库
[select index]
-
如何查看当前数据库的 key-value 数量
[dbsize]
-
清空当前数据库的 key-value 和 清空所有数据库的 key-value
[flushdb flushall]
-
SET
- mset key1 val1 key2 val2 [设置多个key]
- mget key1 key2
- setex key 10 value
10s超时
-
GET
-
DEL
Redis hash(哈希)
Redis hash 是一个键值对集合,key不能重复,无序
用于存储结构体
type User struct {
Name string
Gender string
age ing
}
- hset user name “tom” 存
- hget user name 取
- hset user sex “男”
- hget user sex
- hset user age 18
- hget user age
hgetall
- hgetall user
hmset
- hmset user name “tom” sex “男” age 18
hlen
- hlen user
hexists key field
hexists user name
Redis List(链表)
list:链表,有序,管道
lpush
:
left push [左边插入]
lPush name lsx carter tom
rpush
right push [右边插入]
lrange
LRange name 0 -1 [倒数第一个]
lpop
left pop [取出list左边数据并移走]
rpop
right pop [取出list右边数据并移走]
实际应用
- 最近浏览的10个文件
Redis Set(集合)
set:无序,不重复,HashTable的数据结构
举例:存放电子邮件
- 添加
sadd
sadd email 2672439345@qq.com
- 取出所有值
smembers
smembers email
- 判断是否存在
sismember
sismember email 2672439345@qq.com
- 删除
srem
srem email 2672439345@qq.com
Golang操作Redis
1、安装第三方开源Redis库
操作Redis的API
1)使用第三方的Redis库:redis库
2)使用Redis前,先安装第三方库,在GOPATH路径
下安装指令:
D:\Work\Goland\Go>go get github.com/garyburd/redigo/redis
3)安装后:
src下出现github.com目录
d:\Work\Goland\Go\src\github.com>dir
驱动器 D 中的卷是 Data
卷的序列号是 2C37-C14D
d:\Work\Goland\Go\src\github.com 的目录
2020/12/19 18:16 <DIR> .
2020/12/19 18:16 <DIR> ..
2020/12/19 18:16 <DIR> garyburd
0 个文件 0 字节
3 个目录 821,155,868,672 可用字节
d:\Work\Goland\Go\src\github.com>
特别说明
确保已经安装并配置Git
D:\Work\Goland>git version
git version 2.27.0.windows.1
2、Go操作Redis
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
// 1、Redis数据库连接
conn, error := redis.Dial("tcp", "localhost:6379")
if error != nil {
fmt.Println("连接Redis数据库失败,", error)
return
}
fmt.Println("连接Redis数据库成功!")
defer conn.Close()
// 2、Redis数据库存值
_, err := conn.Do("Set", "key", "廖述幸")
if err != nil {
fmt.Println("操作Redis数据库失败!")
return
}
fmt.Println("操作Redis数据库成功!")
// 3、Redis数据库取值
value ,getError:= redis.String(conn.Do("Get", "key"))
if getError != nil {
fmt.Println("从Redis中取值失败!")
return
}
fmt.Println("取出的值:", value)
}
3、批处理
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
// 1、Redis数据库连接
conn, error := redis.Dial("tcp", "localhost:6379")
if error != nil {
fmt.Println("连接Redis数据库失败,", error)
return
}
fmt.Println("连接Redis数据库成功!")
defer conn.Close()
// 2、Redis数据库存值
_, err := conn.Do("HMSet", "student", "name", "廖述幸", "age", 18, "sex", "男")
if err != nil {
fmt.Println("操作Redis数据库失败!")
return
}
fmt.Println("操作Redis数据库成功!")
// 3、Redis数据库取值
value ,getError:= redis.StringMap(conn.Do("HGetAll", "student"))
if getError != nil {
fmt.Println("从Redis中取值失败!")
return
}
fmt.Println("取出的值:", value)
name := value["name"]
fmt.Println(name)
}
结果
连接Redis数据库成功!
操作Redis数据库成功!
取出的值: map[age:18 name:廖述幸 sex:男]
廖述幸
4、设置有效时间
_, error := conn.Do("expire", "name", "10")
Redis 连接池
1)打开一个Redis连接,关闭(一次性)
2)连接池,先创建一个池的Redis连接,用就拿,不用就放回去,也不关闭
节省的连接时间
代码
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
var pool *redis.Pool
func init() {
pool = &redis.Pool{
// 最大空闲连接数
MaxIdle: 8,
// 和数据库最大连接数, 0表示没有限制
MaxActive: 0,
// 最大空闲时间
IdleTimeout: 100,
Dial: func() (conn redis.Conn, err error) {
return redis.Dial("tcp", "localhost:6379")
},
}
}
func main() {
// 从pool池中取出一个连接
conn := pool.Get()
// 延迟关闭
defer conn.Close()
// 存值
_, setError := conn.Do("Set", "name", "Carter-廖述幸")
if setError != nil {
fmt.Println("存值失败,", setError)
return
}
// 取值
data, getError := redis.String(conn.Do("Get", "name"))
if getError != nil {
fmt.Println("取值失败,", getError)
return
}
fmt.Println(data)
}
结果
Carter-廖述幸
:", value)
name := value[“name”]
fmt.Println(name)
}
**结果**
~~~go
连接Redis数据库成功!
操作Redis数据库成功!
取出的值: map[age:18 name:廖述幸 sex:男]
廖述幸
4、设置有效时间
_, error := conn.Do("expire", "name", "10")
Redis 连接池
1)打开一个Redis连接,关闭(一次性)
2)连接池,先创建一个池的Redis连接,用就拿,不用就放回去,也不关闭
节省的连接时间
代码
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
var pool *redis.Pool
func init() {
pool = &redis.Pool{
// 最大空闲连接数
MaxIdle: 8,
// 和数据库最大连接数, 0表示没有限制
MaxActive: 0,
// 最大空闲时间
IdleTimeout: 100,
Dial: func() (conn redis.Conn, err error) {
return redis.Dial("tcp", "localhost:6379")
},
}
}
func main() {
// 从pool池中取出一个连接
conn := pool.Get()
// 延迟关闭
defer conn.Close()
// 存值
_, setError := conn.Do("Set", "name", "Carter-廖述幸")
if setError != nil {
fmt.Println("存值失败,", setError)
return
}
// 取值
data, getError := redis.String(conn.Do("Get", "name"))
if getError != nil {
fmt.Println("取值失败,", getError)
return
}
fmt.Println(data)
}
结果
Carter-廖述幸