• Go笔记


    变量声明

    • go语言中局部变量声明后必须被使用,否则报错。全局变量无此限制
    • 整数、浮点数、string这些基本类型为值类型,赋值后变量名指向各自的值;而复杂类型为引用类型,两变量赋值后指向同一内存区域
    • 变量声明时若不赋值则为系统默认值(数值类型0, 布尔false, string为"",其他为nil)
    // 一般变量声明的3种方法
    
    // 第一种: var 变量名 类型
    var i int
    
    // 第二种(省略类型,由初值自动判断): var 变量名
    var i = 5
    
    //第三种(省略类型和var关键字,但只能用于函数体内),如:
    str := "string" // := 声明符前面必须有一个变量之前未被声明,而不能单纯用来给已声明变量赋值,否则报错
    
    // 多变量声明
    var i, j = 5, 7 // 三种方法均可
    i, j = j, i // swap values
    
    var (  // 这种因式分解关键字的写法一般用于声明全局变量
        a int
        b bool
    )
    
    // 空白标识符_(实际上无法访问值,用于丢弃函数的某个返回值)
    _, d = foo()
    

    常量声明:

    // 格式:const identifier [type] = value
    const f, s = 5.5, "hello"
    //const可用于枚举,缺省时将使用上一行的表达式
    // iota为系统特殊变量,每次遇到const时置0, 每定义一个变量值加1
    const(
        a = iota   //0
        b          //1
        c          //2
        d = "ha"   //独立值,iota += 1
        e          //"ha"   iota += 1
        f = 100    //iota +=1
        g          //100  iota +=1
        h = iota   //7,恢复计数
        i          //8
    )
    

    语言结构

    1. 一行只有一条语句时可省略分号
    // 当前程序的包名
    package main
    
    // 导入其他包
    import . "fmt" // 导入所有函数和变量(不建议)
    import _ "fmt" // 仅导入,运行init函数,不导入内部变量和函数
    
    // 常量定义 const identifier [type] = value
    const PI = 3.14
    
    // 全局变量的声明和赋值
    var name = "gopher"
    
    // 一般类型声明
    type newType int
    
    // 结构的声明
    type gopher struct{}
    
    // 接口的声明
    type golang interface{}
    
    // 由main函数作为程序入口点启动
    func main() {
        Println("Hello World!")
    }
    

    运算符

    1. 算术:+ - * / % ++(自增) --(自减)
    2. 关系:> >= < <= == !=
    3. 逻辑:&& || !
    4. 位:& | ^ << >>^ 做一元运算符为位取反,二元为异或)
    5. 赋值运算符 := 以及算术、位运算符结合=的赋值运算符
    6. 其他:&(取地址运算符)、* (取对应地址值)
    优先级 运算符
    5 * / % << >> & &^
    4 + - | ^
    3 == != < <= > >=
    2 &&
    1 ||

    数组

    1. 声明和初始化

      var array [5]int // 未初始化默认零值
      array := [...]int{1, 2, 3} // “...” 自动确定大小
      array := [5]int{1: 1, 3: 5} // 初始化arr[1] = 1, arr[3] = 5
      array := [5]*int{1: new(int), 3: new(int)}
      
    2. 数组采用值传递,需满足大小和类型相同才能赋值。若需要在函数中修改,用数组指针传参

      func main() {
          array := [5]int{1, 2, 3}
          modify(&array)
      }
      
      func modify(arr *[5]int) { // 注意区别于指针数组类型 [5]*int
          arr[1] = 5
      }
      

    切片

    1. 定义&初始化

      // 定义
      slice := make([]int, 5) // len = cap = 5
      slice := make([]int, 5, 10) // len = 5, cap = 10
      slice := []int{1:3, 3:5}
      
      // 基于现有数组或切片创建,指向原有(底层)数组,相当于引用赋值。取值范围[i, j]代表区间[i,j),此时长度为 len-i, 容量为 cap-i。i,j可省略,此时分别代表0,len
      // 还可加第三个参数,即[i:j:k], k代表切片容量容量右区间(k<=原cap)
      slice1 := slice[:]
      slice2 : slice[:5]
      
      // 切片清空 (比如清空缓存)
      buf = buf[:0]
      
      var slice []int // nil切片,指向底层数组的指针为nil
      slice := []int{} // 空切片,指向底层数组的指针为一个地址
      
    2. 底层采用数组存储,包含3个字段:

      • 指向底层数组的指针
      • 切片长度
      • 切片容量(仅用于扩展容量,超出长度的部分不能用于索引)
    3. 可通过内置函数 len()cap() 获取切片长度和容量

    4. 赋值为引用传递,此时对切片元素的修改会影响原切片。可用内置的append()对赋值的变量追加元素,若此时底层数组容量足够,则直接对元素进行覆盖(但原切片len和cap不变)。否则新建一个底层数组.

    5. 数组和切片迭代

      for i := 0; i < len(slice); i++ {}
      for idx, val := range slice {} // idx 可改成用下划线“_”省略
      

    Map映射

    1. 定义&初始化

      // make 
      dict := make(map[string]int)
      // 字面量初始化
      dict := map[string]int{"张三": 38, "李四": 20}
      
      dict := map[string]int{} // 空map
      
      var dict map[string]int // nil映射,无空间,使用前需用make赋值
      dict = make(map[string]int)
      dict["张三"] = 38
      
    2. 取值 & 删除

      age := dict["张三"] // 若键不存在,返回零值
      age, exists := dict["张三"] // exists 布尔类型,表示值是否存在
      delete(dict, "张三") // 若map中存在对应key,删除之
      
    3. 赋值采用引用传递

    4. Map存储的是无序键值对的组合,遍历结果不确定。需要有序结果的话需要先排序

      // 遍历方式
      for key := range dict {}
      for key, val := range dict {}
      
      var names []string
      for name := range dict {
          names = append(names, name)
      }
      sort.Strings(names) // 排序
      
      for _, key := range names {
          fmt.Println(key, dict[key])
      }
      
    5. map的键类型可以是任意值类型,内置 or 结构类型,必须能用 == 比较。而map、slice、函数、及含有切片的结构类型等引用类型则不能作为键

    结构类型

    1. 基础类型:包括整型、浮点型、字符型、布尔型, 赋值采用值传递。对它们的操作一般返回一个新创建的值,所以是线程安全的。

    2. 引用类型:包括map、slice、chan、函数类型、接口类型,采用引用传递,本质是传递了底层的指针值(注意slice结构中还包含了len和cap两个基本类型,不会在函数中被修改)

    3. 结构类型

      • 采用值传递,但结构体内的引用类型采用引用传递(本质是指针的值传递)
      • 若要修改结构体值,应传递指针
      type person struct{
          name string
          age int
      }
      
      var p person // 声明并初始化变量,默认零值
      p := person{"Jack", 5} // 初始化顺序和声明的相同
      p := person{age: 5, name: "Jack"} // 不按顺序
      
      // 在函数中修改值
      func main() {
      	jim := person{"Jim",10}
      	fmt.Println(jim)
      	modify(&jim)
      	fmt.Println(jim)
      }
      
      func modify(p *person) {
      	p.age =p.age+10  // golang中一律用句号“.”访问成员
      }
      
    4. 自定义类型:定义结构体 或 基于已有类型

      • Go是强类型语言。即使底层类型相同,如果类型名不同也不能相互赋值

        type Duration int64
        
        var dur Duration
        dur = int64(100) // Error
        fmt.Println(dur)
        
      • 基于已有类型重新定义类型的好处

        • 添加方法
        • 明确业务含义

    函数&方法

    1. 函数

      • 函数定义中没有接收者
    2. 方法

      • 方法定义中有接收者

        type person struct{
            name string
            age int
        }
        
        func (p person) String() {} // 值类型接收者
        func (p *person) modify() {} // 指针型接收者
        
        // 调用时不必严格采用对应的值或指针的类型,编译器或自动进行类型转换
        // 比如var mu sync.Mutex调用mu.Lock()函数
        var p person
        p.modify() 
        (&p).modify() // 两种均可
        
    3. 大小写

      • 函数、方法、变量名、类型的首字母若大写,则可在包外使用,相当于java的public关键字
    4. 多值返回

      func add(a, b int) (int, error) {
      	return a + b, nil // 返回顺序与声明顺序一致
      }
      
      func main() {
          sum, err := add(1, 2)
      	if err != nil {
      		log.Fatal(err)
      		return
      	}
      	fmt.Println(sum)
      }
      
    5. 可变参数

      • 函数入参表类型前加 ... ,必须是最后一个入参。 函数内该参数相当于一个数组
      func main() {
      	print("1","2","3")
      }
      
      func print (a ...interface{}){
      	for _,v:=range a{
      		fmt.Print(v)
      	}
      	fmt.Println()
      }
      

    接口

    1. 接口是抽象的,仅包含一组接口方法。具体实现由用户实现

    2. 如果用户定义的类型(定义为 实体类型),实现了接口类型声明的所有方法,那么这个用户定义的类型就实现了这个接口

    3. 多态:实体类型对象可以赋值给对应的接口类型对象。那么在调用接口类型对象的方法时,将转化成对具体实体类型方法的调用

    4. 接口类型被赋值后,包含两个指针。第一个指针指向存储的实体类型的信息和相关联的方法集,第二个指针指向存储的实体类型的值

    5. 方法集

      • 值接收 的 方法,实体类型的值和指针都可以实现对应的接口(指针传递不会被改变值);而若采用 指针接收, 只有实体类型的指针能够实现对应的接口

        func main() {
        	var c cat
        	//值作为参数传递
        	invoke(&c) // 虽然以指针传递,但不会在方法中改变值
        }
        //需要一个animal接口作为参数
        func invoke(a animal){
        	a.printInfo()
        }
        
        type animal interface {
        	printInfo()
        }
        
        type cat int
        
        //值接收者实现animal接口
        func (c cat) printInfo(){
        	fmt.Println("a cat")
        }
        
      • Methods Receivers Values
        (t T) T and *T
        (t *T) *T

    嵌入类型

    1. 示例

      func main() {
      	ad := admin{user{"张三","zhangsan@flysnow.org"},"管理员"} // 必须按定义的方式初始化
      	ad.user.sayHello() // 被覆盖的方法依然存在
      	ad.sayHello()
          invoke(ad)
          fmt.Println(ad.name) // user成员同时变成admin成员
      }
      
      type user struct {
      	name string
      	email string
      
      }
      
      type admin struct {
      	user // 将已声明的结构体嵌入
      	level string
      }
      
      func (u user) sayHello(){
      	fmt.Println("Hello,i am a user")
      }
      
      func (a admin) sayHello(){ // 方法重写override
      	fmt.Println("Hello,i am a admin")
      }
      
      type Hello interface {
          hi()
      }
      
      func (u user) hi() { // user实现了Hello接口,则admin也实现了该接口
          fmt.Println("Hi. I'm a user.")
      }
      
      func invoke(person Hello) {
          person.hi()
      }
      
    2. Go语言中没有继承的概念。Go提倡的代码组合方式是组合。嵌入的为内部类型,包含的为外部类型

    3. 性质

      • 嵌入后,内部类型的成员便也成为外部类型的成员
      • 在外部类型中可以对内部类型的方法进行重写。但内部类型中的方法依然存在,可以通过内部类型去调用
      • 如果内部实现了某个接口,则外部类型也实现了该接口

    标识符可见性

    1. 示例

      package common
      
      import "fmt"
      
      func NewLoginer() Loginer{ // 内部设计改变时,不影响用户调用的接口
      	return defaultLogin(0) 
      }
      
      type Loginer interface {
      	Login()
      }
      
      type defaultLogin int
      
      func (d defaultLogin) Login(){
      	fmt.Println("login in...")
      }
      
      
      // -------------- main包 -----------//
      package main
      
      func main() {
      	l:=common.NewLoginer()
      	l.Login()
      }
      
    2. 范围:变量、函数、类型、成员(变量/函数)

    3. 通过首字母大小写定义可见性,大写exported,小写unexported

    4. .操作符前面的部分导出了,.操作符后面的部分才有可能被访问;如果.前面的部分都没有导出,那么即使.后面的部分是导出的,也无法访问

      例子 可否访问
      Admin.User.Name
      Admin.User.name
      Admin.user.Name
      Admin.user.name

    Goroutine

    1. 基本概念

      概念 说明
      进程 一个程序对应的一个独立程序空间
      线程 一个执行空间,一个进程可以有多个线程
      逻辑处理器 执行创建的goroutine,绑定一个线程
      调度器 Go运行时中的,分配goroutine给不同的逻辑处理器
      全局运行队列 所有刚创建的goroutine都会放到这里
      本地运行队列 逻辑处理器的goroutine队列
    2. go语言中并发指的是让某个函数独立于其他函数运行的能力,一个goroutine就是一个独立的工作单元,Go的runtime(运行时)会在逻辑处理器上调度这些goroutine来运行,一个逻辑处理器绑定一个操作系统线程,所以说goroutine不是线程,它是一个协程,也是这个原因,它是由Go语言运行时本身的算法实现的

    3. 当我们创建一个goroutine的后,会先存放在全局运行队列中,等待Go运行时的调度器进行调度,把他们分配给其中的一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中,最终等着被逻辑处理器执行即可

    4. 设置逻辑处理器个数等于物理核数

      runtime.GOMAXPROCS(runtime.NumCPU())
      

    资源竞争 (data race)

    1. 当两个或多个goroutine在没有相互同步的情况下访问某个共享的资源时,就会出现资源竞争(Data Race)

      var (
      	count int32
      	wg    sync.WaitGroup
      )
      
      func main() {
      	wg.Add(2)
      	go incCount()
      	go incCount()
      	wg.Wait()
      	fmt.Println(count) // 结果不确定
      }
      
      func incCount() {
      	defer wg.Done()
      	for i := 0; i < 2; i++ {
      		value := count
      		runtime.Gosched()
      		value++
      		count = value
      	}
      }
      
    2. 检查data race的方式

      go run -race main.go
      go build -race // then run the binary generated
      
    3. 避免并发的方式

      // import "sync.atomic"
      atomic.AddInt32(&count, 1)
      
      // import "sync" 
      var mu sync.Mutex // 互斥锁
      mu.Lock()
      mu.Unlock()
      
      // 使用channel
      

    通道Channel

    1. 定义 & 基本使用

      // 双向通道
      ch := make(chan int) // 无缓冲通道(同步通道)
      ch := make(chan int, 5) // 缓冲通道,缓冲容量为5
      var ch chan int // nil通道
      
      // 单向通道,仅向通道发送 chan<- int, 仅从通道接收 <-chan int。虽然也能用make新建,但是一个不能填充数据(发送)只能读取的通道是毫无意义的
      var ch2 <-chan int
      var ch2 chan<- int
      ch2 = ch // 将双向通道赋值给单向通道
      ch <- 5
      <- ch2
      
      // 获取通道容量和通道内的元素个数
      n := len(ch)
      m := cap(ch)
      
      // 关闭通道,此时往channel发送会panic,从channel接收返回零值
      close(ch)
      val, ok := <-ch // ok 表示channel是否已关闭
      
    2. 无缓冲通道要求发送和接收端都需要做好准备,否则一方将阻塞,等待另一方处理,所以又称同步通道。缓冲通道发送后并不要求立马接收

    3. 只有双向通道才能关闭,且只能关闭一次。重复关闭channel或往关闭了的channel发送数据会panic

    并发示例

    1. 并发示例Runner

      package common
      
      import (
      	"errors"
      	"os"
      	"os/signal"
      	"time"
      )
      
      var ErrTimeOut = errors.New("执行者执行超时")
      var ErrInterrupt = errors.New("执行者被中断")
      
      //一个执行者,可以执行任何任务,但是这些任务是限制完成的,
      //该执行者可以通过发送终止信号终止它
      type Runner struct {
      	tasks     []func(int)      //要执行的任务
      	complete  chan error       //用于通知任务全部完成
      	timeout   <-chan time.Time //这些任务在多久内完成
      	interrupt chan os.Signal   //可以控制强制终止的信号
      
      }
      
      func New(tm time.Duration) *Runner {
      	return &Runner{
      		complete:  make(chan error),
      		timeout:   time.After(tm),
      		interrupt: make(chan os.Signal, 1),
      	}
      }
      
      //将需要执行的任务,添加到Runner里
      func (r *Runner) Add(tasks ...func(int)) {
      	r.tasks = append(r.tasks, tasks...)
      }
      
      //执行任务,执行的过程中接收到中断信号时,返回中断错误
      //如果任务全部执行完,还没有接收到中断信号,则返回nil
      func (r *Runner) run() error {
      	for id, task := range r.tasks {
      		if r.isInterrupt() {
      			return ErrInterrupt
      		}
      		task(id)
      	}
      	return nil
      }
      
      //检查是否接收到了中断信号
      func (r *Runner) isInterrupt() bool {
      	select {
      	case <-r.interrupt:
      		signal.Stop(r.interrupt)
      		return true
      	default:
      		return false
      	}
      }
      
      //开始执行所有任务,并且监视通道事件
      func (r *Runner) Start() error {
      	//希望接收哪些系统信号
      	signal.Notify(r.interrupt, os.Interrupt)
      
      	go func() {
      		r.complete <- r.run()
      	}()
      
      	select {
      	case err := <-r.complete:
      		return err
      	case <-r.timeout:
      		return ErrTimeOut
      	}
      }
      
      package main
      
      import (
      	"flysnow.org/hello/common"
      	"log"
      	"time"
      	"os"
      )
      
      func main() {
      	log.Println("...开始执行任务...")
      
      	timeout := 3 * time.Second
      	r := common.New(timeout)
      
      	r.Add(createTask(), createTask(), createTask())
      
      	if err := r.Start(); err != nil{
      		switch err {
      		case common.ErrTimeOut:
      			log.Println(err)
      			os.Exit(1)
      		case common.ErrInterrupt:
      			log.Println(err)
      			os.Exit(2)
      		}
      	}
      	log.Println("...任务执行结束...")
      }
      
      func createTask() func(int) {
      	return func(id int) {
      		log.Printf("正在执行任务%d", id)
      		time.Sleep(time.Duration(id)* time.Second)
      	}
      }
      
    2. 资源共享池 -> Pool

      package common
      
      import (
      	"errors"
      	"io"
      	"sync"
      	"log"
      )
      
      //一个安全的资源池,被管理的资源必须都实现io.Close接口
      type Pool struct {
      	m       sync.Mutex
      	res     chan io.Closer
      	factory func() (io.Closer, error)
      	closed  bool
      }
      
      var ErrPoolClosed = errors.New("资源池已经被关闭。")
      
      //创建一个资源池
      func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
      	if size <= 0 {
      		return nil, errors.New("size的值太小了。")
      	}
          
      	return &Pool{
      		factory: fn, 
      		res:     make(chan io.Closer, size)
      	}, nil
      }
      
      //从资源池里获取一个资源
      func (p *Pool) Acquire() (io.Closer,error) {
      	select {
      	case r,ok := <-p.res:
      		log.Println("Acquire:共享资源")
      		if !ok {
      			return nil,ErrPoolClosed
      		}
      		return r,nil
      	default:
      		log.Println("Acquire:新生成资源")
      		return p.factory()
      	}
      }
      
      //关闭资源池,释放资源
      func (p *Pool) Close() {
      	p.m.Lock()
      	defer p.m.Unlock()
      
      	if p.closed {
      		return
      	}
      
      	p.closed = true
      
      	//关闭通道,不让写入了
      	close(p.res)
      
      	//关闭通道里的资源
      	for r:=range p.res {
      		r.Close()
      	}
      }
      
      func (p *Pool) Release(r io.Closer){
      	//保证该操作和Close方法的操作是安全的
      	p.m.Lock()
      	defer p.m.Unlock()
      
      	//资源池都关闭了,就剩这一个没有释放的资源了,释放即可
      	if p.closed {
      		r.Close()
      		return 
      	}
      
      	select {
      	case p.res <- r:
      		log.Println("资源释放到池子里了")
      	default:
      		log.Println("资源池满了,释放这个资源吧")
      		r.Close()
      	}
      }
      
      package main
      
      import (
      	"flysnow.org/hello/common"
      	"io"
      	"log"
      	"math/rand"
      	"sync"
      	"sync/atomic"
      	"time"
      )
      
      const (
      	//模拟的最大goroutine
      	maxGoroutine = 5
      	//资源池的大小
      	poolRes      = 2
      )
      
      func main() {
      	//等待任务完成
      	var wg sync.WaitGroup
      	wg.Add(maxGoroutine)
      
      	p, err := common.New(createConnection, poolRes)
      	if err != nil {
      		log.Println(err)
      		return
      	}
      	//模拟好几个goroutine同时使用资源池查询数据
      	for query := 0; query < maxGoroutine; query++ {
      		go func(q int) {
      			dbQuery(q, p)
      			wg.Done()
      		}(query)
      	}
      
      	wg.Wait()
      	log.Println("开始关闭资源池")
      	p.Close()
      }
      
      //模拟数据库查询
      func dbQuery(query int, pool *common.Pool) {
      	conn, err := pool.Acquire()
      	if err != nil {
      		log.Println(err)
      		return
      	}
      
      	defer pool.Release(conn)
      
      	//模拟查询
      	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
      	log.Printf("第%d个查询,使用的是ID为%d的数据库连接", query, conn.(*dbConnection).ID)
      }
      //数据库连接
      type dbConnection struct {
      	ID int32//连接的标志
      }
      
      //实现io.Closer接口
      func (db *dbConnection) Close() error {
      	log.Println("关闭连接", db.ID)
      	return nil
      }
      
      var idCounter int32
      
      //生成数据库连接的方法,以供资源池使用
      func createConnection() (io.Closer, error) {
      	//并发安全,给数据库连接生成唯一标志
      	id := atomic.AddInt32(&idCounter, 1)
      	return &dbConnection{id}, nil
      }
      
      
    3. 补充:原生资源池 sync.Pool

      • 资源池大小默认无上限
      • 缓存的对象是临时的,在下一次GC时将会被清除
      • Get() 从资源池获取资源,Put() 放入资源池,返回任意对象 interface{}
      package main
      
      import (
      	"log"
      	"math/rand"
      	"sync"
      	"sync/atomic"
      	"time"
      )
      
      const (
      	//模拟的最大goroutine
      	maxGoroutine = 5
      )
      
      func main() {
      	//等待任务完成
      	var wg sync.WaitGroup
      	wg.Add(maxGoroutine)
      
      	p:=&sync.Pool{  // 通过字面量声明
      		New: createConnection,
      	}
      
      	//模拟好几个goroutine同时使用资源池查询数据
      	for query := 0; query < maxGoroutine; query++ {
      		go func(q int) {
      			dbQuery(q, p)
      			wg.Done()
      		}(query)
      	}
      
      	wg.Wait()
      }
      
      //模拟数据库查询
      func dbQuery(query int, pool *sync.Pool) {
      	conn:=pool.Get().(*dbConnection)
      
      	defer pool.Put(conn)  
      
      	//模拟查询
      	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
      	log.Printf("第%d个查询,使用的是ID为%d的数据库连接", query, conn.ID)
      }
      //数据库连接
      type dbConnection struct {
      	ID int32//连接的标志
      }
      
      //实现io.Closer接口
      func (db *dbConnection) Close() error {
      	log.Println("关闭连接", db.ID)
      	return nil
      }
      
      var idCounter int32
      
      //生成数据库连接的方法,以供资源池使用
      func createConnection() interface{} {
      	//并发安全,给数据库连接生成唯一标志
      	id := atomic.AddInt32(&idCounter, 1)
      	return &dbConnection{ID:id}
      }
      

    读写锁

    1. 原因:读 - 读不互斥,读 - 写、写 - 写操作才互斥。直接采用sync.Mutex效率较低

    2. 示例

      var mu sync.RWMutex
      // 读锁
      mu.RLock()
      mu.RUnlock()
      // 写锁
      mu.Lock()
      mu.Unlock()
      
    3. 示例 SynchronizedMap

      package common
      
      import (
      	"sync"
      )
      //安全的Map
      type SynchronizedMap struct {
      	rw *sync.RWMutex
      	data map[interface{}]interface{}
      }
      //存储操作
      func (sm *SynchronizedMap) Put(k,v interface{}){
      	sm.rw.Lock()
      	defer sm.rw.Unlock()
      
      	sm.data[k]=v
      }
      //获取操作
      func (sm *SynchronizedMap) Get(k interface{}) interface{}{
      	sm.rw.RLock()
      	defer sm.rw.RUnlock()
      
      	return sm.data[k]
      }
      
      //删除操作
      func (sm *SynchronizedMap) Delete(k interface{}) {
      	sm.rw.Lock()
      	defer sm.rw.Unlock()
      
      	delete(sm.data,k)
      }
      
      //遍历Map,并且把遍历的值给回调函数,可以让调用者控制做任何事情
      func (sm *SynchronizedMap) Each(cb func (interface{},interface{})){
      	sm.rw.RLock()
      	defer sm.rw.RUnlock()
      
      	for k, v := range sm.data {
      		cb(k,v)
      	}
      }
      
      //生成初始化一个SynchronizedMap
      func NewSynchronizedMap() *SynchronizedMap{
      	return &SynchronizedMap{
      		rw:new(sync.RWMutex),
      		data:make(map[interface{}]interface{}),
      	}
      }
      

    断言 Type Assertion

    1. 仅针对 interface{} 类型。常见的如将输入传入 func foo(interface{}) , 参数自动转为 interface{} 类型

    2. 示例

      // 直接断言使用
      var a interface{}
      val := a.(string) // 如果断言失败将panic
      val, ok := a.(string) // 断言失败不panic, 但ok为false
      
      // switch断言
      switch val := a.(type) {
      default:
          fmt.Printf("unexpected type %T", t)       // %T prints whatever type t has
      case bool:
          fmt.Printf("boolean %t\n", t)             // t has type bool
      case *int:
          fmt.Printf("pointer to integer %d\n", *t) // t has type *int
      }
      
    3. 转换类型的时候如果是string可以不用断言,使用fmt.Sprint()函数可以达到想要的效果

    Log日志输出

    1. 示例

      // import "log"
      type Logger struct {
      	mu     sync.Mutex // ensures atomic writes; protects the following fields
      	prefix string     // prefix to write at beginning of each line
      	flag   int        // properties
      	out    io.Writer  // destination for output
      	buf    []byte     // for accumulating text to write
      }
      
      func New(out io.Writer, prefix string, flag int) *Logger { // 定义新的Logger
      	return &Logger{out: out, prefix: prefix, flag: flag}
      }
      
      var std = New(os.Stderr, "", LstdFlags)
      
      log.SetPrefix("[UserCenter]") // 设置前缀
      log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) // 设置日志开头信息
      log.SetOutput(os.Stdout)
      log.Println(str1, str2)
      // output: [UserCenter]2017/04/29 05:53:26 main.go:23: <str1> <str2>
      
      // flag类型
      const (
      	Ldate         = 1 << iota     //日期示例: 2009/01/23
      	Ltime                         //时间示例: 01:23:23
      	Lmicroseconds                 //毫秒示例: 01:23:23.123123. 自动替换Ltime
      	Llongfile                     //绝对路径和行号: /a/b/c/d.go:23
      	Lshortfile                    //文件和行号: d.go:23. 自动替换Llongfile
      	LUTC                          //日期时间转为0时区的
      	LstdFlags     = Ldate | Ltime //Go提供的标准抬头信息
      )
      
      
    2. Fatal 系列在Print系列之后调用os.Exit(1)退出; Panic 系列在调用 Print 系列函数后,调用panic函数退出并打印调用栈

      func Println(v ...interface{}) {
      	std.Output(2, fmt.Sprintln(v...))
      }
      
      func Fatalln(v ...interface{}) {
      	std.Output(2, fmt.Sprintln(v...))
      	os.Exit(1)
      }
      
      func Panicln(v ...interface{}) {
      	s := fmt.Sprintln(v...)
      	std.Output(2, s)
      	panic(s)
      }
      
    3. 分级调用示例代码 → 比较麻烦,可以考虑第三方log库,或自定义包装(根据级别调取相应的Logger)

      var (
      	Info *log.Logger
      	Warning *log.Logger
      	Error * log.Logger
      )
      
      func init(){
      	errFile,err:=os.OpenFile("errors.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
      	if err!=nil{
      		log.Fatalln("打开日志文件失败:",err)
      	}
      
      	Info = log.New(os.Stdout,"Info:",log.Ldate | log.Ltime | log.Lshortfile)
      	Warning = log.New(os.Stdout,"Warning:",log.Ldate | log.Ltime | log.Lshortfile)
      	Error = log.New(io.MultiWriter(os.Stderr,errFile),"Error:",log.Ldate | log.Ltime | log.Lshortfile)
      
      }
      
      func main() {
      	Info.Println("飞雪无情的博客:","http://www.flysnow.org")
      	Warning.Printf("飞雪无情的微信公众号:%s\n","flysnow_org")
      	Error.Println("欢迎关注留言")
      }
      
    4. log获取文件信息的主要函数 runtime.Caller, 参数skip表示跳过栈帧数,0表示不跳过,也就是runtime.Caller的调用者。1的话就是再向上一层,表示调用者的调用者

      func Caller(skip int) (pc uintptr, file string, line int, ok bool)
      

    io.Writer 和 io.Reader 接口

    1. 接口定义

      • 把数据的输入和输出,抽象为流的读写,所以只要实现了这两个接口,都可以使用流的读写功能
      // Write() 向底层数据流写入len(p)字节的数据,返回写入的字节长度n(0 <= n <= len(p))
      // 如果n < len(p)或中途出错,err不为nil
      // 过程中不能修改切片p及其内容
      type Writer interface {
      	Write(p []byte) (n int, err error)
      }
      
      // Read() 从底层最多读取len(p)字节的数据到切片p,返回读取的字节数n(0 <= n <= len(p))
      // 读取出错时,返回读取的字节数,且err不等于nil
      // 输入流结束时,返回n>0的字节,err可以为nil或EOF;但再次调用,肯定会返回0,EOF
      // 调用Read方法时,如果n>0时,优先处理处理读入的数据,然后再处理错误err,EOF也要这样处理
      // Read方法不建议返回n=0且err=nil的情况(err等于nil不代表EOF)
      type Reader interface {
      	Read(p []byte) (n int, err error)
      }
      

    Context

    1. 场景

      • 等待goroutine自己结束 → sync.WaitGroup

        func main() {
        	var wg sync.WaitGroup
        
        	wg.Add(2)
        	go func() {
        		time.Sleep(2*time.Second)
        		fmt.Println("1号完成")
        		wg.Done()
        	}()
        	go func() {
        		time.Sleep(2*time.Second)
        		fmt.Println("2号完成")
        		wg.Done()
        	}()
        	wg.Wait()
        	fmt.Println("好了,大家都干完了,放工")
        }
        
      • goroutine不会自己结束,需通知goroutine结束 → chan + select

        func main() {
        	stop := make(chan bool)
        
        	go func() {
        		for {
        			select {
        			case <-stop:
        				fmt.Println("监控退出,停止了...")
        				return
        			default:
        				fmt.Println("goroutine监控中...")
        				time.Sleep(2 * time.Second)
        			}
        		}
        	}()
        
        	time.Sleep(10 * time.Second)
        	fmt.Println("可以了,通知监控停止")
        	stop<- true
        	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
        	time.Sleep(5 * time.Second)
        
        }
        
      • 多个goroutine需要控制结束 → Context

        func main() {
        	ctx, cancel := context.WithCancel(context.Background())
        	go func(ctx context.Context) {
        		for {
        			select {
        			case <-ctx.Done():
        				fmt.Println("监控退出,停止了...")
        				return
        			default:
        				fmt.Println("goroutine监控中...")
        				time.Sleep(2 * time.Second)
        			}
        		}
        	}(ctx)
        
        	time.Sleep(10 * time.Second)
        	fmt.Println("可以了,通知监控停止")
        	cancel()
        	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
        	time.Sleep(5 * time.Second)
        
        }
        
    2. 接口定义

      type Context interface {
      	Deadline() (deadline time.Time, ok bool)  // 获取截止时间
      	Done() <-chan struct{}  // 取消channel如果可读,则说明发起了取消请求
      	Err() error
      	Value(key interface{}) interface{}  // 获取保存的Key-Value中的Value
      }
      
    3. 基本的两个空Context:context.Background() (主要用于main函数、初始化及测试代码)和 context.TODO() 。 二者不可取消、无deadline、无key-value

    4. context的继承衍生

      func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
      func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
      func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
      func WithValue(parent Context, key, val interface{}) Context // 不返回取消函数
      
    5. 使用原则

      • 显式使用context,而不是放在struct里
      • 向函数传递context时,应作为第一个参数,且不应传递nil context(可以传递context.TODO().
      • context的Value仅用于传递request-scoped data(进程、API等),而不是传递非必要数据
      • context是并发安全的,可以同时传递给多个goroutine

    单元测试

    基本测试
    1. 示例

      // main.go
      func Add(a, b int) int {
          return a + b
      } 
      
      // main_test.go
      func TestAdd(t *testing.T) {
          sum := Add(1, 2)
          if sum == 3 {
              t.Log("the result is ok")
          } else {
              t.Fatal("the result is wrong")
          }
      }
      
    2. 基本要求

      • 含有单元测试代码的go文件必须以_test.go结尾,Go语言测试工具只认符合这个规则的文件
      • 单元测试文件名_test.go前面的部分最好是被测试的方法所在go文件的文件名,比如例子中是main_test.go,因为测试的Add函数,在main.go文件里
      • 单元测试的函数名必须以Test开头,是可导出公开的函数
      • 测试函数的签名必须接收一个指向testing.T类型的指针,并且不能返回任何值
      • 函数名最好是Test+要测试的方法函数名,比如例子中是TestAdd,表示测试的是Add这个这个函数
    表组测试

    对多组样例进行测试

    func TestAdd(t *testing.T) {
    	sum := Add(1,2)
    	if sum == 3 {
    		t.Log("the result is ok")
    	} else {
    		t.Fatal("the result is wrong")
    	}
    	
    	sum = Add(3,4)
    	if sum == 7 {
    		t.Log("the result is ok")
    	} else {
    		t.Fatal("the result is wrong")
    	}
    }
    
    模拟调用 - 网络
    1. 单元测试的原则,就是所测方法不要受到所依赖环境的影响。所以对于联网等场景,需要进行模拟调用

    2. 标准库中提供了httptest包用于模拟网络调用

    3. 方式一: 通过httptest.NewRecoder()创建一个 http.ResponseWriter, 模拟真实服务器的响应

      // serve.go
      package common
      
      import (
      	"net/http"
      	"encoding/json"
      )
      
      func Routes(){
      	http.HandleFunc("/sendjson",SendJSON)
      }
      
      func SendJSON(rw http.ResponseWriter,r *http.Request){
      	u := struct {
      		Name string
      	}{
      		Name:"张三",
      	}
      
      	rw.Header().Set("Content-Type","application/json")
      	rw.WriteHeader(http.StatusOK)
      	json.NewEncoder(rw).Encode(u)
      }
      
      // serve_test.go
      func init()  {
      	common.Routes()
      }
      
      func TestSendJSON(t *testing.T){
      	req,err:=http.NewRequest(http.MethodGet,"/sendjson",nil)
      	if err!=nil {
      		t.Fatal("创建Request失败")
      	}
      
      	rw:=httptest.NewRecorder() // 创建一个http.ResponseWriter用于模拟
      	http.DefaultServeMux.ServeHTTP(rw,req) // 触发模拟真实服务器的响应
      
      	log.Println("code:",rw.Code)
      
      	log.Println("body:",rw.Body.String())
      }
      
    4. 方式二:真的在测试机上模拟一个服务器,再调用测试

      func mockServer() *httptest.Server {
      	//API调用处理函数
      	sendJson := func(rw http.ResponseWriter, r *http.Request) {
      		u := struct {
      			Name string
      		}{
      			Name: "张三",
      		}
      
      		rw.Header().Set("Content-Type", "application/json")
      		rw.WriteHeader(http.StatusOK)
      		json.NewEncoder(rw).Encode(u)
      	}
      	//适配器转换
      	return httptest.NewServer(http.HandlerFunc(sendJson))
          //  代码示例中使用了Hander的适配器模式,http.HandlerFunc是一个函数类型,实现了http.Handler 接口,这里是强制类型转换,不是函数的调用
      }
      
      func TestSendJSON(t *testing.T) {
      	//创建一个模拟的服务器
      	server := mockServer() //监听的是本机IP127.0.0.1,端口是随机的
      	defer server.Close()
      	//Get请求发往模拟服务器的地址
      	resq, err := http.Get(server.URL)
      	if err != nil {
      		t.Fatal("创建Get失败")
      	}
      	defer resq.Body.Close()
      
      	log.Println("code:", resq.StatusCode)
      	json, err := ioutil.ReadAll(resq.Body)
      	if err != nil {
      		log.Fatal(err)
      	}
      	log.Printf("body:%s\n", json)
      }
      
    代码覆盖率测试
    1. go test -v -coverprofile=c.out 将测试覆盖率信息输出到 c.out 文件

    2. go tool cover -html=c.out -o=tag.html 生成覆盖率信息文件

    基准测试

    1. 示例

      • 通过 go test -v -bench . -run none -benchtime 3s 进行,-bench . 匹配所有基准测试,-run none 匹配名称为none的单元测试(一般用于过滤),-benchtime 3s 测试时间, -benchmem 显示内存分配次数
      func BenchmarkSprintf(b *testing.B){
      	num:=10
      	b.ResetTimer()
      	for i:=0;i<b.N;i++{
      		fmt.Sprintf("%d",num)
      	}
      }
      
      // 运行Benchmark
      go test -v -bench . -run none -benchtime 3s
      
    2. 要求

      • 基准测试的代码文件必须以_test.go结尾
      • 基准测试的函数必须以Benchmark开头,必须是可导出的
      • 基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数
      • 基准测试函数不能有返回值
      • b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰
      • 最后的for循环很重要,被测试的代码要放到循环里
      • b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能

    反射

    1. 获取值和类型

      • reflect包下有 reflect.Valuereflect.Type 两种类型
      // import "reflect"
      u := User{"zhangsan", 18}
      t := reflect.TypeOf(u)
      v := reflect.ValueOf(v)
      fmt.Println(t, v) // 效果等同于 fmt.Printf("%T %v", u, u)
      
      // 将reflect.Value 重新转为原来的值
      fmt.Println(v.Interface().(User)) 
      
      // 通过reflect.Value中包含了reflect.Type,可通过Type()获取Type
      t1 := v.Type()
      
      // 获取基础类型
      fmt.Println(t.Kind()) 
      
      type User struct {
          name string
          age int
      }
      
      const (
      	Invalid Kind = iota
      	Bool
      	Int
      	Int8
      	Int16
      	Int32
      	Int64
      	Uint
      	Uint8
      	Uint16
      	Uint32
      	Uint64
      	Uintptr
      	Float32
      	Float64
      	Complex64
      	Complex128
      	Array
      	Chan
      	Func
      	Interface
      	Map
      	Ptr
      	Slice
      	String
      	Struct
      	UnsafePointer
      )
      
    2. 遍历字段和方法

      for i := 0; i < t.NumField(); i++ {
          fmt.Println(t.Field(i).Name)
      }
      for i := 0; i < t.NumMethod(); i++ {
          fmt.Println(t.Method(i).Name)
      }
      
    3. 修改字段值

      v := reflect.Value(&u) // 传入指针
      v.Elem() // 获取指针所指的值,类型为Value
      v.Elem().CanSet() // 返回是否能够更改值
      v.Elem().Field(0).SetString("lisi") // 更改字符串值
      v.Elem().Set(User{"lisi", 20}) // 更改结构体值
      
      x := 1 
      v1 := reflect.Value(&x) 
      v1.Elem().SetInt(100) 
      
    4. 动态调用方法 -> 通过反射调用

      func main() {
      	u:=User{"张三",20}
      	v:=reflect.ValueOf(u)
      
      	mPrint:=v.MethodByName("Print") // 获取对应函数类型的Value
          // 可以通过 mPrint.IsValid()验证函数是否可用(即不为zero Value)
      	args:=[]reflect.Value{reflect.ValueOf("前缀")}
      	fmt.Println(mPrint.Call(args)) // 传入[]reflect.Value切片,分别对应函数各个入参
      
      }
      
      type User struct{
      	Name string
      	Age int
      }
      
      func (u User) Print(prfix string){
      	fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
      }
      

    Struct Tag

    1. 示例

      // Key-Value 冒号隔开
      // 不同类型tag之间通过一个空格隔开,其他情况tag内不要有空格
      type User struct {
      	Name string `json:"name,-" yaml:",flow,inline"`
      	Age  int    `json:"age,omitempty" yaml:",omitempty"`
      }
      
      // 通过反射获取Tag
      var u User
      t := reflect.TypeOf(u)
      for i := 0; i < t.NumField(); i++ {
          fmt.Println(t.Field(i).Tag) // 输出该字段整个Tag
          fmt.Println(t.Field(i).Tag.Get("yaml")) // 获取对应类型Tag
      }
      

    unsafe包

    1. 不安全,尽量别用

    2. 示例

      • ArbitraryType 并不是真实存在的类型,只是象征性的
      • unsafe.Sizeof(x) 等价于 reflect.TypeOf(x).Size()
      • unsafe.Alignof(x) 等价于 reflect.TypeOf(x).Align()
      • unsafe.Offsetof(x) 等价于 reflect.TypeOf(x).Field(i).Offset
      func Sizeof(x ArbitraryType) uintptr   // 获取特定类型变量所占空间
      func Alignof(x ArbitraryType) uintptr  // 获取特定类型变量对齐量
      func Offsetof(x ArbitraryType) uintptr // 获取结构体中特定字段相对结构体的偏移量
      
    3. struct 对齐规则

      • 特定类型对齐值 = min(编译器默认对齐值,类型大小Sizeof长度) - 64位默认对齐值为8

      • struct 整体对齐值 = min(默认对齐值,所含字段最大类型长度)

      • 对齐规则

        • 每个字段在内存中偏移值 = 对齐值的倍数
        • 结构体长度 = 结构体对齐值的倍数
      • 示例

        // 对齐值为16,内存分布 bxxx|iiii|jjjj|jjjj
        type user1 struct {
        	b byte
        	i int32
        	j int64
        }
        
        // 对齐值为24,内存分布 bxxx|xxxx|jjjj|jjjj|iiiixxxx (后4位补齐结构体长度)
        type user2 struct {
        	b byte
        	j int64
        	i int32
        }
        
    4. unsafe.Pointer

      • 示例

        func main() {
            x := 5.0
        	fmt.Printf("%d", *(*int64)(unsafe.Pointer(&x)))
            
        	u:=new(user)
        	fmt.Println(*u)
        
        	pName:=(*string)(unsafe.Pointer(u))
        	*pName="张三"
        
        	pAge:=(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(u))+unsafe.Offsetof(u.age)))
        	*pAge = 20
        
        	fmt.Println(*u)
        }
        
        type user struct {
        	name string
        	age int
        }
        
      • 规则

        • 任何指针都可以转换为unsafe.Pointer
        • unsafe.Pointer 可以转换为任何指针
        • uintptr可以转换为unsafe.Pointer
        • unsafe.Pointer可以转换为uintptr
      • 内存偏移涉及到的计算只能通过 uintptr , 所以计算偏移前需要先将 unsafe.Pointer 转为 uintptr

      • 注意uintptr只能出现在表达式中间,不能用于中间变量存储。否则可能因为GC出现异常

    常用包

    随机函数
    import "math/rand"
    rand.Seed(time.Now().UnixNano()) // 随机种子
    rand.Int() // 返回int范围内随机值
    rand.Intn(v) // 返回[0, v) 范围内随机整数
    
    func Shuffle(n int, swap func(i, j int))
    
    数学
    func Max(x, y float64) float64
    func Min(x, y float64) float64
    func Mod(x, y float64) float64
    
    func Floor(x float64) float64
    func Round(x float64) float64
    func Ceil(x float64) float64
    
    func Abs(x float64) float64
    func Pow(x, y float64) float64
    
    func Sqrt(x float64) float64 // 开平方
    func Cbrt(x float64) float64 // 开立方
    
    排序
    sort.Ints(a []int)
    sort.Float64s(a []float64)
    sort.Strings(a []string)
    sort.Slice(slice interface{}, less func(i, j int) bool)
    sort.SliceStable(slice interface{}, less func(i, j int) bool)
    
    字符串
    // import "strings"
    Contains(s, substr string) bool  // 是否包含某个字符串
    ContainsAny(s, chars string) bool // 是否包含任意字符集内的元素
    Split(s, sep string) []string // 将s按分隔符sep分隔为切片,输出可以直接用在for-range结果中
    Join(a []string, sep string) string // 字符串拼接
    
    Trim(s, cutset string) // 去掉首尾存在于cutset字符集的字符(任意多个)
    TrimLeft(s, cutset string)
    TrimRight(s, cutset string)
    TrimPrefix(s, prefix string) // 去掉首部前缀一次
    TrimSuffix(s, suffix string)
    
    ToUpper(s string) string
    ToLower(s string) string
    
    Replace(s, old, new string, n int) string // 替换old为new n次(当n<0,不限次数)
    Repeat(s string, count int) string // 重复s count次
    Index(s, substr string) int // 返回字串开始索引,找不到返回-1
    LastIndex(s, substr string) int
    
    builder := strings.Builder{}
    func (b *Builder) Len() int // 返回底层数组长度
    func (b *Builder) Cap() int // 返回底层数组容量
    func (b *Builder) Grow(n int)  // 腾出n个字节的空闲空间,不够时腾出 2*原cap + n
    func (b *Builder) Reset() // 设置底层切片为nil
    func (b *Builder) String() string  // 转化为字符串
    func (b *Builder) Write(p []byte) (int, error) // 标准实现io.Writer接口
    func (b *Builder) WriteByte(c byte) error
    func (b *Builder) WriteRune(r rune) (int, error)
    func (b *Builder) WriteString(s string) (int, error)
    
    // 字节 & 字符串转换
    s, b := "test", []byte{}
    b = []byte(s)
    s = string(b)
    
    // import "strconv"
    strconv.Atoi(s string) (int, error) // 字符串转整数,注意有两个返回值
    strconv.Itoa(i int) string // 整数转字符串,一个返回值
    
    // 转换成string
    FormatBool(b bool) string
    FormatComplex(c complex128, fmt byte, prec, bitSize int) string // fmt为格式字符,分为 'b e E f g G', prec为小数位数
    FormatFloat(f float64, fmt byte, prec, bitSize int) string
    FormatInt(i int64, base int) string // 转为base进制数
    FormatUint(i uint64, base int) string 
    
    // 解析string,bitSize为适配的bit大小
    ParseBool(str string) (bool, error)
    ParseComplex(s string, bitSize int) (complex128, error)
    ParseFloat(s string, bitSize int) (float64, error)
    ParseInt(s string, base int, bitSize int) (i int64, err error)
    ParseUint(s string, base int, bitSize int) (uint64, error)
    
    // 与Format类型,但加入字节切片
    AppendBool(dst []byte, b bool) []byte
    AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte
    AppendInt(dst []byte, i int64, base int) []byte
    
    字节处理(与strings包基本对应)
    // import "bytes"
    Contains(b, subslice []byte) bool
    ContainsAny(b []byte, chars string) bool
    Equal(a, b []byte) bool
    Join(s [][]byte, sep []byte) []byte
    Split(s, sep []byte) [][]byte
    
    Trim(s []byte, cutset string) []byte
    TrimLeft(s []byte, cutset string) []byte
    TrimRight(s []byte, cutset string) []byte
    TrimPrefix(s, prefix []byte) []byte
    TrimSuffix(s, suffix []byte) []byte
    
    ToLower(s []byte) []byte
    ToUpper(s []byte) []byte
    
    Repeat(b []byte, count int) []byte
    Replace(s, old, new []byte, n int) []byte
    Index(s, sep []byte) int
    LastIndex(s, sep []byte) int
    
    buf := bytes.Buffer{} // 与strings.Builder类似
    func (b *Buffer) Bytes() []byte // 返回底层字节切片
    func (b *Buffer) Len() int
    func (b *Buffer) Cap() int
    func (b *Buffer) Grow(n int)
    func (b *Buffer) Reset()
    func (b *Buffer) String() string
    func (b *Buffer) Write(p []byte) (n int, err error)
    func (b *Buffer) WriteByte(c byte) error
    func (b *Buffer) WriteRune(r rune) (n int, err error)
    func (b *Buffer) WriteString(s string) (n int, err error)
    func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
    
    func (b *Buffer) Next(n int) []byte
    func (b *Buffer) Read(p []byte) (n int, err error)
    func (b *Buffer) ReadByte() (byte, error)
    func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
    func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
    func (b *Buffer) ReadRune() (r rune, size int, err error)
    func (b *Buffer) ReadString(delim byte) (line string, err error)
    func (b *Buffer) Truncate(n int)
    func (b *Buffer) UnreadByte() error
    func (b *Buffer) UnreadRune() error
    
    编解码

    序列化 & 反序列化

    1. 反序列化时查询顺序:对应tag中名称 -> 结构体成员名 -> 忽略大小写进行匹配

    2. JSON结构体tag标注:

      • -:忽略该字段
      • omitempty:若为空,忽略该字段
      • string:将字段值转为JSON字符串
    // import "encoding/json"
    json.Marshal(interface{}) ([]byte, error)
    json.Unmarshal(data []byte, v interface{}) error
    
    // Example
    type Employee struct {
    	FirstName string `json:"firstName"`
    	LastName string `json:"lastName"`
    }
    
    type EmployeeSlice struct {
    	Employees []Employee `json:"employees"`
    }
    
    func main()  {
    	str := `{"employees":[{"firstName":"Bill","lastName":"Gates"},{"firstName":"George","lastName":"Bush"}]}`
    	var res EmployeeSlice
    	json.Unmarshal([]byte(str), &res)
    	fmt.Println(res)
        
    	data := EmployeeSlice{[]Employee{
    		{FirstName:"Bill", LastName:"Gates"},
    		{FirstName:"George", LastName:"Bush"},
    	}}
    	res,_ := json.Marshal(data)
    	fmt.Println(res)
    }
    
    1. 16进制编码
    // import "encoding/hex"
    DecodeString(s string) ([]byte, error)	// 将hex加密字符解码为字节数组
    EncodeToString(src []byte) string		// 将字节数组编码为hex加密字符串
    
    Decode(dst, src []byte) (int, error)	// 将src解码为DecodeLen(len(src))个字节
    Encode(dst, src []byte) int 			// src数据编码为EncodeLen(len(src))个字节
    
    1. 加密(举例)
    import (
    	"crypto/md5"
    	"crypto/sha1"
        "crypto/sha256"
        "encoding/base64"
    )
    
    // md5自行封装
    func md5(str string) string { // sha1、sha256类似
        h := md5.New()
    	io.WriteString(h, str)
    	cipherStr := h.Sum(nil)
    	return hex.EncodeToString(cipherStr)
    }
    
    // base64
    // 编码
    func base64Encode(str []byte) []byte {
    	return []byte(base64.StdEncoding.EncodeToString(str))
    }
    
    // 解码
    func base64Decode(str []byte) ([]byte, error){
    	return base64.StdEncoding.DecodeString(string(str))
    }
    
    时间
    • Go语言里使用Time结构体存储时间信息
    • time.Duration 的本质是int64
    // import "time"
    Now() Time	// 返回当前时间(时区为time.Local)
    (t Time) Unix() int64 // 返回当前本地时间(秒)
    (t Time) UnixNano() int64	// 返回当前本地时间(纳秒)
    
    const {
        NanoSecond	Duration = 1
        MicroSecond	Duration
        MilliSecond	Duration
        Second		Duration
        Minute		Duration
        Hour		Duration	// 最大到小时
    }
    
    time.February	// 常量,二月
    time.Sleep(time.Second)	// 休眠1s
    
    // 解析时间
    Parse(layout, value string) (Time, error) // 根据模板从字符串里解析成时间。layout是固定格式“2006-01-02 15:04:05”, 必须与value格式对应, 解析出来的时区为time.UTC
    ParseInLocation(layout, value string, loc *Location) (Time, error) // 解析到对应的时区
    time.Local, time.UTC  // 本地时区、UTC时区
    
    // 生成时间
    time.Date(year, month, day, min, hour, min, sec, nsec int, loc *Location) Time
    
    // 四舍五入
    (t Time)Round(d Duration) Time 	// 下一个接近的时间点
    (t Time)Truncate(d Duration) Time	// 上一个接近的时间点
    (t Duration)Round(d Duration) Time 	// 下一个接近的时间点
    (t Duration)Truncate(d Duration) Time	// 上一个接近的时间点
    
    
    // 定时器
    time.NewTimer(d Duration) *Timer	// 一次性定时器
    (t *Timer) Reset(d Duration)  // 重置timer到指定间隔(原timer必须过期或stopped)
    (t *Timer) Stop()  // 停止计时器
    time.NewTicker(d Duration) *Timer	// 周期性定时器
    (t *Ticker) Reset(d Duration)
    (t *Ticker) Stop()
    
    网络
    1. URL设置

      // import "net/url"
      // URL结构体
      type URL struct {
              Scheme      string
              Opaque      string    // encoded opaque data
              User        *Userinfo // username and password information
              Host        string    // host or host:port
              Path        string    // path (relative paths may omit leading slash)
              RawPath     string    // encoded path hint (see EscapedPath method)
              ForceQuery  bool      // append a query ('?') even if RawQuery is empty
              RawQuery    string    // encoded query values, without '?'
              Fragment    string    // fragment for references, without '#'
              RawFragment string    // encoded fragment hint (see EscapedFragment method)
      }
      QueryEscape(s string) string 			// url编码
      QueryUnescape(s string) (string, error)	// url解码
      PathEscape(s string) string				// 仅对”/“路径编码
      PathUnescape(s string) (string, error)	// 仅对”/“路径解码
      
      // 表单结构体
      type Values map[string][]string 
      Values.Get(key string) string	// 获取第一个值
      Values.Set(key, value string)	// 设置值
      Values.Add(key, value string)	// 追加值
      Values.Del(key string)	// 删除key
      Values.Encode() string	// Values表单结构体转为字符串
      
    2. HTTP请求(举例)

      // import "net/http"
      /* GET */
      func main() {
      	var url string = "http://httpbin.org/get?page=1&limit=2"
      	resp, err := http.Get(url)
      	if (err != nil) {
      		fmt.Println(err.Error())
      	}
          defer resp.Body.Close()
      
      	fmt.Println(resp.Status)     //200 ok
      	fmt.Println(resp.StatusCode) //200
      
      	var bodyReader io.ReadCloser = resp.Body //返回的是io.Reader
      	body, _ := ioutil.ReadAll(bodyReader)
      	fmt.Println(string(body))
      }
      
      /* PostForm */
      func main() {
      	var apiURL string = "http://httpbin.org/post?page=1"
      	var params url.Values = url.Values{"names": []string{"yjc", "yjc1"}}
      	params.Set("age", "20")
      	resp, err := http.PostForm(apiURL, params)
          // ...
      }
      
      /* 自行封装Request */
      var apiURL string = "http://httpbin.org/post?page=1"
      var params url.Values = url.Values{}
      params.Set("name", "yujc")
      
      // 创建客户端实例
      client := &http.Client{}
      
      //创建请求实例
      req, err := http.NewRequest("POST", apiURL, strings.NewReader(params.Encode()))
      if err != nil {
      	return nil, err
      }
      
      //增加Header
      req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
      req.Header.Add("Content-Encoding", "gzip")
      
      //发起请求
      resp, err := client.Do(req)
      if err != nil {
      	return nil, err
      }
      
      defer resp.Body.Close()
      
    3. 网络性能监控

      import (
      	"net/http"
      	_ "net/http/pprof"
      )
      
      func main(){
          //提供给负载均衡探活以及pprof调试
      	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      		w.Write([]byte("ok"))
      	})
      
      	go http.ListenAndServe(":10000", nil)
          
          time.Sleep(time.Hour)
      }
      // 在浏览器打开 http://127.0.0.1:10000/debug/pprof/就能看到监控的一些信息了
      
      // 可视化界面(需安装graphviz)的两种方法
      go tool pprof http://localhost:10000/debug/pprof/profile
      go tool pprof -http=:8080 http://localhost:10000/debug/pprof/profile
      
    文件读写
    1. os

      // import "os"
      /* 文件(夹)增删 */
      func Mkdir(name string, perm FileMode) error	// mkdir
      func MkdirAll(name string, perm FileMode) error	// mkdir -p
      func Remove(name string) error		// rm -f
      func RemoveAll(path string) error	// rm -rf
      
      /* 文件处理 */
      func Create(name string) (file *File, err error)	// 创建文件,默认权限0666
      func Open(name string) (file *File, err error)		// 打开文件(只读)
      func OpenFile(name string, flag int, perm uint32)	// 以flag方式打开文件,perm为权限
      // flag包含:[O_RDONLY 或 O_WRONLY 或 O_RDWR] | {O_APPEND, O_CREATE[|O_EXCL], O_SYNC,O_TRUNC}
      func (file *File) Write(b []byte) (n int, err error)
      func (file *File) WriteAt(b []byte, off int64) (n int, err error)
      func (file *File) WriteString(s string) (ret int, err error)
      func (file *File) Read(b []byte) (n int, err error)
      func (file *File) ReadAt(b []byte, off int64) (n int, err error)
      
    2. io 和 ioutil

      // io包为I/O原语定义了基本的接口
      type Reader interface {
          Read(p []byte) (n int, err error)
      }
      type ReaderAt interface {
          ReadAt(p []byte, off int64) (n int, err error)
      }
      
      type Writer interface {
          Write(p []byte) (n int, err error)
      }
      type WriterAt interface {
          WriteAt(p []byte, off int64) (n int, err error)
      }
      
      // import "io/ioutil"
      func ReadFile(filename string) ([]byte, error)	// 读取文件所有数据
      func WriteFile(filename string, data []byte, perm os.FileMode) error // 创建或清空文件并写入数据
      func ReadDir(dirname string) ([]os.FileInfo, error) // 读取目录中的所有目录及文件(不包含子目录),列表是经过排序的
      
      func TempFile(dir, prefix) (f *os.File, err error) // 在dir目录创建以prefix开头的临时文件,多次调用会创建不同的临时文件(若dir为空,则在默认临时目录 os.TempDir()。临时文件需要自己删除
      func TempDir(dir, prefix string) (name string, err error) // 同上类似
      
      func ReadAll(r io.Reader) ([]byte, error)	// 读取所有数据
      func NopCloser(r io.Reader) io.ReadCloser	// 包装Reader为ReadCloser类型,增加一个no-op方法
      
    3. 文件复制示例

      可以使用 io.Copy()或者使用 ioutil.WriteFile()+ioutil.ReadFile()进行文件复制,但最高效的还是使用边读边写的方式

      //打开源文件
      fileRead,err :=os.Open("/tmp/test.txt")
      if err != nil {
          fmt.Println("Open err:",err)
          return
      }
      defer fileRead.Close()
      //创建目标文件
      fileWrite,err := os.Create("/tmp/test_copy.txt")
      if err != nil {
          fmt.Println("Create err:",err)
          return
      }
      defer fileWrite.Close()
       
      //从源文件获取数据,放到缓冲区
      buf :=make([]byte, 4096)
      //循环从源文件中获取数据,全部写到目标文件中
      for {
          n,err := fileRead.Read(buf)
          if err != nil && err == io.EOF {
               fmt.Printf("读取完毕,n = d%\n:",n)
               return
          }
          fileWrite.Write(buf[:n]) //读多少、写多少
      }
      
  • 相关阅读:
    phpmyadmin和网页上面的乱码问题
    整理: Android HAL
    warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]-给char* 形参 传入 宏定义字符串
    ubuntu 输入法莫名其妙变繁体
    linux内核版本号添加字符/为何有时会自动添加“+”号以及怎么去掉
    Unable to handle kernel NULL pointer dereference at virtual address 00000000
    Ubuntu 18.04 安装 Samba 服务器及配置
    linux查看版本信息
    图解:电压掉电监测电路如何实现检测工作?
    精密全波整流+一阶RC滤波器检测市电电压
  • 原文地址:https://www.cnblogs.com/DreamEagle/p/16039430.html
Copyright © 2020-2023  润新知