首页 > 编程学习 > Golang学习(二)

Golang学习(二)

发布时间:2022/8/21 15:06:55

12.单元测试

12.1引入

在我们工作中有时需要去确认一个函数或者一个模块的结果是否正确,如:

 

 

12.2传统的方法解决问题

在main函数中调用addUpper函数,看看实际输出结果是否和预期的结果一致,如果结果一致则说名函数正确,否则函数有错误,

代码实现:

 

 

这种方法的缺点:

1)不方便,在main()函数中调用,这样就需要去修改main函数,如果项目正在运行,就不可能去停止项目

2)不利于管理,当我们测试多个函数或者模块时,都需要我们写在main函数,不利于管理和清晰我们的思路

12.3单元测试

12.3.1基本介绍

Go语言自带一个轻量级的测试框架testing和自带的go test 命令来实现单元测试和性能测试,testing框架和其他语言中的框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。

通过单元测试用例,可以解决如下问题:

1)确保每个函数是可以运行的,且运行结果是正确的。

2)确保写出来的代码性能是好的

3)单元测试能及早的发现程序设计或者实现的逻辑错误,使问题及早暴露,便于问题的定位解决,二性能测试的重点在于发现程序设计上的问题,让程序能够在高并发的情况下保持稳定。

 

详细情况见官方文档:

12.3.2快速入门

使用GO的单元测试,对addUpper和sub函数进行测试

测试文件和被测试问价在同一包下面:

 

 

代码:

cal.go

package main
import(
    
)
func addUpper(n int )int {
    res:=0
    for i:=1;i<=n-1;i++{
        res+=i
    }
    return res
}

 

cal_test.go

package main
import (
    _"fmt"
    "testing"
)

func TestAddUpper(t *testing.T){
    res:=addUpper(10)
    if res!=55{
        //fmt.Printf("addUpper(10)执行错误,期望值=%v,实际值=%v",55,res)
        t.Fatalf("addUpper(10)执行错误,期望值=%v,实际值=%v",55,res)
    }
    //如果正确,输出日志
    t.Logf("addUpper(10)执行正确")
}

 

运行结果:

 

 

原理图:

12.3.3总结

1)测试用例文件名必须以_test.go结尾

2)测试用例函数名形如TestXxx形式

3)TestXxxx( * testing.T)参数类型必须是testing.T

4)一个测试用例文件可以有多个测试用例函数,比如TestAddUpper(),TestSub()

5)运行测试用例指令

  go test 如果正确,无日志输出,如果错误会输出日志

  go test -v 运行正确或者错误都会输出日志

6)当出现错误时,可以使用t.Fatalf 来格式化输出错误信息,并退出程序

7)t.Logf 方法可以输出相对应的日志。

8)PASS表示测试用例运行成功,FALL表示测试用例运行失败

9)测试单个文件,一定要带上被测试的原文件   go test -v cal.go cal_test.go

10)测试单个函数或者模块   go test -v -test.run TestAddUpper

12.4综合案例

 

monster.go

package monster
import(
    "encoding/json"
    "io/ioutil"
    "fmt"
)

type Monster struct{
    Name string
    Age int
    Skill string
}

func (this *Monster)Store() bool{
    data,err :=json.Marshal(this)
    if err!=nil{
        fmt.Println("marshal err=",err)
        return false
    }
    //正确序列化就将序列化后的结果写入文件
    filePath := "d:/monster.txt"
    err =ioutil.WriteFile(filePath,data,0666)
    if err!=nil{
        fmt.Println("write file err=",err)
        return false
    }

    return true
}

func(this *Monster)ReStore()bool{
    filePath:="d:/monster.txt"
    data,err:=ioutil.ReadFile(filePath)
    if err!=nil{
        fmt.Println("read file err=",err)
        return false
    }

    err=json.Unmarshal(data,this)
    if err!=nil{
        fmt.Println("Unmarshal err=",err)
        return false
    }

    return true
}

 

monster_test.go

package monster
import(
    "testing"
)

func TestStore(t * testing.T){
    monster:=&Monster{
        Name:"红孩儿",
        Age:10,
        Skill:"吐火",
    }

    res:=monster.Store() 
    if res!=true{
        t.Fatalf("monster.Store()错误,希望为=%v,实际为=%v",true,res)
    }else{
      t.Logf("monster.Store()测试成功!")
  } } func TestReStore(t
*testing.T){ var monster =&Monster{} res:=monster.ReStore() if !res{ t.Fatalf("monster.ReStore()错误,希望为=%v,实际为=%v",true,res) } if monster.Name!="红孩儿"{ t.Fatalf("monster.ReStore()错误,希望为=%v,实际为=%v","红孩儿",res) } t.Logf("monster.ReStore() 测试成功!") }

 

运行结果

 

 

 

13.GOROUTINE和CHANNEL

13.1GOROUTINE-基本介绍

13.1.1进程和线程介绍

1)进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位

2)线程是进程的一个执行实例,是程序执行的最小单元,是比进程更小的能独立运行的基本单位

3)一个进程能创建和销毁多个线程,同一个进程中的多个线程可以并发执行。

4)一个程序运行至少有一个进程,一个进程至少有多个线程

13.1.2程序、进程、线程关系示意图

13.1.3并发和并行

并发和并行的区别:

1)多线程程序在单个核上运行就是并发

2)多线程在多个核上运行就是并发

 

 

13.1.4Go协程和go主线程

Go主线程(有程序员直接成为线程,也可理解为进程):一个Go线程上可以起多个协程,协程是轻量级的线程

协程的特点:

  1)独立的栈空间

  2)共享程序的堆空间

  3)调度由用户控制

  4)协程是轻量级的线程

 

 

13.2goroutine案例

 

 代码:

 1 package main
 2 import(
 3     "fmt"
 4     "strconv"
 5     "time"
 6 )
 7 
 8 func test(){
 9     for i:=1;i<=10;i++{
10         fmt.Println("test() hello,world"+strconv.Itoa(i))
11         time.Sleep(time.Second)
12     }
13 }
14 
15 func main(){
16     go test()
17     for i:=1;i<=10;i++{
18         fmt.Println("main() hello,world"+strconv.Itoa(i))
19         time.Sleep(time.Second)
20     }
21 }

 

输出效果说明,main这个主线程和test这个协程同时执行

主线程和协程执行的流程图:

 

 总结:

1)主线程是一个物理线程,直接作用在cpu上,是重量级的,非常耗费cpu资源

2)协程是从主线程开启的,是轻量级的线程,是逻辑态,对资源耗费相对较少

3)Golang的协程机制是重要的特点,可以轻松的开启上万个协程,其他编程语言的并发机制一般是基于线程的,开启过多的线程资源耗费大,这里就凸显出Go的优势了

13.3goroutine的调度模型

13.3.1MGP模式

13.3.2MGP模式的运行状态

状态1:

 

 状态2:

 

 

 

13.4设置golang运行的cpu数

为了充分利用多CPU的优势,可以在Golang程序中,设置运行的CPU数

 

 

13.5channel管道

13.5.1引入

需求:1-200的各个数的(1-n)的和,并且把结果放进map中,最后显示出来

分析思路:

  使用goroutine完成,效率高,但是会出现并行/并发安全问题

  提出了不同的goroutine如何通信的问题

代码实现:

package main
import(
    "fmt"
    "time"
)

var(
    myMap=make(map[int]int)
)

func test(n int) {
    res:=0
    for i:=1;i<=n;i++{
        res+=i
    }
    myMap[n]=res
}

func main(){
    for i:=1;i<=200;i++{
        go test(i)
    }

    time.Sleep(time.Second*10)

    for i,v:=range myMap{
        fmt.Printf("map[%d]=%d",i,v)
    }
}

示意图:

 

 

以上代码运行是有问题的,发生了资源冲突

13.5.2不同代码间如何通信

1)全局变量的互斥锁

2)使用管道channel来解决

13.6.3使用全局变量加锁同步改进程序

 

 改进后代码:

package main
import(
    "fmt"
    "time"
    "sync"
)

var(
    myMap=make(map[int]int)
    lock sync.Mutex
)

func test(n int) {
    res:=0
    for i:=1;i<=n;i++{
        res+=i
    }
    lock.Lock()
    myMap[n]=res
    lock.Unlock()
}

func main(){
    for i:=1;i<=200;i++{
        go test(i)
    }

    time.Sleep(time.Second*10)

    lock.Lock()
    for i,v:=range myMap{
        fmt.Printf("map[%d]=%d\n",i,v)
    }
    lock.Unlock()
}

13.5.3为什么需要管道

 

 

13.5.4channel的基本介绍

管道的定义:

var 变量名 chan 数据类型

举例:

var intChan chan int

var mapChan chan map[int][string]

var perChan chan *Person

说明:

channel 是引用类型

channel必须初始化才能写入数据,即make后才能使用

管道是有类型的,chan int 类型的只能写入整数int

 

1)管道的本质就是一个数据结构-队列

2)数据是先进先出

3)线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的

4)channel有类型的,一个string的channel只能存放string类型数据

 

 

13.5.5管道的初始化,写入数据到管道,从管道读取数据及基本事项

package main
import(
    "fmt"
)

func main(){
    //创建一个可以存放三个int类型的管道
    var intChan chan int 
    intChan=make(chan int,3)
    fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n",intChan,&intChan)

    //向管道中写入数据
    intChan <-10
    num:=211
    intChan<-num
    intChan<-111
    //intChan<-112   当给管道写入数据超过其容量时all goroutines are asleep - deadlock!

    //看管道的长度和cap容量
    fmt.Printf("channel len=%v cap=%v\n",len(intChan),cap(intChan))
    //输出:channel len=3 cap=3

    //从管道中读取数据
    var num2 int 
    num2=<-intChan 
    fmt.Println("num2=",num2)
    fmt.Printf("channel len=%v cap=%v\n",len(intChan),cap(intChan))
    //输出:channel len=2 cap=3

    //在没有使用协程的情况下,如果我们的管道中的数据已经取出,再去就会报告deadlock

    num3:=<-intChan
    num4:=<-intChan
    num5:=<-intChan
    fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)
    //报错:all goroutines are asleep - deadlock!

}

 

13.5.6channel使用的注意事项

1)只能存放指定的数据类型

2)数据放满后就不能在放入

3)如果从channel取出数据后,可以继续放入

4)在没有使用协程的情况下,如果channel数据取完了,在取,就会报dead lock,使用协程的情况下就会等待

 

13.6案例

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13.7channel的遍历和关闭

channel支持for range 的方式进行遍历,在遍历时,如果没有关闭管道,deadlock的错误

如果关闭管道,会正常遍历数据,遍历完会退出遍历

 

 13.7应用案例

应用案例1:

 代码:

 1 package main
 2 import(
 3     "fmt"
 4     "time"
 5 )
 6 
 7 var (
 8     Write_Chan =make(chan int,50)
 9     Exit_Chan=make(chan bool,1)
10 )
11 
12 func WritDate(){
13     for i:=1;i<=50;i++{
14         Write_Chan<-i
15         fmt.Println("write date=",i)
16         
17         //让输出的效果更加明显,Read管道会等待写管道的
18         time.Sleep(time.Second)
19     }
20     close(Write_Chan)
21 }
22 
23 func ReadDate(){
24     for{
25         num,err:=<-Write_Chan
26         if !err {
27          break
28         }
29         fmt.Println("read date=",num)
30     }
31     Exit_Chan<-true
32     close(Exit_Chan)
33 
34 }
35 
36 func main(){
37     go WritDate()
38     go ReadDate()
39     for{
40         _,ok:=<-Exit_Chan
41         if !ok{
42             break
43         }
44          
45     }
46 
47 }

 

 

应用案例2:也就是说当写进的数据必须大于管道容量一个管道不能只写而不读 

 

 应用案例3:

 代码:

package main
import(
    "fmt"
    "time"
)

func WriteDate(intChan chan int){
    for i:=2;i<=8000;i++{
        intChan<-i
    }
    close(intChan)
}

func PriNum(priChan chan int,intChan chan int,exitChan chan bool){
    for{
        //睡眠的目的是因为主线程的开始读取priChan时可能还没写入数据(可能是,也不太确定
        time.Sleep(time.Millisecond*10)
        num,err:=<-intChan
        if !err{
            break
        }
        flag:=true
        for j:=2;j<=num/2;j++{
            //如果不是素数
            if num%j==0{
                flag=false
            }
        }
        if flag==true{
            priChan<-num
        }
    }
    fmt.Println("有一个PriNum协程结束")
    exitChan<-true
}
 
func main(){
    intChan:=make(chan int,2000)
    priChan:=make(chan int,500)
    exitChan:=make(chan bool,4)
    go WriteDate(intChan)
    for i:=1;i<=4;i++{
        go PriNum(priChan,intChan,exitChan)
    }
    go func(){
        for{
            <-exitChan
        }
        close(priChan)
    }()
    for {
        v,err:=<-priChan
        if !err{
            break
        }
        fmt.Println("prinum=",v)
    }
     
    fmt.Println("主线程接受")

}

 

素数输出程序改为一下,就不需要休眠了

 

 

13.8channel注意事项和细节

1)channel可以声明为只写或者只读性质

 

 

 2)使用select可以解决从管道取数据的阻塞问题

 1 package main
 2 import(
 3     "fmt"
 4     "strconv"
 5 )
 6  
 7 func main(){
 8    intChan:=make(chan int,10)
 9    stringChan:=make(chan string,10)
10    for i:=1;i<=10;i++{
11     intChan<-i
12    }
13    for i:=1;i<=10;i++{
14     stringChan<-"hello"+strconv.Itoa(i)
15    }
16    //传统的方法在遍历管道时,如果没有关闭管道会发生阻塞而导致deadlock
17    //在实际开发中也不好确定什么时候关闭管道,可以使用select方法解决
18    label:
19     for{
20         select{
21             //如果intChan一直没有关闭,不会阻塞而deadlock,会自动到下一个case匹配
22         case v:=<-intChan:
23             fmt.Println("intChan date=",v)
24         case v:=<-stringChan:
25             fmt.Println("stringChan date=",v)  
26         default:
27             fmt.Println("所有管道中的数据已经取完")
28             //return 
29             break label
30         }
31     }
32 
33 }

4)goroutine中使用recover,解决协程中出现painc,导致程序崩溃问题

 

 

 

 

package main
import(
    "fmt"
    "time"
)

func SayHello(){
    for i:=1;i<=10;i++{
        fmt.Println("hello,world!")
    }
}

func test(){
    defer func(){
        if err:=recover();err!=nil{
            fmt.Println("test()发生错误,err=",err)
        }
    }()
    var myMap map[int]string
    myMap[0]="eeee"
}
func main(){
    go SayHello()
    go test()

    for i:=1;i<=10;i++{
        fmt.Println("main() i=",i)
        time.Sleep(time.Second)
    }
   
}

 

 

  

14.反射

14.1反射的基本介绍

14.1.1基本介绍

1)反射可以在运行时,动态获取变量的各种信息,比如变量的类型(type),变量的类别(kind)

2)如果是结构体变量,该可以获取到结构体本身的信息(包括结构体的字段,方法)

3)通过使用反射可以修改变量的值,可以调用关联的方法

4)反射要引用的包"reflect"

5)示意图:

 

 

 

14.1.2反射的应用场景

 

 

 

14.1.3反射的重要函数和概念

1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型

 

2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型,reflect.Value是一个结构体类型,通过reflect.Value可以获得关于该变量的很多信息

 

3)变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中会经常使用到

 

 

14.2使用反射机制编写函数的适配器,桥连接

 

 

 

14.3反射案例

 

 

 

package main
import(
    "fmt"
    "reflect"
)
type Student struct{
    Name string
    Age int 
}
func reflectTest01(num interface{}){

    //获取reflect.Type类型
    rType:=reflect.TypeOf(num)
    fmt.Println("rType=",rType)//输出 int

    //获取到reflect.Value类型
    rVal:=reflect.ValueOf(num)
    fmt.Println("rVal=",rVal)//输出10

    //n:=2+rVal 报错:如下:cannot convert 2 (untyped int constant) to struct{
    //typ *reflect.rtype; ptr unsafe.Pointer; reflect.flag}
    n:=2+rVal.Int()//基本数据类型可以这样,结构体需转成interface{}->类型断言
    fmt.Println("n=",n)//输出:n= 12
}

func reflectTest02(num interface{}){
    //获取reflect.Type类型
    rType:=reflect.TypeOf(num)
    fmt.Println("rType=",rType)//输出 rType= main.Student

    //获取到reflect.Value类型
    rVal:=reflect.ValueOf(num)
    fmt.Printf("rVal=%T,rVal=%v\n",rVal,rVal)//输出rVal=reflect.Value,rVal={marry 18},但是不能取出里面的字段

    iV:=rVal.Interface()
    fmt.Printf("iV=%v iV type=%T\n",iV,iV)//输出:iV={marry 18} iV type=main.Student
    //类型断言后才能取出里面的方法和字段
    stu,ok:=iV.(Student)
    if ok{
        fmt.Println(stu.Name)//输出 marry
    }

}
func main(){
    // var num int =10
    // reflectTest01(num) 
    stu:=Student{Name:"marry",Age:18}
    reflectTest02(stu)
}

 

14.4注意事项和细节

1)reflect.Value.Kind获取变量的类别,返回的是一个常量

 

 

 

2)Type和Kind的区别

Type是类型,Kind是类别,他们可能相同,也可能不同

3)使用反射的方式获取变量的对应类型的值,要求数据类型匹配,比如x 是int,那么就因该使用reflect.Value(x).Int(),而不是其他的,否则报错

 

 

 4)通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入变量的值,同时需要使用到reflect.Value.Elem()方法

 

 

 5)reflect.Value.Elem()因该如何理解?

 

 

 

14.5反射的最佳实践

 

ackage main
import(
    "fmt"
    "reflect"
)

type  Student struct{
    Name string `json:"name"`
    Age int `json:"age"`
    Sex string `json:"sex"`
    Score float32 `json:"score"`
}

func (s Student) GetSum(num int,num2 int) int{
    return num +num2 
}

func (s Student)Set(name string,age int,sex string,score float32){
    s.Name=name
    s.Age=age
    s.Sex=sex
    s.Score=score
}

func (s Student) Print(){
    fmt.Println("学生的基本信息为:",s)
}

func TestSturt(b interface{}){
    typ:=reflect.TypeOf(b)
    val:=reflect.ValueOf(b)
    kid:=val.Kind()//kind()返回的是常量
    if kid!=reflect.Struct{
        return 
    }

    //获取结构体字段
    num:=val.NumField()
    fmt.Println("结构体的字段个数num=",num)//num= 4
    for i:=0;i<num;i++{
        fmt.Printf("Field %d: 值为=%v\n",i,val.Field(i))
        /*输出:Field 0: 值为=marry Field 1: 值为=12 Field 2: 值为=L
            Field 3: 值为=78.8*/
        //获取到struct的标签,需要通过reflect.Type来获取
        tagVal:=typ.Field(i).Tag.Get("json")
        //如果获取到标签就显示,否则不显示
        if tagVal!=""{
            fmt.Printf("Field %d: tag=%v\n",i,tagVal)
        }
    }
    
    //获取该结构体方法个数
    numMethod:=val.NumMethod()
    fmt.Printf("struct has %d method\n",numMethod)

    //方法的默认排序是按照函数名的排序(ASCII码)
    //获取第二个方法并且调用它
    val.Method(1).Call(nil)

    //调用结构体第一个方法
    var params []reflect.Value
    params=append(params,reflect.ValueOf(10))
    params=append(params,reflect.ValueOf(40))
    //call()参数类型是[]reflect.Value,返回值类型是[]reflect.Value
    res:=val.Method(0).Call(params)
    fmt.Println("res=",res[].Int())//输出 50
}
 
func main(){
     var stu=Student{
        Name:"marry",
        Age:12,
        Sex:"L",
        Score:78.8,
     }
     TestSturt(stu)

}

输出结果:

 

 

 

 

 

 

15.TCP编程

15.1TCP SOCKET编程快速入门

15.1.1服务端的处理流程

1)监听端口(8888)

2)接收客户端的tcp连接,建立客户的和服务器的连接

3)创建协程,处理该链接的请求(客户端会通过连接发送请求包)

 

15.1.2客户端的处理流程

1)建立与服务端的连接

2)发送请求数据【终端】,接收服务端返回的数据

3)关闭连接’

示意图:

 

 15.1.3代码实现

 

服务端代码:

 

 1 package main
 2 import(
 3     "fmt"
 4     "net"
 5     _"io"
 6 )
 7 
 8 func process(conn net.Conn){
 9     //循环接收客户端发送的数据
10     defer conn.Close()//关闭连接避免,未关闭的连接太多,其他客户端没法连接
11 
12     for{
13         //服务端接收数据
14         buf:=make([]byte,1024)
15         //等待客户端通过conn发送数据,如果客户端没有发送,就一值在这里等待
16         //如果连接断了,或者超时就会报错
17         fmt.Printf("服务器在等待客户端%s发送信息\n",conn.RemoteAddr().String())
18         n,err:=conn.Read(buf)
19         if err!=nil{
20             fmt.Println("server read err=",err)
21             return 
22         }
23         //显示客户端发送的内容到服务器终端
24         fmt.Println(string(buf[:n]))
25     }
26 
27 }
28 
29 func main(){
30     fmt.Println("main()开始监听...")
31     listen,err:=net.Listen("tcp","0.0.0.0:8888")
32     if err!=nil{
33         fmt.Println("监听出错 err=",err)
34         return 
35     }
36     defer listen.Close()//延时关闭
37 
38     //循环等待客户端的连接
39     for{
40         fmt.Println("循环等待客户端的连接....")
41         conn,err:=listen.Accept()
42         if err!=nil{
43             fmt.Println("Accept err=",err)
44         }else{
45             fmt.Println("Accept 成功 conn=",conn,"客户端的IP",conn.RemoteAddr().String())
46         }
47         go process(conn)
48     }
49     fmt.Printf("listen=%v\n",listen)//输出:listen=&{0xc00007ea00 {<nil> 0}}
50      
51 
52 }

 

 

 客户端代码:

 

package main
import(
    "fmt"
    "net"
    "bufio"
    "os"
)
func main(){
    conn,err1:=net.Dial("tcp","127.0.0.1:8888")
    defer conn.Close()
    if err1!=nil{
        fmt.Println("连接失败 err=",err1)
        return
    }
    //fmt.Println("连接成功,conn=",conn)
    //客户端可以发送单行数据然后退出
    //os.Stdin标准输入,键盘输入的其实是存放到一个文件里面的,然后从文件里面取
    reader:=bufio.NewReader(os.Stdin)
    //键盘输入
    line,err2:=reader.ReadString('\n')
    if err2!=nil{
        fmt.Println("readString err=",err2)
        return
    }

    //将line发送给服务器
    n,err3:=conn.Write([]byte(line))
    if err3!=nil{
        fmt.Println("write err3=",err3)
    }
    fmt.Printf("客户端发送了%d字节的数据",n)

}

进阶:

 

 对客户端文件做了改变

package main
import(
    "fmt"
    "net"
    "bufio"
    "os"
    "io"
    "strings"
)
func main(){
    conn,err1:=net.Dial("tcp","127.0.0.1:8888")
    defer conn.Close()
    if err1!=nil{
        fmt.Println("连接失败 err=",err1)
        return
    }
    //fmt.Println("连接成功,conn=",conn)
    //客户端可以发送单行数据然后退出
    //os.Stdin标准输入,键盘输入的其实是存放到一个文件里面的,然后从文件里面取
    reader:=bufio.NewReader(os.Stdin)
    //键盘输入
     for{
        line,err2:=reader.ReadString('\n')
        if err2==io.EOF{
            fmt.Println("readString err=",err2)
            return
        }
        //如果用户输入的是exit就退出
        line=strings.Trim(line," \r\n")
        if line=="exit"{
            fmt.Println("客户端退出...")
            break
        }
        //将line发送给服务器
        _,err3:=conn.Write([]byte(line))
        if err3!=nil{
            fmt.Println("write err3=",err3)
        }
        //fmt.Printf("客户端发送了%d字节的数据",n)
    }

}

 

16.REDIS的使用

16.1redis基本介绍

redis下载、安装教程:Redis下载和安装(Windows系统) (biancheng.net)

redis安装后默认有16个数据库,编号0-15,初始默认使用0号库。

 

redis操作的基本原理图:

 

 

 

16.2redis操作指令

操作指令官方文档:

 

1)添加key-val [set/mset]

2)查看当前redis库的所有key [key *]

3)获取key-val对应的值【get key /mget key...】

4)切换redis数据库 【select index】

5)如何查看当前数据库的key-val数量【dbsize】

6)清空当前数据库的key -val 【flushdb】和清空所有数据库的key-val【flushall】 

 

 

 

 

 Redis的五大数据类型:

Redis的五大数据类型是String、Hash、List、Set、zset(sorted set:有序集合)

 

16.3String字符串

  • 基本介绍:

  是redis基本的数据类型,一个key对应一个value

  string类型是二进制安全的,除了普通字符串以外,也可以存放图片等数据

  redis中一个字符串value最大是512M

 

  • 举例:存放一个地址信息

  set address beijin

  说明:key:address,value:beijin

  

 

  • String 的CURD

  set [如果存在就是修改,否则就是删除]

  get key 查询

  del key 删除

  

 

 

 

  • 细节说明和注意事项

  setex(set with expire) 键秒值     setex key seconds value

  

 

 

  • mset 同时设置一个或者多个 key-val 对/mget 同时获取一个或者多个 key-val 对

  

 

 

 

16.4 Hash(哈希,类型golang 中的map)

基本介绍:

redist hash 是一个键值对集合,类似golang中的 var user1 map[string][strig]类型

redis hash 是一个string类型的field 和value的映射表,hash特别适合存储对象

 

举例,存放一个User 信息:

 hset user1 name zhangsan age 30 address beijin【hset key field value】

其中key:user1 field-vaule: name zhangsan、age 30 、address beijin

 

 

Hash-CURD:

hset/hmset/hget/hmget/hgetall/hdel

 

 

 

 

Hash的注意事项和使用细节:

1)hmset和hset都可以同时给一个key设置多个field-value

2)hlen统计一下hash key 有好多个field-value

 

3)hexists key field 查看哈希表Key中,给定域field是否存在

 

16.5List(链表)

基本介绍:

list是简单的字符串链表,按照插入顺序排序,可以添加一个元素到链表头部或者尾部

List本质是链表,List的元素是有序的,顺序跟插入、pop出有关,元素的值可以重复

 

举例,存放多个地址信息:

lpush city tianjin beijin shanghai   【 lpush key value [value ...]】

key:city    value: tianjin beijin shanghai 

 

 

list的CURD:

lpush/rpush/lrange【读】/lpop【出链表,删除】/rpop/del

说明:

 

CURD演示:

 

 

注意事项和使用细则:

1)lindex 按照索引下标获取元素,从左往右,编号从0

2)llen key 返回链表的长度,如果不存在被解释为一个空列表,返回0

3)如果一个list的值全部移除对应的键也就消失了

 

16.6Set(集合)

基本介绍:

Redis的set是string类型的按 ASSCII排序的集合

底层是hash table 数据结构,set 也是存放很多字符串元素,字符串元素是无序的(不按输入的顺序存)而且元素的值不能重复

 

举例,存放多个邮件列表信息:

 

 

set的CURD:

举例说明:

sadd 用法:sadd key member [member ...]

smembers 用法:smembers key, 查询key的所有值

sismerber 用法: sismember key member 查询key 里面是否有member 这个元素

 

 srem  用法: srem key member [member ...], 删除指定值

 

 

16.7Golang操作redis 

16.7.1安装第三方开源Redis库

 

 

16.7.2Set/Get接口

说明:通过Golang添加和获取key-value

package main
import(
    "fmt"
    "github.com/garyburd/redigo/redis"
)

func main(){
    // 连接redis
    conn,err1:=redis.Dial("tcp","127.0.0.1:6379")
    if err1!=nil{
        fmt.Println("redis.Dial err=",err1)
        return 
    }else{
        fmt.Println("conn succ=",conn)
    }
    defer conn.Close()

    //通过go向redis写入和读取数据
    _,err2:=conn.Do("set","name","tomy")
    if err2!=nil{
        fmt.Println("set err=",err2)
        return 
    }else{
        fmt.Println("set ok")
    }
    //取数据
    r,err3:=redis.String(conn.Do("get","name"))
    if err3!=nil{
        fmt.Println("get set err=",err3)
        return 
    }else{
        fmt.Println("get ok")
    }
    fmt.Println("get r=",r)

}

批量超做多个key-val,

 

 

16.7.3操作Hash

go操作redis,一个一个放,一个一个读取

package main
import(
    "fmt"
    "github.com/garyburd/redigo/redis"
)

func main(){
    // 连接redis
    conn,err1:=redis.Dial("tcp","127.0.0.1:6379")
    if err1!=nil{
        fmt.Println("redis.Dial err=",err1)
        return 
    }else{
        fmt.Println("conn succ=",conn)
    }
    defer conn.Close()

    //通过go向redis写入和读取数据
    _,err2:=conn.Do("Hset","user1","name","jion")
    if err2!=nil{
        fmt.Println("Hset err=",err2)
        return 
    }else{
        fmt.Println("hset ok")
    }

    _,err2=conn.Do("Hset","user1","age","18")
    if err2!=nil{
        fmt.Println("Hset err=",err2)
        return 
    }else{
        fmt.Println("hset ok")
    }
    //取数据
    r1,err3:=redis.String(conn.Do("Hget","user1","name"))
    if err3!=nil{
        fmt.Println("hget set err=",err3)
        return 
    }else{
        fmt.Println("hget ok")
    }
    fmt.Println("hget r1=",r1)
    r2,err3:=redis.Int(conn.Do("Hget","user1","age"))
    if err3!=nil{
        fmt.Println("hget hset err=",err3)
        return 
    }else{
        fmt.Println("hget ok")
    }
    fmt.Println("hget r2=",r2)

}

golang操作redis,批量放入和读取

package main
import(
    "fmt"
    "github.com/garyburd/redigo/redis"
)

func main(){
    // 连接redis
    conn,err1:=redis.Dial("tcp","127.0.0.1:6379")
    if err1!=nil{
        fmt.Println("redis.Dial err=",err1)
        return 
    }else{
        fmt.Println("conn succ=",conn)
    }
    defer conn.Close()

    //通过go向redis写入和读取数据
    _,err2:=conn.Do("HMset","user2","name","marry","age",19)
    if err2!=nil{
        fmt.Println("HMset err=",err2)
        return 
    }else{
        fmt.Println("HMset ok")
    }

    //读取数据
    r1,err3:=redis.Strings(conn.Do("Hgetall","user2"))
  //r1,err3:=redis.Strings(conn.Do("hmget","user2","name","age")
if err3!=nil{ fmt.Println("hget set err=",err3) return }else{ fmt.Println("hgetall ok") } for i,v:=range r1{ fmt.Printf("r[%d]=%s\n",i,v) } }

 

16.7.4给数据设置有效时间/操作redis/Redis连接池

给数据设置有效时间:

 

操作redis:

 

 

Redis连接池:

核心代码:

 

 

案例演示:

package main
import(
    "fmt"
    "github.com/garyburd/redigo/redis"   
)

var pool *redis.Pool 
//当启动程序是就初始化连接词
func init(){
    pool=&redis.Pool{
        MaxIdle:8,
        MaxActive:0,
        IdleTimeout:100,
        Dial:func()(redis.Conn,error){
            return redis.Dial("tcp","localhost:6379")
        },
    }
}

func main(){
    //先从pool次中取出一个连接
    coon:=pool.Get()
    defer coon.Close()
    _,err1:=coon.Do("Set","name","tom")
    if err1!=nil{
        fmt.Println("Set err=",err1)
        return 
    }else{
        fmt.Println("Set ok!")
    }

    
    //取出数据
    r1,err2:=redis.String(coon.Do("Get","name"))
    if err2!=nil{
        fmt.Println("get err=",err2)
        return 
    }else{
        fmt.Println("get ok!")
    }

    fmt.Println("r1 name=",r1)

}

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Copyright © 2010-2022 mfbz.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号