Golang

 

[TOC]

起步

配置GOPATH

go项目的存放路径

1609730844373

默认创建了= =

如果更改,PATH里./go/bin也对应修改

项目

程序以package为单位,以package main中的func main()为入口

生态

Web框架

  • beego 国内
  • gin 轻量级
  • echo 差不多
  • lris 较大

微服务框架

  • go kit 微服务工具集
  • Istio 一体化 集成式

容器编排

  • k8s
  • swarm

服务发现

  • consul

存储引擎

  • etcd 分布式k/v存储 类似redis
  • tidb

静态建站

  • hugo

中间件

  • nsq 消息队列
  • zinx 轻量服务器
  • leaf 游戏服务器
  • grpc RPC框架
  • codis redis集群

爬虫框架

  • go query

基础语法

导入

import (
	"fmt"
	"math"
)
import f "fmt"
func main() {
	f.Println(add(42, 13)) //重命名标识符
}

函数

func add(x int, y int) int {
	return x + y
}
func add(x, y int) int { //省略连续的相同类型
	return x + y
}
func swap(x, y string) (string, string) { //多值返回
	return y, x
}
func split(sum int) (x, y int) { //相当于提前定义了要返回的值的名称
	x = sum * 4 / 9
	y = sum - x
	return //不带命名的return语句 将会直接返回上面定义了名称的返回值
}

变量

var a, b, c bool //声明了三个bool类型的变量
//声明可以出现在任何位置
func main() {
	var i int //声明的变量具有默认值
	fmt.Println(i, a, b, c)
}
var i, j int = 1, 2 //声明变量的同时初始化

func main() {
	var a b, c = true, false, "no!" //变量可以从初始化中获得类型
	fmt.Println(i, j, a, b, c)
}
k := 3 //简洁赋值语句 代替var
//函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
func test(x int) int  {
	return x
}

func main() {
	x := 1.2
	fmt.Println(test(int(x))) //不同类型赋值需要显式转换
}

常量

//与var相对的 声明常量使用关键字const
const Pi = 3.14
// 常量不能用 := 声明

循环、分支

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}
func main() {
	sum := 1
	for sum < 1000 { //Golang中的while
		sum += sum
	}
	fmt.Println(sum)
}
if x < 0 { //分支

} else {
    
}
func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim { //在分支判断的前面执行一个表达式
		return v
	}
	return lim
}
func main() { //switch
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os { //没什么好说的吧
	case "darwin":
		fmt.Println("OS X.")
        // break自动提供
	case "linux":
		fmt.Println("Linux.")
	default:
		fmt.Printf("%s.\n", os)
	}
}
func main() { // if-else if-...-else
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}
//defer关键字

var s string

func getWorld() string {
	s = "world"
	return s
}

func main() {
	defer fmt.Println(getWorld()) //将函数推迟到本层函数执行完毕后执行,但是参数表达式会立即完成
	fmt.Printf("在上面那个函数真正调用之前,其参数表达式已经完成,即调用getWorld()过后,s=%s\n", s)
	fmt.Println("hello")
}
func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i) //被defer的函数会被压入一个栈中,依次调用
	}

	fmt.Println("done")
}

指针

var p *int

i := 42
p = &i

fmt.Println(*p) 
*p = 21         

结构体

type Vertex struct {
	X int
	Y int
}

func main() {
	a := Vertex{1, 2}
	fmt.Println(a)
}
func main() {
	p := &Vertex{1, 2} //声明并初始化一个结构体指针
	p.X = 1e9 //隐式(*p).X
	fmt.Println(v)
}
v := Vertex{X: 1}  // 只显式地给部分值 Y:0 被隐式地赋予

数组

var a [10]int //声明
primes := [6]int{2, 3, 5, 7, 11, 13} //声明并初始化
//类型不可省略
//显式初始化时数量可以省略 省略后是切片(实际上是创建一个数组,然后构建一个引用了它的切片)
//长度不可变

切片

a[low : high] //前闭后开
func main() {
	a := []int{1, 2, 3, 4}
	b := a[0: 2]
	c := a[2: 4]
	fmt.Println(b, c) //切片像是对数组的引用

	for i:=0; i<4; i++{a[i] = 0}
	fmt.Println(b, c) //原数组修改后切片的内容也会改动

	b[0] = -1
	c[0] = -1
	fmt.Println(a) //同样对切片的修改也会引发原数组内容的变化
}
/*
切片拥有 长度 和 容量
	    len()   cap()
长度:切片所包含元素的个数
容量:他的第一个元素开始,到被引用的数组的最后一个元素的个数
**/
s := []int{2, 3, 5, 7, 11, 13} //创建一个对长度为6的数组的切片
s = s[:0] //截取切片使其长度为0 长度变化 容量不变
s = s[:4] //拓展其长度 长度变化 容量不变
s = s[2:] //舍弃前两个值 长度变化 容量变化

// 超出容量发生runtimeerro
//a := make([]type, len, cap)  用make创建切片(动态数组)
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
} //二维切片
/*
向切片追加新元素
func append(s []T, vs ...T) []T
arg1: 被追加的切片
arg2: 追加进去的元素

当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
**/

func main() {
	var s []int
	printSlice(s)

	// 添加一个空切片
	s = append(s, 0)
	printSlice(s)

	// 这个切片会按需增长
	s = append(s, 1)
	printSlice(s)

	// 可以一次性添加多个元素
	s = append(s, 2, 3, 4)
	printSlice(s)
}
//切片遍历
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
// pow := []int{...}

func main() {
	for i, v := range pow { //rang pow返回两个值,第一个是下标,第二个数元素副本,不需要的用_代替
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

映射

//声明一个string到Type的映射
var m map[string]Type
//用make创建一个映射
m = make(map[string]Type)
//声明并初始化一个映射
type Vertex struct {
	Lat, Long float64
}
var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}
//省略类型
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}
//增、改
m[key] = val
//查
elem = m[key]
//删
delete(m, key)
//检测键是否存在值
elem, ok = m[key]

函数变量化

函数可以作为变量、参数、返回值进行传递

函数的闭包

什么是闭包?存在自由变量的函数就是闭包。

自由变量与约束变量?

func liner(a, b int) func(int) int {
    return func(x int){
        return x * a + b
    }
}
//对于被返回的闭包可以说 x 是受限制的约束变量,a b 是自由变量
//且这个闭包与自由变量a、b是绑定的

案例:斐波那契闭包

// 返回一个“返回int的函数”
func fibonacci() func() int {
	x1, x2, i := 1, 1, 0
	return func() int {
		i++
		if i == 1 || i == 2{
			return 1
		}else {
			t := x2
			x2 += x1
			x1 = t
			return x2
		}
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

方法(带接收者的函数)

接收者能够通过.调用这个函数

type Rectangle struct {
	X, Y float64
}

func (v Rectangle) Area() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Rectangle{3, 4}
	fmt.Println(v.Area())
}

接收者只能是同一个包内定义的类型

func (f float64) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}//×

type MyFloat float64 
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}//√

方法对接收者的副本进行操作,通过设置指针接收者来使其作用于原本的变量

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

函数对形参和指针敏感;

指针重定向:

  • 指针为接收者的方法的调用对象可以使值也可以是指针,对于值实际上的调用为(&v).fun();-

  • 值为接收者的方法的调用对象可以是值也可以是指针,对于指针实际上的调用为(*v).fun();

接口

接口是一组方法签名的集合

//eg
type Abser interface {
	Abs() float64
}
//完整实例
type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Pi)
	v := Vertex{3, 4}

	a = f  // a MyFloat 实现了 Abser
	a = &v // a *Vertex 实现了 Abser

	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	// 所以没有实现 Abser。
	//a = v 报错

	fmt.Println(a.Abs())
}

type MyFloat float64 //拥有接口中相同签名方法,故被视为Abser的实现

func (f MyFloat) Abs() float64 { //被视为对Abser中Abs()的实现
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 { //同上
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

类型通过实现一个接口中所有方法来实现该接口,即使你并没有import那个包,故Go没有implements关键字

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

上句,理解不能

接口可以像值一样传递,接口值调用方法会调用其具体实现的方法,在内部接口值看做元组(value, type)

接口值可以接受nil,对接收了nil的接口调用方法实际上也就是对nil尝试引用,会产生空指针异常。但是奇妙的是接收了nil的接口本身不为nil,所以空指针预防就只能在具体实现的方法中做。

nil的接口,可以看成元组是空的,调用产生空指针异常

空接口 type interface {},因为任何类型都至少实现了0个方法,故空接口用来接收任何类型的值,类似Object

type X interface {}
func main() {
	var i X
	describe(i) //(<nil>, <nil>)

	i = 42
	describe(i) //(42, int)

	i = "hello"
	describe(i) //(hello, string)
}

func describe(i X) {
	fmt.Printf("(%v, %T)\n", i, i)
}

类型断言提供了访问接口值的具体类型的途径

t := i.(T) // 该语句断言接口值i的具体实现是T,并将这个接口值转型具体变量t

断言失败会产生panic,可通过 t, ok := i.(T)获得判断是否断言成功的句柄,且失败不会产生panic

类型选择,类型断言和switch的组合

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

一些特殊的接口:

  • type Stringer interface{ String() string } 这个接口属于包fmt,实现这个方法用以支持许多描述类型的调用

    //example
    package main
      
    import "fmt"
      
    type IPAddr [4]byte
      
    // TODO: 给 IPAddr 添加一个 "String() string" 方法
    func (v *IPAddr) String() string {
    	return fmt.Sprintf("%v.%v.%v.%v", v[0], v[1], v[2], v[3])
    }
      
    func main() {
    	hosts := map[string]IPAddr{
    		"loopback":  {127, 0, 0, 1},
    		"googleDNS": {8, 8, 8, 8},
    	}
    	for name, ip := range hosts {
    		fmt.Printf("%v: %v\n", name, ip)
    	}
    }
    
  • type error interface{ Error() string },这个接口属于内建接口,实现这个接口,来获得错误信息

    //example
    package main
      
    import (
    	"fmt"
    	"math"
    )
      
    type ErrNegativeSqrt float64
      
    func (number ErrNegativeSqrt) Error() string {
        //递归调用死循环,由于number实现了Error(),fmt在解释时将其视作error,因此调用了number.Error(),从而出现了递归调用,最终导致栈溢出
        //下意识地准备用引用,麻了
    	//return fmt.Sprintf("cannot Sqrt negative number: %v", number)
        return fmt.Sprintf("cannot Sqrt negative number: %v", float64(number))
    }
      
    func Sqrt(x float64) (float64, error) {
    	if x >= 0 {
    		return math.Sqrt(x), nil
    	}else {
    		return 0, ErrNegativeSqrt(x)
    	}
    }
      
    func main() {
    	fmt.Println(Sqrt(2))
    	fmt.Println(Sqrt(-2))
    }
    
  • io.Reader包装io.Reader

    package main
      
    import (
    	"io"
    	"os"
    	"strings"
    )
      
    func rot13(b byte) byte {
    	switch {
    	case 'A' <= b && b <= 'Z':
    		b = (b - 'A' + 13) % 26 + 'A'
    	case 'a' <= b && b <= 'z':
    		b = (b - 'a' + 13) % 26 + 'a'
    	}
    	return b
    }
      
    type rot13Reader struct {
    	r io.Reader
    }
      
    func (rotR *rot13Reader) Read(bytes []byte) (int, error){
    	n, e := rotR.r.Read(bytes)
    	for i, value := range bytes{
    		bytes[i] = rot13(value)
    	}
    	return n, e
    }
      
    func main() {
    	s := strings.NewReader("Lbh penpxrq gur pbqr!")
    	r := rot13Reader{s}
    	_, _ = io.Copy(os.Stdout, &r)
    }
    
  • image接口

    package image
      
    type Image interface {
        ColorModel() color.Model
        Bounds() Rectangle
        At(x, y int) color.Color
    }
    

多线程

goroutine ——> go fun()

启动一个新的Go程并执行fun()

Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。 ? 不同的地址空间就不需要同步吗?

信道是带有类型的管道

  ch := make(chan int) // 声明并初始化一个信道
  ch <- v    // 将 v 发送至信道 ch。
  v := <-ch  // 从 ch 接收值并赋予 v。

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。从而完成同步

  s := []int{7, 2, 8, -9, 4, 0}
  
  c := make(chan int)
  go sum(s[:len(s)/2], c)//17
  go sum(s[len(s)/2:], c)//-5
  go sum(s[:], c)//12
  x, y, z:= <-c, <-c, <-c // 从 c 中接收
  
  //17进入信道后阻塞之,取出后-5进入信道

带缓冲的信道

  ch := make(chan int, 2)

触发deadlock

  ch := make(chan int, 2)
  ch <- 1
  ch <- 2
  ch <- 3

测试信道是否被关闭

  v, ok := <- ch //若没有值可以接收且信道已被关闭,那么在执行完后ok为false

循环接收

  for i := range ch  //直到ch被关闭

关闭信道 close(ch)

只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发panic

通常情况下,信道不像文件不需要特地关闭,除非用于逻辑控制

  // 这功能有点像yield啊
  func fibonacci(n int, c chan int) {
  	x, y := 0, 1
  	for i := 0; i < n; i++ {
  		c <- x
  		x, y = y, x+y
  	}
  	close(c)
  }
  
  func main() {
  	c := make(chan int, 10)
  	go fibonacci(cap(c), c)
  	for i := range c {
  		fmt.Println(i)
  	}
  }

select 挑选一个准备好的信道操作

  func fibonacci(c, quit chan int) {
  	x, y := 0, 1
  	for {
  		select {
  		case c <- x:
  			x, y = y, x+y
  		case <-quit:
  			fmt.Println("quit")
  			return
  		}
  	}
  }
  
  func main() {
  	c := make(chan int)
  	quit := make(chan int)
  	go func() {
  		for i := 0; i < 10; i++ {
  			fmt.Println(<-c)
  		}
  		quit <- 0
  	}()
  	fibonacci(c, quit)
  }

select默认选择,其他分支都没有准备好,就会触发default

func main() {
	tick := time.Tick(100 * time.Millisecond) //每100ms触发一次
	boom := time.After(500 * time.Millisecond) //500ms后触发
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

练习:等价二叉查找树

func Travel(t *tree.Tree, ch chan int){
	if t == nil {
		return
	}
	Travel(t.Left, ch)
	ch <- t.Value
	Travel(t.Right, ch)
}

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int){
	Travel(t, ch)
	close(ch)
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool{
	c1, c2 := make(chan int), make(chan int)
	go Walk(t1, c1)
	go Walk(t2, c2)
	for val1 := range c1{
		if val1 != <-c2 {
			return false
		}
	}
	return true
}

互斥锁

sync.Mutex

  • Lock方法和Unlock方法
  • defer Unlock()以确保必定会解锁

练习:web爬虫

/*待更正*/
package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
	Fetch(url string) (body string, urls []string, err error)
}


type Crawler struct {
	sync.Mutex
	fetched map[string]string
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func (crawler *Crawler) Crawl(url string, depth int, fetcher Fetcher) {
	// TODO: 并行的抓取 URL。
	// TODO: 不重复抓取页面。
	// 下面并没有实现上面两种情况:

	//defer fmt.Printf("<- Done with %v\n", url)

	if depth <= 0 {
		return
	}

	//判断结果是否已经存在
	crawler.Lock()
	if _, ok := crawler.fetched[url]; ok{
		crawler.Unlock()
		return
	}
	//crawler.fetched[url] = "[loading.]"
	crawler.Unlock()

	body, urls, err := fetcher.Fetch(url)

	if err != nil {
		fmt.Println(err)
		return
	}

	crawler.Lock()
	crawler.fetched[url] = body
	crawler.Unlock()

	
	done := make(chan bool)
	for _, u := range urls {
		go func(url string) {
			//Lambda式中函数不能用迭代变量
			crawler.Crawl(url, depth-1, fetcher)
			done <- true
		}(u)
	}
	//利用channel的阻塞等待函数执行结束
	for range urls {
		<- done
	}
	return
}

func main() {
	c := Crawler{ fetched: make(map[string]string)}
	c.Crawl("https://golang.org/", 4, fetcher)
	for url, body := range c.fetched {
		fmt.Printf("found: %s %q\n", url, body)
	}
}

// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.



package main

import (
	"errors"
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch returns the body of URL and
	// a slice of URLs found on that page.
	Fetch(url string) (body string, urls []string, err error)
}

// fetched tracks URLs that have been (or are being) fetched.
// The lock must be held while reading from or writing to the map.
// See https://golang.org/ref/spec#Struct_types section on embedded types.
var fetched = struct {
	m map[string]error
	sync.Mutex
}{m: make(map[string]error)}

var loading = errors.New("url load in progress") // sentinel value

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	if depth <= 0 {
		//fmt.Printf("<- Done with %v, depth 0.\n", url)
		return
	}

	fetched.Lock()
	if _, ok := fetched.m[url]; ok {
		fetched.Unlock()
		fmt.Printf("<- Done with %v, already fetched.\n", url)
		return
	}
	// We mark the url to be loading to avoid others reloading it at the same time.
	fetched.m[url] = loading
	fetched.Unlock()

	// We load it concurrently.
	body, urls, err := fetcher.Fetch(url)

	// And update the status in a synced zone.
	fetched.Lock()
	fetched.m[url] = err
	fetched.Unlock()

	if err != nil {
		fmt.Printf("<- Error on %v: %v\n", url, err)
		return
	}
	fmt.Printf("Found: %s %q\n", url, body)
	done := make(chan bool)
	for i, u := range urls {
		fmt.Printf("-> Crawling child %v/%v of %v : %v.\n", i, len(urls), url, u)
		go func(url string) {
			Crawl(url, depth-1, fetcher)
			done <- true
		}(u)
	}
	for i, u := range urls {
		fmt.Printf("<- [%v] %v/%v Waiting for child %v.\n", url, i, len(urls), u)
		<-done
	}
	fmt.Printf("<- Done with %v\n", url)
}

func main() {
	Crawl("https://golang.org/", 4, fetcher)

	fmt.Println("Fetching stats\n--------------")
	for url, err := range fetched.m {
		if err != nil {
			fmt.Printf("%v failed: %v\n", url, err)
		} else {
			fmt.Printf("%v was fetched\n", url)
		}
	}
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := (*f)[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

Go Modules

替代Go Path工作模式,用来创建项目

Go mod命令

命令 作用
go mod init 生成go.mod文件
go mod download 下载go.mod中指明的依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑go,mod
go mod vendor 导出项目所有依赖到vendor目录
go mod verify 校验一个依赖是否被修改过
go mod why 查看为什么需要依赖某模块

go mod 环境变量

  • GOPROXY 拉取依赖时的镜像站,修改:go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
  • GOSUMDB 下载校验和检查站,可从上获取
  • GONOPROXY|GONOSUMDB|GOPRIVATE 私有库,不会经过GOPROXYGOSUMDBGOPRIVATE会覆盖前2

文件操作

file, err := os.Open("")
if err != nil {
    //文件打开错误
}

defer file.Close() // 关闭
// 使用字节数组
var buffer [256]byte
n, err := file.Read(buffer)
if err != nil {
    //读字节错误
    //文件末尾会抛出错误EOF
}
//使用bufio
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n') 
//使用ioutil
content, err := ioutil.ReadFile("")

func OpenFile(name string, flag int, pernm fileMode) (*file, error) {...}

os.OpenFile("xxx.txt", os.O_RDONLY, 0777)
  • os.O_RDONLY: 只读
  • os.O_WRONLY: 只写
  • os.O_RDWR: 读写
  • os.O_CREAT: 创建
  • os.O_TRUNC: 清空
  • os.O_APPEND: 追加
file.Write([]byte ) 
file.WriteString(string )

使用bufio

writer := bufio.NewWriter(file)
writer.WriteString("???") //写入缓冲
writer.Flush() //写入文件

使用ioutil

err := ioutil.WriteFile("xxx", []byte, perm)
if err != nil {...}

获取标准输入

var str string
fmt.Scanln(&str) // 以空白符号截止
reader := bufio.NewReader(os.stdin) //标准输出同理
str, err := reader.ReadString('\n') //指定字符截止