样例代码

01-hello-world 阅读更多

package main import "fmt" func main() { fmt.Println("hello world") } // 使用go run hello-world.go来执行本程序 // 使用go build 将本程序编译成二进制文件,然后使用./hello-world 执行编译后的二进制文件

02-values 阅读更多

// Golang有多种值类型,包括字符串、整数、浮点数、布尔值等 package main import "fmt" func main() { // 使用加号(+)可以将string合并到一起 fmt.Println("go" + "lang") fmt.Println("1+1=", 1+1) fmt.Println("7.0/3.0=", 7.0/3.0) // 布尔运算符是短路运算符 fmt.Println(true && false) fmt.Println(true || false) fmt.Println(!true) }

03-variables 阅读更多

// 在Go中,变量被显式声明并由编译器用于(例如检查函数调用的类型正确性)。 package main import "fmt" func main() { // var 声明一个或多个变量,可以在一次声明中同时声明多个变量 // Go将会推断初始化变量的类型 var a = "initial" fmt.Println(a) var b, c int = 1, 2 fmt.Println(b, c) var d = true fmt.Println(d) // 在声明变量时没有对它进行任何初始化操作,那么该变量的默认值为该类型的零值。(如 int 的零值为 0) var e int fmt.Println(e) // := 句法是一种短变量声明和初始化的方式。(如 上面的变量f的声明和初始化) f := "apple" fmt.Print(f) }

04-constants 阅读更多

// Golang支持的常量包括字符、字符串、布尔和数值 package main import ( "fmt" "math" ) // const 用于声明常量,const语句可以出现在任何var语句可以出现的地方 const s string = "constant" func main() { fmt.Println(s) // 常量表达式以任意精度执行算术 const n = 500000000 const d = 3e20 / n fmt.Println(d) // 数值常量在被设置之前是无类型的,(如 上面例子中的常量 d 通过显式转换) fmt.Println(int64(d)) // 在上下文中使用数字时可以自动给它设置类型,(如 变量赋值或者函数调用) fmt.Println(math.Sin(n)) // 此处的math.Sin()函数需要一个float64类型的数值 }

05-for 阅读更多

// for是Golang中唯一的循环结构,for循环有三种基本形式 package main import "fmt" func main() { i := 1 // 最基本的类型,具有单一条件 for i <= 3 { fmt.Println(i) i = i + 1 } // 经典的初始化/条件/下一步循环 for j := 7; j < 9; j++ { fmt.Println(j) } // 无条件的循环将会一直执行直到break循环 // 或者从封闭的函数中return for { fmt.Println("loop") break } // 使用contine继续循环的下一次迭代 for n := 0; n <= 5; n++ { if n%2 == 0 { continue } fmt.Println(n) } }

06-if-else 阅读更多

// 用Golang中的分支(if/else)非常简洁明了 package main import "fmt" func main() { // Go语言中的条件不需要括号,但需要大括号 if 7%2 == 0 { fmt.Println("7 is even") } else { fmt.Println("7 is odd") } // 没有else语句的if语句 if 8%4 == 0 { fmt.Println("8 is divisible by 4") } // 变量声明可以优先于条件,变量声明后在分支中都可以使用 if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } } // 在Go语言中没有三元组,所以即使是最基本的条件也需要完整的if语句

07-switch 阅读更多

// Switch语句表达多个分支的条件 package main import ( "fmt" "time" ) func main() { i := 2 fmt.Print("Write ", i, " as ") switch i { case 1: fmt.Print("one\n") case 2: fmt.Print("two\n") case 3: fmt.Print("three\n") } switch time.Now().Weekday() { case time.Saturday, time.Sunday: // 在同一个case语句中使用逗号分隔多个表达式 fmt.Println("It's the weekend") default: // default是可选的,没有符合的情况就默认执行这个语句 fmt.Println("It's a weekday") } t := time.Now() switch { // 没有表达式的switch语句可以认为是if/else语句的另一种形式 case t.Hour() < 12: // case表达式也可以不是常量 fmt.Println("It's before noon") default: fmt.Println("It's after noon") } whatAmI := func(i interface{}) { switch t := i.(type) { // switch比较类型而不是比较值,可以用于发现接口值的类型,变量t将会具有和case子句所对应的类型 case bool: fmt.Println("I'm a bool") case int: fmt.Println("I'm a int") default: fmt.Printf("Don't know type %T\n", t) } } whatAmI(true) whatAmI(1) whatAmI("hey") }

08-arrays 阅读更多

// 在Go中,数组是定长的元素编号序列 package main import "fmt" func main() { var a [5]int // 创建一个存储5个int的数组a,元素类型和长度都是数组类型的一部分,默认情况下数组中存储零值 fmt.Println("emp:", a) a[4] = 100 //使用array[index]=value语法,在索引处设置值 fmt.Println("set:", a) fmt.Println("get:", a[4]) // 使用array[index]来获取值 fmt.Println("len:", len(a)) // 内置函数len返回数组的长度 b := [5]int{1, 2, 3, 4, 5} // 使用这种语法在一行中声明并初始化数组 fmt.Println("dcl:", b) // 使用fmt.Println将会以[v1 v2 v3 ...]形式输出数组 var twoD [2][3]int // 数组类型是一维的,但是可以通过组合类型来创建多维数据结果 for i := 0; i < 2; i++ { for j := 0; j < 3; j++ { twoD[i][j] = i + j } } fmt.Println("2d: ", twoD) } // 切片比数组在Go中更常用

09-slice 阅读更多

// 切片是Go中的关键数据类型,为序列提供了比数组更强大的接口。 package main import "fmt" func main() { // 与数组不同,切片的类型由它包含的元素(而不是元素数量)决定,使用内置的make函数创建长度非零的空切片 s := make([]string, 3) // 在这里创建一个长度为3的空的字符串切片,其中默认零值为空字符串 fmt.Println("emp:", s) s[0] = "a" s[1] = "b" s[2] = "c" // 可以像数组那样设置和获取切片的值 fmt.Println("set:", s) fmt.Println("get:", s[2]) fmt.Println("len:", len(s)) // len函数返回切片的长度 // 除了上面的基本操作,切片还有更多其他操作,使得它比数组更丰富 s = append(s, "d") // 内置函数append返回包含一个或多个新值的切片 s = append(s, "e", "f") // 需要接收来自append函数的返回值,因为可能会得到一个新的切片 fmt.Println("apd:", s) c := make([]string, len(s)) // 创建一个与s长度相同的空的切片c用于存储s的拷贝 copy(c, s) // 切片也可以被拷贝 fmt.Println("cpy:", c) // 切片支持slice[low:high]语法的切片操作 l := s[2:5] // 得到包含元素s[2],s[3]和s[4]的切片 fmt.Println("sl1:", l) l = s[2:] //从s[2]开始到结尾 fmt.Println("sl2:", l) l = s[:5] //从头开始到s[5]结束 fmt.Println("sl3:", l) t := []string{"g", "h", "i"} // 可以在一行中声明并初始化一个切片变量 fmt.Println("dcl:", t) twoD := make([][]int, 3) // 切片可以被组合成多维数据结构 for i := 0; i < 3; i++ { innerLen := i + 1 twoD[i] = make([]int, innerLen) // 内部切片的长度是可变的,这与多维数组不同 for j := 0; j < innerLen; j++ { twoD[i][j] = i + j } } fmt.Println("2d", twoD) } // 注意,虽然切片的类型与数组不同,但是在fmt.Println中的输出形式是相似的 // Go语言团队在切片设计与实现中的更对细节:https://blog.golang.org/go-slices-usage-and-internals

10-maps 阅读更多

// map是Go内置的关联数据类型(在其他语言中有时成为hash或者字典) package main import "fmt" func main() { m := make(map[string]int) // 使用内置函数make(map[key-type]value-type)创建空的map // 使用name[key]=val句法设置键值对 m["k1"] = 7 m["k2"] = 13 fmt.Println("map: ", m) // 使用fmt.Println将会输出map中全部的键值对 v1 := m["k1"] // 使用name[key]来获取key所对应的值 fmt.Println("v1: ", v1) fmt.Println("len: ", len(m)) // 在map上调用内置函数len时返回的是键值对的数量 delete(m, "k2") // 内置函数delete删除map中的键值对 fmt.Println("map: ", m) // 不需要获取值本身,所以使用空标识符_忽略它 _, prs := m["k2"] // 从map中获取值时,可选的第二个返回值标识该键是否在map中,这可以用于消除缺失键和具有零值(如 “”或者0)的键之间的歧义 fmt.Println("prs: ", prs) n := map[string]int{"foo": 1, "bar": 2} // 使用这种句法在一行内声明和初始化map fmt.Println("map: ", n) } // 注意,当使用fmt.Println输出map时,输出的形式如map[k:v k:v]

11-range 阅读更多

// range遍历各种数据结构中的元素 package main import "fmt" func main() { nums := []int{2, 3, 4} sum := 0 for _, num := range nums { // 使用range来对切片中的数字求和,数组也是这样使用 sum += num } fmt.Println("sum: ", sum) for i, num := range nums { // 在数组和切片上使用range都会返回每个条目的索引和值,上面的例子中,不需要返回的索引时,使用空白标识符来忽略它 if num == 3 { fmt.Println("index: ", i) } } kvs := map[string]string{"a": "apple", "b": "banana"} // 在map上使用range将会迭代其中的键值对 for k, v := range kvs { fmt.Printf("%s --> %s\n", k, v) } for k := range kvs { // range也可以只遍历map中的键值 fmt.Println("key: ", k) } for i, c := range "go" { // 在字符串上使用range将会遍历unicode代码点,返回的第一个值是rune的索引,第二个值是rune自身 fmt.Println(i, c) } } // go中byte就是uint8,rune就是int32

12-functions 阅读更多

// 函数是Go的核心 package main import "fmt" func plus(a, b int) int { // plus函数输入两个int并以int返回他们的和 return a + b // Go需要显式返回,而不会自动返回最后一个表达式的值 } func plusplus(a, b, c int) int { // 当有多个连续的相同类型的参数时,可以省略相同参数类型的参数名称,直到声明该类型的最后一个参数 return a + b + c } func main() { res := plus(1, 2) // 使用name(args)来调用函数 fmt.Println("1+2 =", res) res = plusplus(1, 2, 3) fmt.Println("1+2+3 =", res) }

13-multiple-return-values 阅读更多

// Go内置支持多个返回值,这个特性通常用在惯用的Go中,如从函数中返回结果和错误值 package main import "fmt" func vals() (int, int) { // 函数签名中的(int,int),表示这个函数返回两个int return 3, 7 } func main() { a, b := vals() fmt.Println(a) fmt.Println(b) _, c := vals() // 如果只需要返回值中的一个子集,可以使用空白标识符_ fmt.Println(c) }

14-variadic-functions 阅读更多

// 可变参数函数可以被任意数量的尾随参数调用 // 例如,fmt.Println就是一个常见的可变参数函数 package main import "fmt" func sum(nums ...int) { // 将任意数量的int作为参数的函数 fmt.Print(nums, " ") total := 0 for _, num := range nums { total += num } fmt.Println(total) } func main() { sum(1, 2) // 可变参数函数可以像通常那样被单个参数调用 sum(1, 2, 3) nums := []int{1, 2, 3, 4} // 如果在切片中有多个参数,可以使用func(slice...)句法将切片应用到可变参数函数中 sum(nums...) }

15-closures 阅读更多

// Go支持匿名函数,可用于形成闭包 // 当需要定义一个没有名字的内部函数时,匿名函数很有用 package main import "fmt" func intSeq() func() int { // 函数intSeq返回一个定义在intSeq函数体中的匿名函数 // 返回的函数关闭变量i以形成闭包 i := 0 return func() int { i++ return i } } func main() { nextInt := intSeq() // 调用intSeq函数,将结果(函数)分配给nextInt // 此函数值捕获其自身的i值,每次调用nextInt都会更新该值 // 多次调用nextInt来查看闭包的效果 fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) // 创建一个新的测试函数,来确定该状态对于特定函数是唯一的 newInts := intSeq() fmt.Println(newInts()) }

16-recursion 阅读更多

// Go支持递归函数 package main import "fmt" func fact(n int) int { if n == 0 { return 1 } return n * fact(n-1) } func main() { fmt.Println(fact(7)) }

17-pointer 阅读更多

// Go支持指针,允许传递对程序中的值和记录的引用 package main import "fmt" func zeroval(ival int) { // zeroval函数的参数是int,所以参数将按值传递 ival = 0 // zeroval将会获得ival值的拷贝,而不是调用函数时传入的ival } func zeroptr(iptr *int) { // zeroptr函数的参数是*int ,这表示它需要一个int指针 *iptr = 0 // 函数体中的*iptr解除在那个地址上的从内存地址到当前值的引用,为解除引用的指针赋值会改变引用地址的值 } func main() { i := 1 fmt.Println("initial:", i) zeroval(i) fmt.Println("zeroval:", i) zeroptr(&i) // &i语法给出i的内存地址,即指向i的指针 fmt.Println("zeroptr:", i) fmt.Println("pointer:", &i) // 指针也可以被打印 } // zeroval不会更改main()中的i,但是zeroptr会更改,因为它具有对该变量的内存地址的引用。

18-struct 阅读更多

// Go中的结构是字段的类型集合,它们可用于将数据分组在一起以形成记录 package main import "fmt" // person :结构体包含name和age两个字段 type person struct { name string age int } // NewPerson :根据给定的name创建一个person结构体 func NewPerson(name string) *person { p := person{name: name} p.age = 42 return &p // 可以安全的返回指向局部变量的指针,因为局部变量将在函数范围内存活 } func main() { fmt.Println(person{"Bob", 20}) //这种语法创建一个新的结构体 fmt.Println(person{name: "alice", age: 30}) // 可以在初始化结构体时命名字段 fmt.Println(person{name: "Fred"}) // 忽略的字段将会被命名为该字段的零值 fmt.Println(&person{name: "Ann", age: 40}) // &前缀将产生一个指向结构体的指针 fmt.Println(NewPerson("Jon")) // 在构造函数中封装新结构的创建不太好 s := person{name: "Sean", age: 50} fmt.Println(s.name) // 使用点访问结构体的字段 sp := &s fmt.Println(sp.age) // 在结构体指针上也可以使用点,此时指针会自动接触引用 sp.age = 51 // 结构体是可变的 fmt.Println(sp.age) }

19-methods 阅读更多

// Go支持在结构体类型上定义方法 package main import "fmt" type rect struct { width, height int } func (r *rect) area() int { // area方法有一个*rect类型的接收器 return r.width * r.height } func (r rect) perim() int { // 既可以给指针接收器类型定义方法也可以给值接收器类型定义方法 return 2*r.width + 2*r.height } func main() { // Go自动处理方法调用中值和指针之间的转换 r := rect{width: 10, height: 5} fmt.Println("area:", r.area()) // 使用指针接收器类型来避免在调用方法时出现值拷贝的情况或者允许方法改变接收的结构 fmt.Println("perim:", r.perim()) rp := &r fmt.Println("area:", rp.area()) fmt.Println("perim:", rp.perim()) }

20-interface 阅读更多

// 接口是方法签名的命名集合 package main import ( "fmt" "math" ) type geometry interface { // geometry接口 area() float64 perim() float64 } // rect类型和circle类型都将实现geometry接口 type rect struct { width, height float64 } type circle struct { radius float64 } // 在Go中要实现一个接口只需要实现接口中全部的方法即可 func (r rect) area() float64 { return r.width * r.height } func (r rect) perim() float64 { return 2*r.width + 2*r.height } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perim() float64 { return 2 * math.Pi * c.radius } // 如果变量具有接口类型,那么可以调用该接口的方法 func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } func main() { // rect和circle结构体类型都实现了geometry接口 // 所以可以使用这些结构的实例作为参数 r := rect{width: 3, height: 4} c := circle{radius: 5} measure(r) measure(c) }

21-errors 阅读更多

// Go的惯例中,通过明确的贩毒的返回值来传达错误,这与Java和Ruby等语言中使用的异常以及C语言中有时候使用的重载单个结果或错误值形成对比 // 在Go中很容的看到哪些函数返回错误并使用任何其他相同的语言结构体来处理它们,而不是错误任务 package main import ( "errors" "fmt" ) func f1(arg int) (int, error) { // 按照惯例,errors是最后一个返回的值,error类型(一个内置的接口) if arg == 42 { return -1, errors.New("can't work with 42") // 使用给定的错误消息来构造一个基本的错误值 } return arg + 3, nil // nil表示没有错误 } type argError struct { // 自定义错误类型 arg int prob string } func (e *argError) Error() string { // 实现Error方法以实现error接口 return fmt.Sprintf("%d-%s", e.arg, e.prob) } func f2(arg int) (int, error) { if arg == 42 { return -1, &argError{arg, "can't work with it"} // 使用&argError语法来构建一个新的结构体,同时为字段提供初始化值 } return arg + 3, nil } func main() { for _, i := range []int{7, 42} { if r, e := f1(i); e != nil { fmt.Println("f1 failed:", e) } else { fmt.Println("f1 worked:", r) } } for _, i := range []int{7, 42} { if r, e := f2(i); e != nil { fmt.Println("f2 failed:", e) } else { fmt.Println("f2 worked:", r) } } // 如果要以编程方式使用自定义错误中的数据,则需要通过类型断言将错误作为自定义错误类型的实例 _, e := f2(42) if ae, ok := e.(*argError); ok { fmt.Println(ae.arg) fmt.Println(ae.prob) } }

22-goroutines 阅读更多

// Goroutine是一个轻量级的执行线程 package main import "fmt" func f(from string) { for i := 0; i < 3; i++ { fmt.Println(from, ":", i) } } func main() { f("direct") // 调用f()函数,并以同步的方式运行 go f("goroutine") // 在goroutine中调用f()函数,这个新的goroutine将与发起调用的goroutine同时执行 go func(msg string) { // 以匿名函数调用启动goroutine fmt.Println(msg) }("going") // 上述两个函数调用在不同的goroutine中异步运行,执行到此为止 fmt.Scanln() // Scanln()函数需要在程序退出钱按一个按键 fmt.Println("done") } // 程序运行后,首先看到阻塞调用的输出,然后是两个goroutine的交错输出 // 这种交错输出反应了Go运行时启动的并发运行的goroutine

23-channel 阅读更多

// 通道是连接并发goroutine的管道 // 可以通过管道在一个goroutine与另一个goroutine之间发送和接收值 package main import "fmt" func main() { message := make(chan string) // 使用make(chane val-type)语法来创建一个新的通道,通道根据传入的值来确定类型 // 使用channel <- 语法向通道中传入一个值,此处在一个新的goroutine中向message通道中传入一个字符串ping go func() { message <- "ping" }() msg := <-message fmt.Println(msg) } // 程序运行后,字符串ping将会通过通道成功的从一个goroutine传递到另一个goroutine中 // 默认情况下,直到发送和接收都准备好,才会进行发送和接收操作 // 这种特性使得的我们可以在程序结束时等到ping消息,而不需要任何其他同步

24-channel-buffering 阅读更多

// 默认情况下,通道是不带缓冲区的,这意味着只有接收端和发送端同时准备好才能发送数据 // 带缓冲区的通道在没有相应的接收端时,可以接收有限数量的值 package main import "fmt" func main() { message := make(chan string, 2) // 创建字符串通道,最多缓冲两个值 // message通道是带有缓存的,因此可以将值发送到通道中,而不需要相应的并发接收 message <- "buffered" message <- "channel" // 正常接收到通道中的两个值 fmt.Println(<-message) fmt.Println(<-message) }

25-channel-sync 阅读更多

// 可以使用通道来跨goroutine同步执操作 package main import ( "fmt" "time" ) func worker(done chan bool) { // 在goroutine中执行的函数,done通道用于通知另一个goroutine此函数已经执行完成 fmt.Print("working...") time.Sleep(time.Second) fmt.Println("done") done <- true // 发送一个值来通知函数执行完成 } func main() { done := make(chan bool, 1) go worker(done) // 启动一个goroutine,并将通知通道传递给它 <-done // 阻塞,直到从通道中接收到worker发出的通知 // 如果删除上面<-done这一行,程序可能在worker启动之前就已经退出了 }

26-channel-directions 阅读更多

// 将通道作为函数的参数时,可以指定通道为单向通道 // 这种特性增加了程序的类型安全 package main import "fmt" func ping(pings chan<- string, msg string) { // ping函数只接收输入通道,尝试在输入通道上进行接收数据将发生编译时错误 pings <- msg } func pong(pings <-chan string, pongs chan<- string) { // pong函数接收一个输出通道和一个输入通道 msg := <-pings pongs <- msg } func main() { pings := make(chan string, 1) pongs := make(chan string, 1) ping(pings, "passed message") pong(pings, pongs) fmt.Println(<-pongs) }

27-select 阅读更多

// Go中的select等待多个通道的操作 // 将goroutine\通道和select结合是Go中很强大的功能 package main import ( "fmt" "time" ) func main() { startTime := time.Now() c1 := make(chan string) c2 := make(chan string) // 每个通道将在一段时间后收到一个值,以此来模拟在并发的goroutine中的阻塞RPC操作 go func() { time.Sleep(1 * time.Second) c1 <- "one" }() go func() { time.Sleep(2 * time.Second) c2 <- "two" }() for i := 0; i < 2; i++ { select { // 使用select来同事等待这两个值,并在它们到达时打印每个值 case msg1 := <-c1: fmt.Println("received", msg1) case msg2 := <-c2: fmt.Println("received", msg2) } } endTime := time.Since(startTime) fmt.Println(endTime) } // 由于1秒和2秒的睡眠是同时进行的,所以程序执行的总时间是2秒出头

28-timeout 阅读更多

// timeouts 对于需要连接到外部资源或者需要绑定执行时间的程序非常重要 // 使用通道和select在Go中实现timeout简单切优雅 package main import ( "fmt" "time" ) func main() { c1 := make(chan string, 1) // 创建一个缓冲区为1的通道,在不读取的情况下,这是防止goroutine泄露的常见模式 go func() { // 另外的goroutine将在两秒后向通道c1中写入“result1” time.Sleep(2 * time.Second) c1 <- "result1" }() select { // select语句实现超时选择 case res := <-c1: // 等待上面的goroutine返回结果 fmt.Println(res) case <-time.After(1 * time.Second): // 等待1秒后将时间写入返回的通道中 fmt.Println("timeout1") } c2 := make(chan string, 1) go func() { time.Sleep(2 * time.Second) c2 <- "result2" }() select { case res := <-c2: fmt.Println(res) case <-time.After(3 * time.Second): // 等待3秒后将时间写入返回的通道中 fmt.Println("timeout2") } }

29-nonblocking-channel 阅读更多

// 通道上的发送和接收操作都是阻塞的 // 使用带有default子句的select来实现非阻塞发送,接收甚至是非阻塞多路select package main import "fmt" func main() { messages := make(chan string) signals := make(chan bool) // 非阻塞接收,如果message中有值,那么select将会使用<-message 这个case,否则执行default select { case msg := <-messages: fmt.Println("received message", msg) default: fmt.Println("no message received") } msg := "hi" // 非阻塞发送,这里msg不能发送到message通道中,因为这是一个非缓冲区通道,同时通道没有接收器,所以执行default select { case messages <- msg: fmt.Println("sent message", msg) default: fmt.Println("no message sent") } // 在default之上实现多路非阻塞select select { case msg := <-messages: fmt.Println("received message", msg) case sig := <-signals: fmt.Println("received signal", sig) default: fmt.Println("no activity") } }

30-closing-channel 阅读更多

// 关闭通道表示不再会有值传递给它,这对于与通道的接收器进行通信完成非常有用。 package main import "fmt" func main() { jobs := make(chan int, 5) // 通道jobs在main goroutine与worker goroutine之间通讯,当没有jobs之后将关闭通道 done := make(chan bool) // 这里是worker goroutine go func() { for { // 不断的从通道中获取值 j, more := <-jobs // 在这种特殊的二值形式中,如果通道已经关闭且其中的值都已经被接收,那么more将会接收到false值 if more { fmt.Println("received job", j) } else { fmt.Println("received all jobs") done <- true // 完成所有操作后通过done通道来发出通知 return } } }() for j := 0; j < 3; j++ { // 发送3个job到jobs通道中,然后把通道关闭 jobs <- j fmt.Println("send job", j) } close(jobs) fmt.Println("send all jobs") <-done }

31-range-over-channels 阅读更多

// 使用for和range遍历基础数据结构,使用同样的句法遍历通道中的值 package main import "fmt" func main() { queue := make(chan string, 2) // 在queue通道中遍历这两这个值 queue <- "one" queue <- "two" close(queue) //range遍历从queue通道中接收到的每一个元素 for elem := range queue { // 因为在上面的代码中将通道关闭了,遍历将在获取到2个元素之后终止 fmt.Println(elem) } } // 非空通道可以被关闭,被关闭后仍然可以接收剩余的值

32-timers 阅读更多

// 我们通常需要在未来的某些时间点执行Go代码或者在某个时间段内重复 // Golang内置了timer(计时器)和ticker(断续器),使得上述目的很容易实现 package main import ( "fmt" "time" ) func main() { // Timer代表未来的某个时间 timer1 := time.NewTimer(2 * time.Second) //告诉timer等待的时长,然后提供一个时间到达后进行通知的通道 <-timer1.C // timer的通道C被<-timer1.C阻塞,直到通道内被传入一个表示时间到期的值 fmt.Println("Timer 1 expired") // 如果只是想要等待,可以使用time.Sleep // 使用Timer的好处是,可以在时间过期之前取消它 timer2 := time.NewTimer(time.Second) go func() { <-timer2.C fmt.Println("Timer 2 expired") }() stop2 := timer2.Stop() if stop2 { fmt.Println("Timer 2 stopped") } } // 第一个Timer将在程序运行2秒后过期,第二个Timer将会被停止而没有机会过期

33-tickers 阅读更多

// Timer(计时器)用于在未来的执行一次某件事 // Ticker(断续器)用于定期重复做某件事 package main import ( "fmt" "time" ) func main() { // 类似Timer的机制,使用一个通道来传递值 ticker := time.NewTicker(500 * time.Millisecond) // 使用通道内置的range每个500毫秒遍历一下进入通道中的值 done := make(chan bool) go func() { for { select { case <-done: return case t := <-ticker.C: fmt.Println("Tick at", t) } } }() time.Sleep(3600 * time.Millisecond) ticker.Stop() // ticker可以像timer那样被停止,一旦ticker被停止就不会在接收任何值传入通道中 done <- true fmt.Println("Ticker stopped") }

34-worker-pools 阅读更多

// 使用goroutine和通道实现worker pool package main import ( "fmt" "time" ) // 运行多个并发的实例 // 从jobs通道中获取工作,并将结果写入result通道中 // 每个工作都会睡眠一秒钟来模拟执行任务 func worker(id int, jobs <-chan int, result chan<- int) { for j := range jobs { fmt.Println("worker", id, "started job", j) time.Sleep(time.Second) fmt.Println("worker", id, "finished job", j) result <- j * 2 } } func main() { // 创建两个通道用于发送jobs和接收结果 jobs := make(chan int, 100) results := make(chan int, 100) // 启动3个worker,最后会被阻塞,因为jobs通道为空 for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 向jobs通道中传入5个值,然后将通道关闭 for j := 1; j <= 5; j++ { jobs <- j } close(jobs) // 收集所有工作的结果,这也能确保worker的goroutine已经完成 for a := 1; a <= 5; a++ { <-results } } // 等待多个goroutine的另一种方法是使用WaitGroup

35-waitGroup 阅读更多

// 要等待多个goroutine完成,可以使用WaitGroup package main import ( "fmt" "sync" "time" ) // 在每个goroutine中运行的函数 // 注意必须通过指针将WaitGroup传递给函数 func worker(id int, wg *sync.WaitGroup) { fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) //Sleep函数模拟执行任务 fmt.Printf("Worker %d done\n", id) wg.Done() // 通知WaitGroup工作已经完成 } func main() { var wg sync.WaitGroup // 此WaitGroup用于等待此处启动的所有goroutine完成 for i := 1; i < 5; i++ { // 启动多个goroutine,并为每个goroutine增加WaitGroup计数器 wg.Add(1) go worker(i, &wg) } wg.Wait() // 阻塞,知道WaitGroup计数器返回0,即所以goroutine通知他们已经完成 }

36-rate-limit 阅读更多

// 速率限制是控制资源利用和维持服务质量的重要机制。 // Golang优雅的支持 goroutine、通道和断续器(ticker)的速率限制 package main import ( "fmt" "time" ) func main() { // 基本的速率限制 requests := make(chan int, 5) // 通过同名的通道来提供对传入请求的处理限制 for i := 1; i <= 5; i++ { requests <- i } close(requests) limiter := time.Tick(200 * time.Millisecond) // limiter通道每200毫秒接收一个值,这是速率限制机制中的调节器 // 在处理每个请求之前,通过limiter通道来阻塞请求,并限制每200毫秒一个请求 for req := range requests { <-limiter fmt.Println("request", req, time.Now()) } fmt.Println() // 在速率限制方案中允许短时间的突发请求,同时保留总体的速率限制 // 可以通过缓冲 limiter 通道来达到这个目的 // burstyLimiter 通道允许最多3个突发事件 burstyLimiter := make(chan time.Time, 3) // 填充通道以表示允许的突发请求 for i := 0; i < 3; i++ { burstyLimiter <- time.Now() } go func() { // 每200毫秒向burstyLimiter添加一个值,它的最大限制是3 for t := range time.Tick(200 * time.Millisecond) { burstyLimiter <- t } }() // 模拟传入了5个请求,其中前3个将受益于burstyLimiter的突发处理能力 burstyRequests := make(chan int, 5) for i := 1; i < =5; i++ { burstyRequests <- i } close(burstyRequests) for req := range burstyRequests { <-burstyLimiter fmt.Println("request", req, time.Now()) } } // 在第二批的请求中,有突发事件的处理机制,所以前3个会立即被处理,然后在每200毫米处理后2个请求

37-atomic-counters 阅读更多

// Golang中管理状态的首要机制是通过通道进行通信 // 使用 sync/atomic 包中的原子计数器实现多个goroutine的访问 package main import ( "fmt" "sync" "sync/atomic" ) func main() { var ops uint64 // 使用无符号整数表示计数器 var wg sync.WaitGroup // WaitGroup将会等待所以goroutine完成任务 for i := 0; i < 50; i++ { // 启动50个goroutine,每个都会将计数器增加1000 wg.Add(1) go func() { for c := 0; c < 1000; c++ { // 使用AddUint64函数以原子的方式增加计数器的值 // 使用&语法将ops变量的内存地址传递给它 atomic.AddUint64(&ops, 1) } wg.Done() }() } wg.Wait() // 等待所以goroutine完成 // 现在访问ops是安全的,因为已经没有其他的goroutine在写它 fmt.Println("ops:", ops) // 使用atomic.LoadUint64等函数可以在更新数据时进行安全的读取 }

38-mutexes 阅读更多

// 对于简单的计数器状态可以通过 atomic 操作来控制 // 对于更复杂的状态,可以使用互斥锁(mutex)安全地访问多个goroutine中的数据 package main import ( "fmt" "math/rand" "sync" "sync/atomic" "time" ) func main() { var state = make(map[int]int) // 创建一个map表示状态(state) var mutex = &sync.Mutex{} // 互斥锁会同步对状态的访问 // 用于追踪读写的操作数量 var readOps uint64 var writeOps uint64 for r := 0; r < 100; r++ { // 启动100个goroutine,执行针对状态的重复读取 go func() { total := 0 for { key := rand.Intn(5) // 对于每次读取,生成一个访问key mutex.Lock() // 锁定互斥锁,以确保对状态的独占访问 total += state[key] // 从map中读取所选key对应的值 mutex.Unlock() // 释放互斥锁 atomic.AddUint64(&readOps, 1) // 增加readOps计数 time.Sleep(time.Millisecond) // 每个goroutine每毫秒执行一次操作 } }() } for w := 0; w < 10; w++ { // 启动10个goroutine来模拟写入操作,处理模式与读取操作相同 go func() { for { key := rand.Intn(5) val := rand.Intn(100) mutex.Lock() state[key] = val mutex.Unlock() atomic.AddUint64(&writeOps, 1) time.Sleep(time.Millisecond) } }() } time.Sleep(time.Second) // 等待上述读写操作工作1秒钟 // 获取1秒内的读取和写入的操作次数 readOpsFinal := atomic.LoadUint64(&readOps) fmt.Println("readOps:", readOpsFinal) writeOpsFinal := atomic.LoadUint64(&writeOps) fmt.Println("writeOps:", writeOpsFinal) // 锁定状态并输出状态中的值 mutex.Lock() fmt.Println("state:", state) mutex.Unlock() } // 运行结果表面,针对互斥同步状态在一秒内大概执行了9万次操作

39-stateful-goroutine 阅读更多

// 在前面的例子中,使用显示定义的互斥锁来跨多个goroutine同步对共享状态的访问。 // 另一种方式是使用goroutine和通道内置的同步功能来实现相同的效果, // 这种基于通道的方式和Go共享内存的想法一致,通过通信使每块数据只被一个goroutine拥有。 package main import ( "fmt" "math/rand" "sync/atomic" "time" ) // 在这个例子中,状态只会被一个goroutine拥有, // 这将保证数据永远不会因为并发访问而损坏。 // 为了读取或者写入状态,其他的goroutine需要发送消息给状态的持有goroutine并收到相应的响应 // readOp和WriteOp结构体封装了这些请求和持有状态的goroutine的一个响应方法 type readOp struct { key int resp chan int } type writeOp struct { key int val int resp chan bool } func main() { // 记录总共执行的读取和写入次数 var readOps uint64 var writeOps uint64 // 读取和写入通道将被其他的goroutine使用来分别发出读取或写入请求 reads := make(chan readOp) writes := make(chan writeOp) // 这是持有状态的goroutine go func() { // goroutine私有的状态map var state = make(map[int]int) for { // 这个goroutine不断的从读取和写入通道中进行选择,并响应获取到的结果 select { case read := <-reads: read.resp <- state[read.key] case write := <-writes: state[write.key] = write.val write.resp <- true } } }() for r := 0; r < 100; r++ { // 启动100个goroutine通过reads通道向持有状态的goroutine发送读取请求 go func() { // 每次读取请求都会构造一个readOp结构,并通过reads通道进行发送,然后通过resp通道获取结果 for { read := readOp{ key: rand.Intn(5), resp: make(chan int)} reads <- read <-read.resp atomic.AddUint64(&readOps, 1) time.Sleep(time.Millisecond) } }() } for w := 0; w < 10; w++ { // 启动10个goroutine进行写入请求 go func() { for { write := writeOp{ key: rand.Intn(5), val: rand.Intn(100), resp: make(chan bool)} writes <- write <-write.resp atomic.AddUint64(&writeOps, 1) time.Sleep(time.Millisecond) } }() } // 等待上述goroutine执行一秒钟 time.Sleep(time.Second) // 捕获并返回操作的总次数 readOpsFinal := atomic.LoadUint64(&readOps) fmt.Println("readOps:", readOpsFinal) writeOpsFinal := atomic.LoadUint64(&writeOps) fmt.Println("writeOps:", writeOpsFinal) } // 程序运行结果表明,给予goroutine的状态管理完成了10万次操作 // 这种情况下,基于goroutine的方法比基于互斥锁的方法更复杂一些, // 在某些情况下它可能很有用,例如涉及多个管道或管理多个互斥锁时容易出错。

40-sorting 阅读更多

// Go的sort包实现了内置和用户定义类型的排序。 package main import ( "fmt" "sort" ) func main() { // sort函数对于内置类型有特定方法 // 对于切片使用就地排序,不会生成新切片 strs := []string{"c", "b", "a"} sort.Strings(strs) fmt.Println("Strings:", strs) ints := []int{7, 2, 5} sort.Ints(ints) fmt.Println("Ints:", ints) // 判断给定切片是否 已经升序排列 s := sort.IntsAreSorted(ints) fmt.Println("Sorted:", s) }

41-sorting-by-func 阅读更多

// 如何对集合中的数据进行非自然顺序的排序。 // 假设要对string更具长度而非字母顺序排序。 package main import ( "fmt" "sort" ) // 为了按Go中自定义函数排序,需要一个相应的类型 type byLength []string // 创建一个byLength类型,它只是内置类型[]string的别名 // 在自定义类型上实现sort.Interface中的len、Less、Swap方法 // 这样就可以使用sort包的泛型Sort函数 func (a byLength) Len() int { return len(a) } // Swap和Less在不同的类型之间都是很相似的 func (a byLength) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // Less中写的是实际的自定义排序逻辑 // 在我们的例子中,根据string的长度进行升序排序,所以使用len()进行长度比较 func (a byLength) Less(i, j int) bool { return len(a[i]) < len(a[j]) } func main() { fruits := []string{"peach", "banana", "kiwi"} // 将原始的fruits切片转换为buLength类型, // 然后在该类型上使用sort.Sort函数来实现自定义排序 sort.Sort(byLength(fruits)) fmt.Println(fruits) } // 通过遵循这种模式来创建自定义类型, // 并在该类型上实现sort.Interfacce的三个接口方法, // 然后在该自定义类型的集合上调用sort.Sort函数, // 可以按照任意方式对Go切片进行排序

42-panic 阅读更多

// panic通常表示一些意料之外的错误。 // 大多数情况下,我们使用它来快速应对在正常操作期间不应该发生的错误或者不准备优雅地处理的错误。 package main import ( "os" ) func main() { // 使用panic检查意料之外的错误 panic("a problem") // panic的常见用法是一个函数返回了一个不知道如何处理或者不想处理的错误值时直接终止 _, err := os.Create("/tmp/file") if err != nil { panic(err) } } // 运行程序将会出发panic,打印错误消息和goroutine追踪信息,然后以非零状态退出

43-defer 阅读更多

// Defer 用于确保稍后在程序执行执行函数调用,通常用于清理目的。 // 在其他编程语言中,defer通常用于ensure或者finally中。 package main import ( "fmt" "os" ) func main() { // 使用createFile获取一个文件对象后, f := createFile("./43-defer.txt") // f = createFile("/defer.txt"),运行后将会报错 // 立即使用defer和closeFile将该文件关闭, // 这将在writeFile函数执行完成后,在封闭函数main的末尾执行 defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File) { fmt.Println("writing") fmt.Fprintf(f, "The file is generated by 43-defer.go.") } func closeFile(f *os.File) { fmt.Println("closing") // 关闭文件是检查错误很重要 // 即使是在defer中也是如此 err := f.Close() if err != nil { fmt.Fprintf(os.Stderr, "error:%v\n", err) os.Exit(1) } } // 运行程序,确定文件在写入后被关闭

44-collection-func 阅读更多

// 我们经常需要我们的程序对数据集合执行操作, // 例如 1.选择满足给定谓词的所有项目 // 2.使用自定义函数将所有项目映射到新集合。 // 在某些编程语言中,使用generic数据结构和算法 // Go不支持generics,在Go中,如果程序或者数据类型特别需要,通常会提供集合函数 // 以下是一些字符串切片的集合函数示例, // 可以使用以下示例构建自己的函数 // 请注意,在某些情况下,最简单的方法是直接内联集合操作代码,而不是创建和调用辅助函数 package main import ( "fmt" "strings" ) // Index 返回目标字符串t的第一个索引,如果没有匹配到则返回 -1 func Index(vs []string, t string) int { for i, v := range vs { if v == t { return i } } return -1 } // Include 返回 true 如果目标字符串 t 存在于切片中 func Include(vs []string, t string) bool { return Index(vs, t) >= 0 } // Any 返回 true 如果切片中有一个字符串满足函数 f func Any(vs []string, f func(string) bool) bool { for _, v := range vs { if f(v) { return true } } return false } // All 返回 true 如果切片中所有的字符串都满足函数 f func All(vs []string, f func(string) bool) bool { for _, v := range vs { if !f(v) { return false } } return true } // Filter 返回一个新的切片,新切片中包含原切片中所有满足函数 f 的字符串 func Filter(vs []string, f func(string) bool) []string { vsf := make([]string, 0) for _, v := range vs { if f(v) { vsf = append(vsf, v) } } return vsf } // Map 返回一个新的切片,其中包含将函数 f 应用与原始切片中每个字符串的结果 func Map(vs []string, f func(string) string) []string { vsm := make([]string, len(vs)) for i, v := range vs { vsm[i] = f(v) } return vsm } func main() { var strs = []string{"peach", "apple", "pear", "plum"} // 尝试各种集合函数 fmt.Println(Index(strs, "pear")) fmt.Println(Include(strs, "grape")) fmt.Println(Any(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(All(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(Filter(strs, func(v string) bool { return strings.Contains(v, "e") })) fmt.Println(Map(strs, strings.ToUpper)) // 上述例子中使用的都是匿名函数,也可以使用正确类型的命名函数 }

45-string-func 阅读更多

// 标准库strings包提供了很多有用的字符串相关函数。 package main import ( "fmt" s "strings" ) // 为 fmt.Println 创建别名 p var p = fmt.Println func main() { // 这些都是strings包的函数,而不是字符串对象自身的方法 // 需要将待处理的字符串作为第一个参数传递给函数 p("Contains:\t", s.Contains("test", "es")) p("Count:\t\t", s.Count("test", "t")) p("HasPrefix:\t", s.HasPrefix("test", "te")) p("HasSuffix:\t", s.HasSuffix("test", "st")) p("Index:\t\t", s.Index("test", "e")) p("Join:\t\t", s.Join([]string{"a", "b"}, "-")) p("Repeat:\t\t", s.Repeat("a", 5)) p("Replace:\t", s.Replace("foo", "o", "0", -1)) p("Split:\t\t", s.Split("a-b-c-d-e", "-")) p("ToLower:\t", s.ToLower("TEST")) p("ToUpper:\t", s.ToUpper("test")) p() // 以下函数不是strings包中的函数 // 以字节的形式获取字符串的长度 p("Len:\t\t", len("hello")) // 以字节的形式获取字节在字符串中的索引 p("Char:\t\t", "hello"[1]) // 需要注意的是,上面两个函数都是工作中字节级别的 // Go使用UTF-8编码字符串,所以这两个函数通常很有用 // 如果正在使用可能的多字节字符,则需要使用编码感知操作,查看这片博文 https://blog.golang.org/strings }

46-string-formating 阅读更多

// Go在printf中对字符串的格式化输出的支持非常好。 package main import ( "fmt" "os" ) type point struct { x, y int } func main() { p := point{1, 2} // Go提供多种打印动词来格式化一般的Go值 fmt.Printf("%v\n", p) // 打印point结构体 fmt.Printf("%+v\n", p) // 如果打印的值是结构体,%+v将会输出结构体的字段名 fmt.Printf("%#v\n", p) // %#v将会输出一段Go语法来表示该值,例如生成该值的源代码片段 fmt.Printf("%T\n", p) // 打印值的类型 fmt.Printf("%t\n", true) // 格式化布尔值 fmt.Printf("%d\n", 123) // 有多种格式化输出整数的方式,%d是标准的十进制格式化输出 fmt.Printf("%b\n", 14) // 输出二进制形式 fmt.Printf("%c\n", 33) // 输出与给定整数对应的字符 fmt.Printf("%x\n", 456) // 输出十六进制编码 fmt.Printf("%f\n", 78.9) //有多种格式化输出浮点数的方式,%f是标准的十进制格式化输出 // 以科学计数法的形式输出浮点数,%e和%E略有不同 fmt.Printf("%e\n", 123400000.0) fmt.Printf("%E\n", 123400000.0) fmt.Printf("%s\n", "\"string\"") // 输出字符串使用%s fmt.Printf("%q\n", "\"string\"") // 像源码一样双引号引其字符串使用%q fmt.Printf("%x\n", "hex this") // 像整数一样,以十六进制形式输出字符串,每个输入字节有两个输出字符 fmt.Printf("%p\n", &p) // %p用于输出指针 // 格式化数字时,通常需要控制结果图的宽度和精度 // 要指定整数的宽度,在%后加上数字 // 默认情况下,结果右对齐,并使用空格填充 fmt.Printf("|%6d|%6d|\n", 12, 345) fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45) // 指定浮点数宽度的同时限定小数点的精度 fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45) // 使用 - 符号实现左对齐 // 输出字符串时控制宽度,默认为右对齐 fmt.Printf("|%6s|%6s|\n", "foo", "b") fmt.Printf("|%-6s|%-6s|\n", "foo", "b") // 以上都是Printf将格式化的内容输出到os.Stdout // Sprintf格式化并返回一个字符串而不在任何地方输出它 s := fmt.Sprintf("a %s", "string") fmt.Println(s) // 使用Fprintf格式化并打印到os.Stdout以外的io.Writers fmt.Fprintf(os.Stderr, "an %s\n", "error") }

47-regular-expressions 阅读更多

// Go内置支持正则表达式 package main import ( "bytes" "fmt" "regexp" ) func main() { match, _ := regexp.MatchString("p([a-z]+)ch", "peach") // 判断正则表达式是否与字符串匹配 fmt.Println(match) // 编译一个优化的正则表达式结构,这个结构有许多方法可用 r, _ := regexp.Compile("p([a-z]+)ch") fmt.Println(r.MatchString("peach")) // 判断正则表达式是否与字符串匹配 fmt.Println(r.FindString("peach punch")) // 查找正则表达式的匹配项 fmt.Println(r.FindStringIndex("peach punch")) // 查找第一个与正则表达式匹配的项,返回匹配的开始和结束索引而不是匹配到的文本 fmt.Println(r.FindStringSubmatch("peach punch")) // 返回整个匹配模式和子匹配的信息,如p([a-z]+)ch和([a-z]+) fmt.Println(r.FindStringSubmatchIndex("peach punch")) // 返回整个匹配模式和子匹配的索引 fmt.Println(r.FindAllString("peach punch pinch", -1)) // 返回正则表达式的所有匹配项 fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch", -1)) // 返回正则表达式的所有匹配项的整个匹配模式和子匹配模式的索引 fmt.Println(r.FindAllString("peach punch pinch", 2)) // 提供非负整数作为第二个参数,来限制正则表达式的匹配数量 fmt.Println(r.Match([]byte("peach")) // 以字节形式匹配正则表达式 // 使用正则表达式常见常量时,可以使用Compile的变体MustCompile // 普通的Compile不适用于常量,因为它有2个返回值 r = regexp.MustCompile("p([a-z]+)ch") fmt.Println(r) fmt.Println(r.ReplaceAllString("a peach", "<fruit>")) // 将匹配的字符串子集替换为其他值 in := []byte("a peach") out := r.ReplaceAllFunc(in, bytes.ToUpper) // 使用给定函数转换匹配的文本 fmt.Println(string(out)) }

48-json 阅读更多

// Go提供对JSON编码和解码的内置支持,包括内置和自定义类型 package main import ( "encoding/json" "fmt" "os" ) // 使用下面的两个结构体来演示编码和解码自定义类型 type response1 struct { Page int Fruits []string } // 只有导出字典才会被编码/解码为JSON // 导出字段首字母大写 type response2 struct { Page int `json:"page"` Fruits []string `json:"fruits"` } func main() { // 一些原子类型编码为JSON字符串 bolB, _ := json.Marshal(true) fmt.Println(string(bolB)) intB, _ := json.Marshal(1) fmt.Println(string(intB)) fltB, _ := json.Marshal(2.34) fmt.Println(string(fltB)) strB, _ := json.Marshal("gopher") fmt.Println(string(strB)) // 将切片和map编码为JSON数组和对象 slcD := []string{"apple", "peach", "pear"} slcB, _ := json.Marshal(slcD) fmt.Println(string(slcB)) mapD := map[string]int{"apple": 5, "lettuce": 7} mapB, _ := json.Marshal(mapD) fmt.Println(string(mapB)) // JSON包可以自动编码自定义数据类型 // 它只包含编码输出中的导出字段,默认情况下将这些名称作为JSON的键 res1D := &response1{ Page: 1, Fruits: []string{"apple", "peach", "pear"}} res1B, _ := json.Marshal(res1D) fmt.Println(string(res1B)) // 可以在结构体的字段声明上使用标记来自定义编码成的JSON的键的名称 res2D := &response2{ Page: 1, Fruits: []string{"apple", "peach", "pear"}} res2B, _ := json.Marshal(res2D) fmt.Println(string(res2B)) // 将JSON数据解码为Go中的值 byt := []byte(`{"num":6.13,"strs":["a","b"]}`) // 提供一个变量用于放置JSON包解码出的数据 // 此map将存储键为string值为任意类型的数据 var dat map[string]interface{} // 进行解码并检查相关的错误 if err := json.Unmarshal(byt, &dat); err != nil { panic(err) } fmt.Println(dat) // 为了使用解码后的map中的值,需要将它们转换为合适的类型 num := dat["num"].(float64) fmt.Println(num) //访问嵌套数据需要进行一些列转换 strs := dat["strs"].([]interface{}) str1 := strs[0].(string) fmt.Println(str1) // 将JSON解码为自定义数据类型 // 这样做的好处是可以为程序增加额外的类型安全性 // 并且在访问解码数据时不需要类型断言 str := `{"page":1,"fruits":["apple","peach"]}` res := response2{} json.Unmarshal([]byte(str), &res) fmt.Println(res) fmt.Println(res.Fruits[0]) // 上面的例子中,使用字节和字符串作为标准输出和JSON之间的媒介 // 还可以将JSON编码直接流式传输到os.Writers // 如: 1. os.Stdout // 2. HTTP响应体 enc := json.NewEncoder(os.Stdout) d := map[string]int{"apple": 5, "lettuce": 7} enc.Encode(d) }

49-xml 阅读更多

// Go使用encoding.xml包提供对XML和类XML格式的内置支持。 package main import ( "encoding/xml" "fmt" ) // Plant 结构体将被映射为XML // 与JSON示例类似,字段标记包含编辑器和解码器的指令 // 使用XML包的一些特殊功能: // 1. XMLName字段名称表示此结构的XML元素的名称 // 2. id,attr表示id字段是XML的属性而不是嵌套元素 type Plant struct { XMLName xml.Name `xml:"plant"` ID int `xml:"id,attr"` Name string `xml:"name"` Origin []string `xml:"origin"` } func (p Plant) String() string { return fmt.Sprintf("Plant id = %v, name = %v, origin = %v", p.ID, p.Name, p.Origin) } func main() { coffee := &Plant{ID: 27, Name: "Coffee"} coffee.Origin = []string{"Ethiopia", "Brazil"} //使用MarshalIndent来生成更易读取的输出 out, _ := xml.MarshalIndent(coffee, " ", " ") fmt.Println(string(out)) // 要将通用XML标头添加到输出,需要显式添加它 fmt.Println(xml.Header + string(out)) // 使用Unmarshal将带有XML的字节流解析为数据结构 // 如果XML格式错误或无法映射到Plant,将返回错误性描述 var p Plant if err := xml.Unmarshal(out, &p); err != nil { panic(err) } fmt.Println(p) tomato := &Plant{ID: 81, Name: "Tomato"} tomato.Origin = []string{"Mexico", "California"} type Nesting struct { XMLName xml.Name `xml:"nesting"` Plants []*Plant `xml:"parent>child>plant` // parent> child> plant field标记告诉编码器将所有植物嵌套在<parent> <child>下 } nesting := &Nesting{} nesting.Plants = []*Plant{coffee, tomato} out, _ = xml.MarshalIndent(nesting, " ", " ") fmt.Println(string(out)) }

50-time 阅读更多

// Go为times和durations提供广泛支持 package main import ( "fmt" "time" ) func main() { p := fmt.Println now := time.Now() // 获取当前时间 p(now) // 可以通过提供年月日等来构建时间结构,TImes总是与位置有关,如时区 then := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) p(then) // 可以提取时间值的各个组件 p(then.Year()) p(then.Month()) p(then.Day()) p(then.Hour()) p(then.Minute()) p(then.Second()) p(then.Nanosecond()) p(then.Location()) // 获取星期几 p(then.Weekday()) // 比较两个时间,比较前一个值与后一个值的前、后、相等关系 p(then.Before(now)) p(then.After(now)) p(then.Equal(now)) // Stub方法返回两个时间之间的时间间隔,默认以小时为单位 diff := now.Sub(then) p(diff) // 可以以各种单位计算持续时间的长度 p(diff.Hours()) p(diff.Minutes()) p(diff.Seconds()) p(diff.Nanoseconds()) // 使用Add根据给定的时间间隔向来前推进时间 // 时间间隔前面加上负号(-)来向后推移时间 p(then.Add(diff)) p(then.Add(-diff)) }

51-epoch 阅读更多

// 程序中的一个常见要求是获取自Unix时代以来的秒数,毫秒数或纳秒数。 package main import ( "fmt" "time" ) func main() { now := time.Now() // 获取当前时间 secs := now.Unix() // 将当前时间修改为从Unix时代以来的秒数 nanos := now.UnixNano() // 将当前时间修改为从Unix时代以来的纳秒数 fmt.Println(now) // 没有UnixMillis,所以需要手动除以纳秒来获取 millis := nanos / 1000000 fmt.Println(secs) fmt.Println(millis) fmt.Println(nanos) // 将自Unix时代以来的整秒数或纳秒数转换为相应的时间 fmt.Println(time.Unix(secs, 0)) fmt.Println(time.Unix(0, nanos)) }

52-time-format-parse 阅读更多

// Go支持通过基于模式设计的时间格式化和解析。 package main import ( "fmt" "time" ) func main() { p := fmt.Println t := time.Now() // 根据RFC3339规范格式化时间 p(t.Format(time.RFC3339)) // 根据RFC3339规范解析时间 t1, _ := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00") p(t1) // 根据自定义样式格式化时间 p(t.Format("3:04PM")) p(t.Format("Mon Jan _2 15:04:05 2006")) p(t.Format("2006-01-02T15:04:05.999999-07:00")) // 根据自定义样式解析时间 form := "3 04 PM" t2, _ := time.Parse(form, "8 41 PM") p(t2) // 对于纯数字的表示可以使用标准格式化输出和时间提取组件 fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) // 格式解析错误 ansic := "Mon Jan _2 15:04:05 2006" _, e := time.Parse(ansic, "8:41PM") p(e) // parsing time "8:41PM" as "Mon Jan _2 15:04:05 2006": cannot parse "8:41PM" as "Mon" }

53-random-number 阅读更多

// Go的math/rand包提供伪随机数生成。 package main import ( "fmt" "math/rand" "time" ) func main() { fmt.Print(rand.Intn(100), ",") // rand.Intn(100)返回0<=n<100的随机整数 fmt.Print(rand.Intn(100)) fmt.Println() fmt.Println(rand.Float64()) // rand.Float64()返回0.0<=f<1.0的64位浮点数 fmt.Print((rand.Float64()*5)+5, ",") fmt.Print((rand.Float64() * 5) + 5) fmt.Println() // 默认的数字生成器具有确定性,因此默认情况下每次都会产生相同的数字序列 // 为了生成不同的序列,提供一个变化的种子 s1 := rand.NewSource(time.Now().UnixNano()) r1 := rand.New(s1) // 对于需要保密的随机数,上述方式是不安全的,可以使用crypto/rand包 // 调用rand.Rand的结果就像调用rand包的函数那样 fmt.Print(r1.Intn(100), ",") fmt.Print(r1.Intn(100)) fmt.Println() // 如果提供相同的种子,那么会生成一样是随机数序列 s2 := rand.NewSource(42) r2 := rand.New(s2) fmt.Print(r2.Intn(100), ",") fmt.Print(r2.Intn(100)) fmt.Println() s3 := rand.NewSource(42) r3 := rand.New(s3) fmt.Print(r3.Intn(100), ",") fmt.Print(r3.Intn(100)) }

54-number-parse 阅读更多

// 从字符串中解析数字是许多程序中基本且常见的任务。 package main import ( "fmt" "strconv" // 内置标准库strconv提供数字解析功能 ) func main() { p := fmt.Println f, _ := strconv.ParseFloat("1.234", 64) // 64表示要解析的精度位数 p(f) i, _ := strconv.ParseInt("123", 0, 64) // 0表示从字符串的前缀推断出基数 p(i) d, _ := strconv.ParseInt("0x1c8", 0, 64) // ParseIntn()能够识别十六进制的数字 p(d) u, _ := strconv.ParseUint("789", 0, 64) p(u) k, _ := strconv.Atoi("135") // Atoi()是解析十进制的快捷函数 p(k) _, e := strconv.Atoi("wat") //对于错误输入返回一个错误 p(e) }

55-URL-parse 阅读更多

// URLs提供了一种统一的资源定位方式。 package main import ( "fmt" "net" "net/url" ) func main() { p := fmt.Println // 解析此URL,其中包含scheme、认证信息、主机、端口、路径、请求参数和请求片段 s := "postgres://user:pass@host.com:5432/path?k=v#f" // 解析URL并检查是否存在错误 u, err := url.Parse(s) if err != nil { panic(err) } p(u.Scheme) // 获取scheme p(u.User) // User中包含全部的认证信息 // 使用Password()和Username()来获取单独的值 pw, _ := u.User.Password() p(pw) p(u.Host) host, port, _ := net.SplitHostPort(u.Host) p(host) p(port) // 获取路径和#后面的请求片段 p(u.Path) p(u.Fragment) p(u.RawQuery) // 以k=v的字符串形式获取请求参数 // 将请求参数解析为map // 解析后的请求参数的map是map[string]slice形式 m, _ := url.ParseQuery(u.RawQuery) p(m) // 如果只需要第一个值,则索引0即可 p(m["k"][0]) }

56-SHA1-hashes 阅读更多

// SHA1哈希经常用于计算二进制或者文本blob的短标识, // 例如Git就使用SHA1来标识版本化的文件和目录。 package main import ( "crypto/sha1" "fmt" ) func main() { s := "sha1 this string" // 生成散列的模式是: // 1. sha1.New() // 2. sha1.Write(bytes) // 3. sha1.Sum([]byte{}) h := sha1.New() h.Write([]byte(s)) //将字符串强制类型转换为字节,[]byte(s),其中s为字符串 // 将最终的哈希结果作为字节切片 // 可以将Sum()方法的参数添加到一个已经存在的字节切片中,通常不需要使用它 bs := h.Sum(nil) // SHA1的值通常以十六进制的形式打印出来 // 使用%x格式化谓词将哈希的结果转换为十六进制的字符串 fmt.Println(s) fmt.Printf("%x\n", bs) } // 还有其他的计算哈希的方式,如MD5哈希, // 导入crypto/md5包,然后使用md5.New() // 如果需要加密安全的hash,那就要仔细研究一下哈希强度了。

57-Base64 阅读更多

// Go内置支持base64编解码。 package main import ( "encoding/base64" "fmt" ) func main() { data := "abc123!?$*&()'-=@~" // 编解码这个字符串 // Go支持标准的和URL兼容的Base64 // 使用标准编码器进行编码,编码器需要字节数组,所以将字符串转换为字节数组 sEnc := base64.StdEncoding.EncodeToString([]byte(data)) fmt.Println(sEnc) // 解码会返回错误,可用于检测输入格式是否正确 sDec, _ := base64.StdEncoding.DecodeString(sEnc) fmt.Println(string(sDec)) fmt.Println() // 使用与URL兼容的Base64编解码方式 uEnc := base64.URLEncoding.EncodeToString([]byte(data)) fmt.Println(uEnc) uDec, _ := base64.URLEncoding.DecodeString(uEnc) fmt.Println(string(uDec)) } // 对同一个字符串使用标准编码器和URL兼容的编码器进行编码等到的值略有不同(末尾是+或者-) // 但是都能根据需要解码为原始字符串

58-read-file 阅读更多

// 读取和写入文件是Go程序要完成的基本任务。 package main import ( "bufio" "fmt" "io" "io/ioutil" "os" ) // 读取文件需要检查大部分调用错误, // check函数有助于我们精简代码 func check(err error) { if err != nil { panic(err) } } func main() { // 读取文件最基本的操作是将整个文件个内容都放在内存中 dat, err := ioutil.ReadFile("./58-read-file") check(err) fmt.Println(string(dat)) // 控制文件的读取方式和读取位置 // 使用os.Open函数获取一个os.FIle返回值 f, err := os.Open("./58-read-file") check(err) // 从文件开头读取一些字节 // 创建一个byte切片来运行读取最多5个字节,但也需要注意实际读取了多少字节 b1 := make([]byte, 5) n1, err := f.Read(b1) check(err) fmt.Printf("%d bytes:%s\n", n1, string(b1[:n1])) // 搜索文件中的指定位置,并从那里读取 o2, err := f.Seek(6, 0) check(err) b2 := make([]byte, 2) n2, err := f.Read(b2) check(err) fmt.Printf("%d bytes @ %d:", n2, o2) fmt.Printf("%v\n", string(b2[:n2])) // io包提供一些读取文件的有用函数 // 例如使用ReadAtLast函数实现上述功能 o3, err := f.Seek(6, 0) check(err) b3 := make([]byte, 2) n3, err := io.ReadAtLeast(f, b3, 2) check(err) fmt.Printf("%d bytes @ %d:%s\n", n3, o3, string(b3)) // 没有内置的后退功能,使用Seek(0,0)来实现后退 _, err = f.Seek(0, 0) check(err) // bufio包实现了一个缓冲读取器,对于多个小的读取很高效,而且可以提供额外的读取方法 r4 := bufio.NewReader(f) b4, err := r4.Peek(5) // 根据参数值读取文件中的字节数而不修改读取器的位置 check(err) fmt.Printf("5 bytes: %s\n", string(b4)) // 文件操作完成后关闭文件 // 通常使用defer在打开文件操作后立即添加上关闭操作 f.Close() }

59-write-file 阅读更多

// 在Go中写入文件的方式和读取文件的方式类似。 package main import ( "bufio" "fmt" "io/ioutil" "os" ) func check(err error) { if err != nil { panic(err) } } func main() { // 将字符串或者字节写入到文件中 d1 := []byte("hello\ngo\n") err := ioutil.WriteFile("./59-write-file1", d1, 0644) check(err) // 要进行更精细的写入,那么就打开一个文件然后写入 f, err := os.Create("./59-write-file2") check(err) // 根据惯例打开一个文件后,立即使用defer关闭它 defer f.Close() // 将字节切片写入到文件中 d2 := []byte{115, 111, 109, 101, 10} n2, err := f.Write(d2) check(err) fmt.Printf("wrote %d bytes\n", n2) // 使用WriteString函数写入字符串到文件中 n3, err := f.WriteString("writes\n") fmt.Printf("wrote %d bytes\n", n3) // 启动一个Sync()将写入数据刷新到持久化存储中 f.Sync() // bufio也提供缓冲写入器 w := bufio.NewWriter(f) n4, err := w.WriteString("buffered\n") fmt.Printf("wrote %d bytes\n", n4) // 使用Flush()函数确保将所有缓冲操作都已经应用到底层的写入器 w.Flush() } // write-file1内容 hello go // write-file2内容 some writes buffered

60-line-filters 阅读更多

// 行过滤器是一种常见的程序类型,它读取stdin的输入,处理它,然后将一些派生结果打印到stdout // 常见的行过滤器有:grep和sed。 package main import ( "bufio" "fmt" "os" "strings" ) func main() { // 使用缓冲扫描器包装无缓冲的os.Stdin, // 以此来提供一种方便的扫描方法,将扫描器推进到下一个token(即默认扫描其中的下一行) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { ucl := strings.ToUpper(scanner.Text()) // Text函数返回当前token(在此处为输入的下一行) fmt.Println(ucl) } // 检查扫描中的错误 // 遇到文件结尾是很常见的,但在扫描时不会报错 if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } }

61-file-path 阅读更多

// filepath 包提供可以在操作系统之间移植的函数来解析或者构建file path // 如 : 1. Linux中的dir/file // 2. Windows中的dir\file。 package main import ( "fmt" "path/filepath" "strings" ) func main() { // 使用Join函数来快捷的构造路径 // 它接受任意数量的参数并从中构造分层路径 p := filepath.Join("dir1", "dir2", "filename") fmt.Println("p:", p) // 应该始终使用Join而不是手动连接 \s 或者 /s // 除了提供可移植性之外,Join还将通过删除多余的分隔符和目录来规范路径 fmt.Println(filepath.Join("dir1//", "filename")) fmt.Println(filepath.Join("dir1/../dir1", "filename")) // Dir和Base函数可以拆分目录和文件的路径 // split函数将在一次调用中把目录和文件两者的路径都返回 fmt.Println("Dir(p):", filepath.Dir(p)) fmt.Println("Base(p):", filepath.Base(p)) // 判断一个路径是否是绝对路径 fmt.Println(filepath.IsAbs("dir/file")) fmt.Println(filepath.IsAbs("/dir/file")) // 有些文件名在点后面后扩展名 // 可以使用Ext函数将这些名称拆开 filename := "config.json" ext := filepath.Ext(filename) fmt.Println(ext) // 要删除扩展名以查找文件名,使用strings.TrimSuffix函数 fmt.Println(strings.TrimSuffix(filename, ext)) // Rel返回基础路径与目标路径之间的相对路径 // 如果无法相对于基础路径生成目标路径那么返回错误 rel, err := filepath.Rel("a/b", "a/b/t/file") if err != nil { panic(err) } fmt.Println(rel) rel, err = filepath.Rel("a/b", "a/c/t/file") if err != nil { panic(err) } fmt.Println(rel) }

62-directories 阅读更多

// Go有几个有用的功能来处理文件系统中的目录。 package main import ( "fmt" "io/ioutil" "os" "path/filepath" ) func check(err error) { if err != nil { panic(err) } } func main() { // 在当前工作目录下创建一个新的子目录 err := os.Mkdir("subdir", 0755) check(err) // 在创建临时目录时,最好将其删除 // os.RemoveAll将会删除整个目录树,类似 rm -rf defer os.RemoveAll("subdir") // 辅助函数用于创建一个新的空文件 createEmptyFile := func(name string) { d := []byte("") check(ioutil.WriteFile(name, d, 0644)) } createEmptyFile("subdir/file1") err = os.MkdirAll("subdir/parent/child", 0755) check(err) createEmptyFile("subdir/parent/file2") createEmptyFile("subdir/parent/file3") createEmptyFile("subdir/parent/child/file4") // ReadDir函数会列出目录的内容,返回一个os.FileInfo对象组成的切片 c, err := ioutil.ReadDir("subdir/parent") check(err) fmt.Println("Listing subdir/parent") for _, entry := range c { fmt.Println("", entry.Name(), entry.IsDir()) } // Chdir用于切换当前工作目录,类型与cd命令 err = os.Chdir("subdir/parent/child") check(err) // 在列出当前目录时看到子目录 subdir/parent/child 的内容 c, err = ioutil.ReadDir(".") check(err) fmt.Println("Listing subdir/parent/child") for _, entry := range c { fmt.Println(" ", entry.Name(), entry.IsDir()) } // 回到开始的路径 err = os.Chdir("../../..") check(err) // 可以递归的访问目录,包括其中所以的子目录 // Walk函数接收回调函数来处理每个文件和目录的访问 fmt.Println("Visiting subdir") err = filepath.Walk("subdir", visit) } func visit(p string, info os.FileInfo, err error) error { if err != nil { return err } fmt.Println("", p, info.IsDir()) return nil }

63-temp-file-dir 阅读更多

// 在整个程序执行过程中,经常需要创建程序退出后不需要的数据 // 临时文件和目录对此非常有用,因为它们不会随着时间的推移而污染文件系统 package main import ( "fmt" "io/ioutil" "os" "path/filepath" ) func check(err error) { if err != nil { panic(err) } } func main() { // 创建临时文件的最便捷的方式就是调用ioutil.TempFile函数 // 它创建一个文件并打开它进行读写 // 给ioutil.TempFile函数传递的一个参数是空字符串,所以会操作系统的默认位置创建文件 f, err := ioutil.TempFile("", "sample") check(err) // 在类Unix操作系统上,目录可能是/tmp // 文件名以ioutil.TempFile的第二个参数作为前缀, // 其余部分自动选择以确保并发调用将始终创建不同的文件名。 fmt.Println("Temp file name", f.Name()) // 文件使用完成后清理文件, // 操作系统也可能在一段时间后自动清理临时文件 // 显式的执行清理操作是一个最佳实践 defer os.RemoveAll(f.Name()) // 向临时文件中写入一些数据 _, err = f.Write([]byte{1, 2, 3, 4}) check(err) // 如果要创建多个临时文件,可以先创建一个临时目录 // ioutil.TempDir的参数与ioutil.TempFIle的参数相同 // 只是返回的是目录名而不是一个文件名 dname, err := ioutil.TempDir("", "sampledir") fmt.Println("Temp dir name", dname) defer os.RemoveAll(dname) // 可以通过在临时目录前添加前缀来合成临时文件名。 fname := filepath.Join(dname, "file1") err = ioutil.WriteFile(fname, []byte{1, 2}, 0666) check(err) }

64-cmd-line-arg 阅读更多

// 命令行参数是参数化执行程序的常用方法。 // 例如: go run hello.go 使用run和hello.go 作为go的参数 package main import ( "fmt" "os" ) func main() { argsWithProg := os.Args // os.Args提供对原始命令行参数的访问 argsWithoutProg := os.Args[1:] // 请注意,此切片中的第一个值是程序的路径,os.Args [1:]保存程序的参数 arg := os.Args[3] // 使用正常的索引获取单个参数 fmt.Println(argsWithProg) fmt.Println(argsWithoutProg) fmt.Println(arg) }

65-cmd-line-flags 阅读更多

// 命令行标识是制定命令行程序选项的常用方法 // 例如: wc -l 其中 -l 就是一个命令行标识 package main import ( "flag" // Go提供flag包支持基本的命令行标识解析 "fmt" ) func main() { // 基本的标识声明可以用字符串、整数和布尔型 // 声明一个字符串型标识word,默认值为foo并带有一个简短的描述 // flag.String函数返回一个字符串指针(而不是一个字符串值) wordPtr := flag.String("word", "foo", "a string") numbPtr := flag.Int("numb", 42, "an int") boolPtr := flag.Bool("fork", false, "a bool") // 也可以使用一个程序中已经存在的变量来声明一个命令行标识 // 注意,西药传递一个指针给标识的声明函数 var svar string flag.StringVar(&svar, "svar", "bar", "a string var") // 一旦所以的标识都声明好,就可以调用flag.Parse函数来执行命令行解析 flag.Parse() // 请注意在输出时,需要取消指针引用,即获取指针的实际值 fmt.Println("word:", *wordPtr) fmt.Println("numb:", *numbPtr) fmt.Println("fork:", *boolPtr) fmt.Println("svar:", svar) fmt.Println("tail:", flag.Args()) } //请注意在命令行执行程序时,如果省略标识,则会自动采用其默认值 // 请注意,flag包要求所有命令行标识要在位置参数之前出现,否则标识会被解析为位置参数 // go run 65-cmd-line-flags.go -word=opt a1 a2 a3 -numb=7 // word: opt // numb: 42 // fork: false // svar: bar // tail: [a1 a2 a3 -numb=7] // 使用-h或--help标志可以获得命令行程序的自动生成的帮助文本 // go run 65-cmd-line-flags.go -h // Usage of /tmp/go-build690548213/b001/exe/65-cmd-line-flags: // -fork // a bool // -numb int // an int (default 42) // -svar string // a string var (default "bar") // -word string // a string (default "foo") // exit status 2 // 如果使用未提供给flag包的标识,程序将打印错误消息并再次显示帮助文本

66-cmd-line-subcmd 阅读更多

// 一些命令行工具,比如go工具或git有许多子命令,每个子命令都有自己的一组标志。 // 例如: go build 和 go get 是go工具的两个不同子命令 // 使用flag包可以轻松定义具有自己标志的简单子命令 package main import ( "flag" "fmt" "os" ) func main() { // 使用flag.NewFlagSet函数声明子命令 // 然后继续定义特定一该子命令的命令行标识 fooCmd := flag.NewFlagSet("foo", flag.ExitOnError) fooEnable := fooCmd.Bool("enable", false, "enable") fooName := fooCmd.String("name", "", "name") // 对于不同的子命令可以定义不同的命令行标识 barCmd := flag.NewFlagSet("bar", flag.ExitOnError) barLevel := barCmd.Int("level", 0, "level") // 子命令应该是程序的第一个参数 if len(os.Args) < 2 { fmt.Println("expected 'foo' or 'bar' subcommands'") os.Exit(1) } // 检查调用了哪个子命令 switch os.Args[1] { // 对于每个子命令,解析自己的命令行标识并访问尾随的位置参数 case "foo": fooCmd.Parse(os.Args[2:]) fmt.Println("subcommand 'foo'") fmt.Println("enable:", *fooEnable) fmt.Println("name:", *fooName) fmt.Println("tail:", fooCmd.Args()) case "bar": barCmd.Parse(os.Args[2:]) fmt.Println("subcommand 'bar'") fmt.Println("level:", *barLevel) fmt.Println("tail:", barCmd.Args()) default: fmt.Println("expected 'foo' or 'bar' subcommands") os.Exit(1) } }

67-env-var 阅读更多

// 环境变量是将配置信息传递给Unix程序的通用机制。 package main import ( "fmt" "os" "strings" ) func main() { os.Setenv("FOO", "1") // 使用os.Setenv来设置一个键值对 fmt.Println("FOO", os.Getenv("FOO")) // 使用os.Getenv获取给定键的值 fmt.Println("BAR", os.Getenv("BAR")) // 如果系统中没有设置这个值则会返回一个空的字符串 fmt.Println("") // os.Environ获取系统中全部环境变量的键值对 // 这将会返回一个key=value形式的字符串切片 for _, e := range os.Environ() { pair := strings.Split(e, "=") // 使用strings.Split将键和值分开 fmt.Println(pair[0]) } }

68-HTTP-client 阅读更多

// Go标准库为net/http包中的HTTP客户端和服务器提供了出色的支持 package main import ( "bufio" "fmt" "net/http" ) func main() { // 向HTTP服务器发出HTTP GET请求 // http.Get是创建http.Client对象并调用其Get方法的便捷方式 // 它使用http.DefaultClient对象,该对象具有有用的默认设置 resp, err := http.Get("http://gobyexample.com") if err != nil { panic(err) } defer resp.Body.Close() // 输出HTTP响应状态 fmt.Println("Response status:", resp.Status) scanner := bufio.NewScanner(resp.Body) // 输出响应体的前五行 for i := 0; scanner.Scan() && i < 5; i++ { fmt.Println(scanner.Text()) } if err := scanner.Err(); err != nil { panic(err) } }

69-HTTP-server 阅读更多

// 使用net/http包可以轻松编写基本的HTTP服务器 package main import ( "fmt" "net/http" ) // net/http 服务器的基本概念是handlers(处理器) // 一个处理器是一个实现http.Handler接口的对象 // 编写处理器的常用方法是在具有相应签名的函数上使用http.HandlerFunc适配器 func hello(w http.ResponseWriter, req *http.Request) { // 作为处理器的函数将http.ResponseWriter和http.Request作为参数 // 响应输出器用于填写HTTP响应 // 在这里只是响应"hello\n" fmt.Fprintf(w, "hello\n") } func headers(w http.ResponseWriter, req *http.Request) { // 这个处理器通过读取所有HTTP请求标头并将它们回显到响应主体来做一些更复杂的事情 for name, headers := range req.Header { for _, h := range headers { fmt.Fprintf(w, "%v:%v\n", name, h) } } } func main() { // 使用http.HandleFunc函数在服务器路由上注册上面定义的处理器。 // http.HandleFunc在net/http包中设置默认路由器,并将函数作为参数 http.HandleFunc("/hello", hello) http.HandleFunc("/headers", headers) // 使用端口和处理器调用ListenAndServe函数 // nil表示使用刚刚设置的默认路由器 http.ListenAndServe(":8090", nil) }

70-spawning-process 阅读更多

// 有时候Go程序需要生成其他非Go进程 // 例如一些网站的语法高亮,是通过从Go程序生成pygmentize进程来实现 package main import ( "fmt" "io/ioutil" "os/exec" ) func main() { // 一个简单的命令,该命令不带参数或输入,只是将内容输出到stdout // exec.Command帮助程序创建一个对象来表示此外部进程 dateCmd := exec.Command("date") // .Output是另一个帮助程序,它处理运行命令,等待命令完成和收集输出的常见情况 // 如果没有错误,dateOut将保存带有日期信息的字节 dateOut, err := dateCmd.Output() if err != nil { panic(err) } fmt.Println(">date") fmt.Println(string(dateOut)) // 稍微复杂一点的命令,将数据传输到stdin上的外部进程并从stdout收集结果 grepCmd := exec.Command("grep", "hello") // 在这里,显式地获取输入/输出管道, // 启动进程,向其写入一些输入,读取输出结果,最后等待进程退出 grepIn, _ := grepCmd.StdinPipe() grepOut, _ := grepCmd.StdoutPipe() grepCmd.Start() grepIn.Write([]byte("hello grep\ngoodbye grep")) grepIn.Close() grepBytes, _ := ioutil.ReadAll(grepOut) grepCmd.Wait() // 在上面的例子中省略了错误检查,可以使用if err != nil 模式来表示错误 // 使用StdoutPipe收集结果,使用StderrPipe收集错误 fmt.Println(">grep hello") fmt.Println(string(grepBytes)) // 在生成命令时,需要提供一个显式描述的命令和参数数组,而不是只能传入一个命令行字符串 // 如果要使用字符串生成完整命令,可以使用bash的-c选项 lsCmd := exec.Command("bash", "-c", "ls -a -l -h") lsOut, err := lsCmd.Output() if err != nil { panic(err) } fmt.Println(">ls -a -l -h") fmt.Println(string(lsOut)) } // 生成的程序返回的输出与我们直接从命令行运行它们的输出相同。

71-execing-process 阅读更多

// 上一个例子生成一个外部的进程,当我们需要一个正在运行的Go进程可访问的外部进程时,会这样做 // 有时只想用另一个(也许是非Go进程)替换当前的Go进程。为此,将使用Go的经典exec函数来实现 package main import ( "os" "os/exec" "syscall" ) func main() { // 下面的示例将执行ls命令 // Go需要一个想要执行的二进制文件的绝对路径 // 使用exec.LookPath函数来找打它 binary, lookErr := exec.LookPath("ls") if lookErr != nil { panic(lookErr) } // Exec需要切片形式的参数(与一个大字符串相对应) // 第一个参数应该是程序名称 args := []string{"ls", "-a", "-l", "-h"} // Exec还需要一组环境变量才能使用。在这里,只提供当前的环境变量 env := os.Environ() // 这是实际的syscall.Exec调用 // 如果此调用成功,那么进程的执行将在此处结束,并由/bin/ls -a -l -h进程替换 // 如果有错误,将获得返回值 execErr := syscall.Exec(binary, args, env) if execErr != nil { panic(execErr) } } // 请注意,Go不提供经典的Unix fork函数 // 通常这不是问题,因为启动goroutine,产生进程和exec'ing进程涵盖了fork的大多数用例

72-signals 阅读更多

// 有时希望Go程序能够智能地处理Unix信号。 // 例如,希望服务器在收到SIGTERM时正常关闭,或者命令行工具在收到SIGINT时停止处理输入 package main import ( "fmt" "os" "os/signal" "syscall" ) func main() { // 通过在通道上发送os.Signal值来发送信号通知 // 创建一个通道来接收这些通知(还会在程序退出时发出通知) sigs := make(chan os.Signal, 1) done := make(chan bool, 1) // signal.Notify注册​​给定通道以接收指定信号的通知 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // 该goroutine执行信号的阻塞接收 // 当它获得一个信号,将会打印出来该信号,然后发出完成通知 go func() { sig := <-sigs fmt.Println("") fmt.Println(sig) done <- true }() // 程序将在此处等待,直到获得预期的信号 // 如上面的goroutine所示,发送完成后的值,然后退出 fmt.Println("awaiting signal") <-done fmt.Println("exiting") }

73-exit 阅读更多

// 使用os.Exit根据定状态码立即退出 package main import ( "fmt" "os" ) func main() { // 使用os.Exit时不会运行defers,因此永远不会调用此fmt.Println defer fmt.Println("!") os.Exit(3) } // 请注意,不像C语言,Go不使用main的整数返回值来指示退出状态。 // 如果想以非零状态退出,则应使用os.Exit。 // 如果您使用go run运行73-exit.go,则将通过go并打印退出。 // 通过构建和执行二进制文件,可以在终端中查看状态。 // go build exit.go // ./exit // echo $? // 3 // 注意程序永远不会打印感叹号(!) 。