GoLang学习笔记之进阶编程(四):标准库常用包

目录

前言

一、Sort包的基本使用

二、Time包的基本使用

2.1.获取时间

2.2.格式化时间

2.3.从字符串中解析时间

2.4.获取时间戳

2.5.时间的加减运算

三、Flag包的基本使用

四、net/http包的基本使用

五、context包的使用

5.1.WithCancel()

5.2.WithDeadLine()

5.3.WithTimeOut()

5.4.WithValue() 

总结


总结
前言

        GO语言标准库为我们封装了许多常用的方法,我将在本章节简要记录几个常用包的使用。包括sort、time、flag、net/http包与context包的简单使用。


一、Sort包的基本使用

        sort包为我们提供了Sort接口,任何数据类型只要满足了Sort接口的以下三种方法,即可调用sort包的函数进行排序。

  • Len()int:要求能够获取待排序数据的长度
  • Less(i,j int)bool:提供数据的比较规则
  • Swap(i,j int):要求Less成立时能调用该方法交换两个值的位置

        我们可以用一个切片封装任何我们想要排序的数据,然后通过自定义Less()方法,调用sort.Sort函数对我们的数据进行排序,下面给出一个示例。

/ 下面通过一个例子对自定义结构体按不同字段排序
type product struct{
	name string
	price float32
	nums int
}

type list []*product

func (l list)Len()int{
	return len(l)
}

// Less 函数定义比较规则,这里先按名称比较
func (l list)Less(i,j int)bool{
	if l[i].name < l[j].name{
		return true
	}
	return false
}

func (l list)Swap(i,j int){
	l[i],l[j] = l[j],l[i]
}

// 打印切片中每个结构体的信息
func (l list) print() {
	for _, v := range l {
		fmt.Printf("name:%s price:%f nums:%d\n", v.name, v.price, v.nums)
	}
}

func main(){
list := list{
		&product{name: "benchi", price: 40.5, nums: 10},
		&product{name: "aodi", price: 41.3, nums: 15},
		&product{name: "laosilaisi", price: 128.8, nums: 2},
	}
	// 根据名称首字母排序
	sort.Sort(list)
	list.print()
    /*输出
    name:aodi price:41.299999 nums:15
    name:benchi price:40.500000 nums:10
    name:laosilaisi price:128.800003 nums:2
    */

}

        我们还可以通过结构体内嵌的方式,利用结构体外部方法覆盖内部方法的原则,修改比较原则,示例如下

type fieldPrice struct {
	list // 匿名嵌入字段list,即结构体fieldPrice拥有list的所有方法
}

/* 给结构体fieldPrice添加一个Less方法,当sort底层调用时,调用的是fieldPrice的Less方法
即外部方法覆盖了内部方法
*/
func (fp fieldPrice) Less(i, j int) bool {
	if fp.list[i].price < fp.list[j].price {
		return true
	}
	return false
}

func byPrice(l list) sort.Interface {
	return &fieldPrice{l}
}

func main(){
    // 按价格升序排序
	fmt.Println("--------------------------------")
	sort.Sort(byPrice(list))
	list.print()
    /*输出
    name:benchi price:40.500000 nums:10
    name:aodi price:41.299999 nums:15
    name:laosilaisi price:128.800003 nums:2
    */
}

        我们还可以通过sort.Reverse方法对数据进行逆序排序

func amin(){

    // 按价格逆序排序
    fmt.Println("--------------------------------")
	sort.Sort(sort.Reverse(byPrice(list)))
	list.print()
    /*输出
    name:laosilaisi price:128.800003 nums:2
    name:aodi price:41.299999 nums:15
    name:benchi price:40.500000 nums:10

    */
}


二、Time包的基本使用

        time包中提供了一些基础方法,使开发者能在程序中获取实时时间,并对时间进行一些相关操作,这里简要介绍一下time包的几种常见用法。

2.1.获取时间

func main(){
    t1 := time.Now() // 返回的是一个time.Time类型的对象
	fmt.Println(t1) // 2021-12-14 16:16:12.891015088 +0800 CST m=+0.000122351

	// 2.使用time.Date()函数获取一个指定的时间对象
	t2 := time.Date(2021,time.July,24,17,52,39,0,time.Local)
	fmt.Println(t2)   // 输出:2021-07-24 17:52:39 +0800 CST
}

2.2.格式化时间

// 3.对于Time对象,可以使用format方法将时间格式化成字符串
	// 格式化模板可以随便写,但是数字必须是2006 1-2-3-4-5
	s1 := t1.Format("2006年1月2日 15时04分05秒")
	fmt.Println(s1) // 输出:2021年12月14日 16时16分12秒

        注意:go语言和别的语言相比比较奇葩的一点就是在时间格式化的时候,只能使用go语言的诞生时间2006年1月2日15点04分05秒,格式可以随便更改,但数据只能是那一时刻。 

2.3.从字符串中解析时间

 // 对于一个字符串,我们可以使用time包中Parse()函数将其转换成time对象
	t3, err := time.Parse("2006年1月2日 15时04分05秒", s1)
	if err != nil {
		fmt.Printf("parse time failed,err:%v\n", err) 
		return
	}
	fmt.Println(t3) // 输出:2021-12-14 16:16:12 +0000 UTC

2.4.获取时间戳

// 4.时间戳:指的是当前时间距离1970年1月1日0时0分0秒的时间间隔
	// time.Unix()获得的时间戳单位为秒
	// time.UnixNano()获得的时间戳单位为纳秒
	fmt.Println(t1.Unix()) //输出:1639469772
	fmt.Println(t1.UnixNano()) // 输出:1639469772891015088

2.5.时间的加减运算

// 时间的加减运算
	t4 := t1.Add(time.Hour * 24)
	fmt.Println(t4) // 输出:2021-12-15 16:16:12.891015088 +0800 CST m=+86400.000122351
	t5 := t4.Sub(t1)
	fmt.Println(t5) // 输出:24h0m0s

三、Flag包的基本使用

        我们在执行某些程序的时候,往往希望能够通过终端命令行输入一些参数来与我们的程序进行互动。那么我们的程序如何获取这些输入的参数呢。

        os包中提供了一个叫os.Args的切片,它保存了用户输入到终端中的所有参数,包括执行程序命令本身。

func getArgs()[]string{
	// os.Args将当前命令行封装成一个slice,第一个值为命令本身
	if len(os.Args) > 1{
		return os.Args[1:]
	}
	return []string{os.Args[0]}
}

func main(){
    ret := getArgs()
	fmt.Println(ret) // 输入:./04args -name=lhq -age=22 输出: [-name=lhq -age=22]
}

        像上面的示例,我们希望输入-name=lhq时,能够直接获得参数的值,而不是整个命令本身,这就要借助我们的flag包了。 

// 1.直接定义flag变量,返回的是指针
	name := flag.String("name", "LuLu", "Please input your name")

	// 2.通过变量绑定,获得的是值
	var age int
	flag.IntVar(&age, "age", 18, "Please input your age")

	// 无论用那种方法设置命令行参数,都必须使用Parse()进行解析,才能在代码中使用这些参数的值
	flag.Parse()
	fmt.Println(*name) // 注意,直接定义flag变量返回的是指针,输出:lhq
	fmt.Println(age) // 输出:22

        此外,flag包也有几个常用函数能获取参数列表

// 常见flag函数
	fmt.Println(flag.Args())  // flag.Arg()返回命令行除命令本身和自定义之外的所有参数,此处输出:[]
	fmt.Println(flag.NArg())  // flag.NArg()返回命令行中除了自定义的参数以外的参数个数,此处输出:0
	fmt.Println(flag.NFlag()) // flag.NFlag()返回命令行中自定义参数的个数,此处输出:2

四、net/http包的基本使用

        net/http包提供了许多有关web开发的方法,是一个十分重要的包,这里仅做简单介绍,更多情况需要自行学习。

        我们可以使用http.Get()函数来向指定url发出一个Get请求。

func main(){
    resp1,err := http.Get("https://www.baidu.com") 
	defer resp1.Body.Close() 
	msg,err := ioutil.ReadAll(resp1.Body)
	if err != nil{
		fmt.Printf("Read http response failed,err:%v\n",err)
		return
	}
	fmt.Println(msg)

}

        我们进行web开发时,往往需要以POST方式申请一些API,POST请求的方式有多种,比如在url后面直接带参数,也可以在Body中带请求,还有json,form-data等格式,这里介绍一种带参数的POST请求方法。

func main(){
    postValue := url.Values{
		"login":{"lulu},
		"password":{"123456"},
	}

    postUrl := fmt.Sprintf("http://127.0.0.1:9000/api/authentication/login")

	resp, err := http.PostForm(postUrl, postValue)
    defer resp.Body.Close()
    if err != nil{
        log.Fatal("get response failed,err:%v\n",err)
    }
    
    cookies := resp.Cookies() // 获取response的所有cookies,若没有则返回空slice
    fmt.Println(cookies)
}

        如果我们请求的API需要权限认证,那么用以上方法就无法通过权限认证了,下面一种方法可以让我们自定义请求头,从而将权限认证信息放入请求头,以便通过权限认证。

func post(url string) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		log.Error("request failed,err:%v\n", err)
		return
	}
	req.SetBasicAuth("lhq", "123456") // 设置权限认证的账户信息
	client.Do(req) // 发出请求
}

五、context包的使用

         context包为我们提供了很好的方法去管理并发的goroutine。考虑实际应用场景,一个request请求到达服务器,服务器往往会开启一个goroutine去处理该request,在这个goroutine中,很有可能又会开启多个goroutine去处理像连接数据库,rpc处理等业务,我们如何在任意一个环节出现问题的时候,关闭掉由这个request所启动的所有goroutine,以防止出现goroutine泄漏呢,context包为我们提供了思路。下面将介绍context包最常见的四个函数。

5.1.WithCancel()

func f1(ctx context.Context) chan int {
	ret := make(chan int)
	n := 0
	go func() { // 在一个goroutine中启动另外的goroutine,一定要防止该goroutine泄漏
		for {
			time.Sleep(time.Millisecond * 10)
			n = n + 1
			select {
			case ret <- n:
			case <-ctx.Done(): // ctx.Done()返回一个通道,一旦有goroutine调用了cancel()函数,该通道就会有值
				close(ret)
				return
			default:
			}

		}
	}()
	return ret
}

func main(){
/*----------func WithCancel(parent Context) (ctx Context, cancel CancelFunc)----------
	该函数返回一个context以及一个cancel函数
	cancel函数用于往ctx.Done()的通道中写入值
	一旦父context被关闭,所有子goroutine都会被关闭
	*/
	
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	for n := range f1(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}
}

5.2.WithDeadLine()

func f2(ctx context.Context, done chan struct{}) {
	for {
		select {
		case <-time.After(time.Millisecond * 10):
			fmt.Println("10 millsec")
		case <-ctx.Done():
			fmt.Println(ctx.Err())
			done <- struct{}{}
			return
		}
	}
}

func main(){
/*-----func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
	为context设置deadline,一旦达到deadline,ctx.Done()的通道会被赋值
*/
	done := make(chan struct{})
	date := time.Now().Add(50 * time.Millisecond)
	ctx, cancel := context.WithDeadline(context.Background(), date)
	defer cancel()
	go f2(ctx, done)
	<-done
	/*输出
	10 millsec
	10 millsec
	10 millsec
	10 millsec
	context deadline exceeded
	*/
}

5.3.WithTimeOut()

func f3(ctx context.Context, done chan struct{}) {
	for {
		fmt.Println("db connecting")
		time.Sleep(time.Millisecond * 10) // 假设数据库连接一次要10ms
		select {
		case <-ctx.Done():
			done <- struct{}{}
			return
		default:
		}
	}
}

func main(){
/*
		func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
		设置ctx的超时时间,一旦超时则关闭ctx
	*/
	done := make(chan struct{})
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
	defer cancel()
	go f3(ctx, done)
	<-done
	/*输出
	db connecting
	db connecting
	db connecting
	db connecting
	db connecting
	*/
}

5.4.WithValue() 

type MyCode string

func f4(ctx context.Context, done chan struct{}) {
	key := MyCode("who")
	value, ok := ctx.Value(key).(string) //返回值是interface,必须进行类型断言
	if !ok {
		fmt.Println("invalid code")
		return
	}
	for {
		fmt.Println(value)
		time.Sleep(time.Millisecond * 10)
		select {
		case <-ctx.Done():
			done <- struct{}{}
			return
		default:
		}
	}
}

func main(){
/*
		func WithValue(parent Context, key, val interface{}) Context
		该方法只用于对API和进程间传递数据,而并非用于给函数传参
		key值必须可比较,且不能是string或任何内置类型,最好的使用方法是使用type起别名
	*/
	done := make(chan struct{})
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
	ctx = context.WithValue(ctx, MyCode("who"), "lhq")
	defer cancel()
	go f4(ctx, done)
	<-done
    /*输出
        lhq
        lhq
        lhq
        lhq    
        lhq
    */
}



总结

        标准库中有许多实用的方法,有需要可以自行到GO语言标准库中文文档学习。

你可能感兴趣的:(GoLang进阶,golang,开发语言,后端)