Go by Example
Go by Example
Hello World 我们第一个程序就是打印经典的“hello world”,下面是完整的代码
1 2 3 4 5 package mainimport "fmt" func main () { fmt.Println("hello world" ) }
要运行这个程序,将代码保存为 hello-world.go,然后使用go run
有时候我们想让程序编译成二进制文件,可以使用go build
,然后就可以直接运行了。
Values Go 有多种值的类型,包括 string,integer,float,boolean 等。如下是几个基本例子。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { fmt.Println("go" +"lang" ) fmt.Println("1+1=" ,1 +1 ) fmt.Pritnln("7.0/3.0=" ,7.0 /3.0 ) fmt.Println(true &&false ) fmt.Println(true ||false ) fmt.Pritnln(!true ) }
Variables 在 Go 中,变量被编译器显式的声明和使用,例如检查函数调用类型的正确性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { var a string ="initial" fmt.Println(a) var b,c int = 1 ,2 fmt.Println(b,c) var d = true fmt.Println(d) var e int fmt.Println(e) f:="short" fmt.Println(f) }
Constants Go 支持的常量有字符、字符串、布尔值以及数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" import "math" const s string = "constant" func main () { fmt.Println(s) const n = 50000000 const d = 3e20 / n fmt.Println(d) fmt.Println(int64 (d)) fmt.Println(math.Sin(n)) }
For for 是 Go 中唯一的循环结构,下面是三种基本的 for 循环类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "fmt" func main () { i := 1 for i<=3 { fmt.Println(i) i = i+1 } for j:=7 ;j<=9 ;j++{ fmt.Println(j) } for { fmt.Println("loop" ) break } for n:=0 ;n<=5 ;n++ { if n%2 == 0 { continue } fmt.Println(n) } }
If/Else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func main () { if 7 %2 ==0 { fmt.Println("7 is even" ) }else { fmt.Println("7 is odd" ) } 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 中条件周围不需要圆括号,但是花括号是必要的。
Go 中没有三目 if 语句,所以对于最基本的条件也需要写完整的 if 语句。
Switch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" import "time" func main () { i:=2 fmt.Print("write" ,i,"as" ) switch i{ case 1 : fmt.Println("one" ) case 2 : fmt.Println("two" ) case 3 : fmt.Println("three" ) } switch time.Now().weekday(){ case time.Saturday,time.Sunday: fmt.Println("it's the weekend" ) default : fmt.Println("it's a weekday" ) } t:=time.Now() switch { case t.Hour()<12 : fmt.Println("it's before noon" ) default : fmt.Println("it's after noon" ) } whatAmI := func (i interface {}) { switch t:=i.(type ){ case bool : fmt.Println("I'm a bool" ) case int : fmt.Println("I'm an int" ) default : fmt.Println("Don't know type %T\n" ,t) } } whatAmI(true ) whatAmI(1 ) whatAmI("hey" ) }
Arrays 在 Go 中,数组是特定长度元素的编号序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { var a [5 ]int fmt.Println("emp:" ,a) a[4 ]=100 fmt.Println("set:" ,a) fmt.Println("get:" ,a[4 ]) fmt.Println("len:" ,len (a)) b:=[5 ]int {1 ,2 ,3 ,4 ,5 } fmt.Prinln("dc1:" ,b) 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) }
当使用 fmt.Println 方法打印时,数组将以 [v1 v2 v3 …] 的形式展现
Slices slice 是一个重要的数据类型,对于序列提供了比数组更强大的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package mainimport "fmt" func main () { s:=make ([]string ,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)) s=append (s,"d" ) s=append (s,"e" ,"f" ) fmt.Println("apd:" ,s) c:=make ([]string ,len (s)) copy (c,s) fmt.Println("cpy:" ,c) l:=s[2 :5 ] fmt.Println("sl1:" ,l) l=s[:5 ] fmt.Pritnln("sl2:" ,l) l=s[2 :] 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) }
虽然 slice 与 array 是不同的类型,但是使用 fmt.Println 的展示结果很相似
Maps Map 是 Go 内置的关联数据类型(其他语言可能成为哈希或者字典)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func main () { m:=make (map [string ]int ) m["k1" ]=7 m["k2" ]=13 fmt.Println("map:" ,m) v1:=m["k1" ] fmt.Println("v1:" ,v1) fmt.Println("len:" ,len (m)) delete (m,"k2" ) fmt.Println("map:" ,m) _,prs:=m["k2" ] fmt.Println("prs:" ,prs) n:=map [string ]int {"foo" :1 ,"bar" :2 } fmt.Println("map:" ,n) }
map 将以 [k:v k:v] 的形式打印
Range range 可以遍历各种数据结构中的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" func main () { nums:=[]int {2 ,3 ,4 } sum:=0 for _,num:=range nums{ sum+=num } fmt.Println("sum:" ,sum) for i,num:=range nums{ if num==3 { fmt.Pritnln("index" ,i) } } kvs:=map [string ]string {"a" :"apple" ,"b" :"banana" } for k,v:=range kvs{ fmt.Printf("%s -> %s\n" ,k,v) } for k:= range kvs{ fmt.Println("key:" ,k) } for i,c:range "go" { cmt.Println(i,c) } }
Functions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func plus (a int ,b int ) int { return a+b }func plusPlus (a,b,c int ) int { return a+b+c }func main () { res := plus(1 ,2 ) fmt.Println("1+2=" ,res) res = plusPlus(1 ,2 ,3 ) fmt.Println("1+2+3=" ,res) }
Multiple Return Values Go 内置支持了返回多个值。这一特点经常用于 Go 的习惯用法,例如同时返回结果和错误值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func vals () (int ,int ) { return 3 ,7 }func main () { a,b := vals() fmt.Println(a) fmt.Println(b) _,c := vals() fmt.Println(c) }
Variadic Functions 变参函数,可以使用任意数量的参数来进行调用。例如 fmt.Println 是一个常见的变参函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func sum (nums ...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 } sum(nums...) }
Closures Go 支持匿名函数,它可以形成闭包。当你想要定义一个不记名的内部函数时,匿名函数就很有用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func intSeq () func int { i := 0 return func () int { i+=1 return i } }func main () { nextInt:=intSeq() fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Pritnln(nextInt()) newInts:=intSeq() fmt.Println(newInts()) }
Recursion Go 支持递归函数。下面是一个经典的斐波那契数列示例
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func fact (n int ) int { if n==0 { return 1 } return n * fact(n-1 ) }func main () { fmt.Println(fact(7 )) }
Pointers Go 可以使用指针,让你在程序中传递值或记录的引用。
下面通过两种方式的对比来展示指针的使用:zeroval
和zeroptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func zeroval (ival int ) { ival = 0 }func zeroptr (iptr *int ) { *iptr = 0 }func main () { i:=1 fmt.Println("initial:" ,i) zeroval(i) fmt.Println("zeroval:" ,i) zeroptr(&i) fmt.Println("zeroptr:" ,i) fmt.Println("pointer:" ,&i) }
zeroval 没有改变 main 函数中 i 的值,而 zeroptr 会,因为它拥有指向变量 i 的内存地址。
Structs Go 的 Struct 结构是字段类型的集合。对于从记录中将数据组织到一起很有帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" type person struct { name string age int }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 }) s:=person{name:"Sean" ,age:50 } fmt.Println(s.name) sp:=&s fmt.Println(sp.age) sp.age=51 fmt.Println(sp.age) }
Methods Go 支持在结构体上定义方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" type rect struct { width,height int }func (r *rect) area () int { return r.width * r.height }func (r rect) perim () int { return 2 *r.width+2 *r.height }func main () { 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()) }
Interfaces 接口是方法签名的命名集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport "fmt" import "math" type geometry interface { area() float64 perim() float64 }type rect struct { width,height float64 }type circle struct { radius float64 }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 () { r:=rect{width:3 ,height:4 } c:=circle{radius:5 } measure(r) measure(c) }
Errors 在 Go 中,传递错误的惯用法是通过明确的,分离的返回值。这和 Java 活 Ruby 中的 exception 以及 C 中重载使用单个的结果 / 错误值不同。Go 的方法使得很容易看出哪些函数返回错误,并使用与任何其他非错误任务相同的语言结构来处理它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport "errors" import "fmt" func f1 (arg int ) (int ,error) { if arg==42 { return -1 ,errors.New("can't work with 42" ) } return arg+3 ,nil }type argError struct { arg int prob string }type argError struct { arg int prob string }func (e *argError) Error () string { 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" } } 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("f1 failed:" ,e) }else { fmt.Println("f1 worked:" ,r) } } _,e:=f2(42 ) if ae,ok := e.(*argError);ok{ fmt.Println(ae.arg) fmt.Println(ae.prob) } }
Goroutines goroutine 是一个轻量级的执行线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func f (from string ) { for i:=0 ;i<3 ;i++{ fmt.Println(from,";" ,i) } }func main () { f("direct" ) go f("goroutine" ) go func (msg string ) { fmt.Println(msg) }("going" ) var input string fmt.Scanln(&input) fmt.Println("done" ) }
当我们运行这个程序的时候,我们将首先看到阻塞调用,然后是两个 goroutine 的交错输出。这个交错反应了 goroutine 在 Go 运行时是并发执行的。
Channels Channel 是连接并发执行的 goroutine 的管道。你可以从一个 goroutine 传递值到 channel 中,再在另一个 goroutine 接收它。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { messages:=make (chan string ) go func () {messages<-"ping" }() msg:=<-messages fmt.Println(msg) }
当我们运行这个程序时,”ping” 信息成功的通过我们的 channel 从一个 goroutine 传递到了另一个。
默认情况下,发送和接收在发送者和接受者都准备好之前阻塞。这个特性允许我们在程序结尾等待 “ping” 信息而无需使用其他的同步手段
Channel Buffering 默认下 channel 没有缓冲区,这意味着他们将只有在响应的接收者 (<-chan) 准备好时,才能允许发送 (chan<-)。具有缓冲区的 channel,接受有限个数的值,而无需相应的接收者。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { messages:=make (chan string ,2 ) messages<-"buffered" messages<-"channel" fmt.Println(<-messages) fmt.Println(<-messages) }
Channel Synchronization 我们可以使用 channel 来跨 goroutine 同步执行。这里是一个使用阻塞接收来等待 goroutine 结束的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" import "time" func worker (done chan bool ) { fmt.Print("working..." ) time.Sleep(time.Second) fmt.Println("done" ) done<-true }func main () { 启动一个worker goroutine,赋予它用以通知的channel done:=make (chan bool ,1 ) go worker(done) <-done }
如果你移除了 <-done 行,这个程序可能会在 worker 开始前就结束。
Channel Directions 当把 channel 用作函数的参数时,你可以指定一个 channel 是否只发送或者只接收数据。这种特异性增加了程序的类型安全性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func ping (pings chan <- string ,msg string ) { pings<-msg }func pong (pings <-chan string ,pongs chan <- string ) { 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) }
Select Go 的 select 让你能够等待多个 channel 操作。通过 select 结合 goroutine 和 channel 是 Go 的重要特色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "time" import "fmt" func main () { c1:=make (chan string ) c2:=make (chan string ) go func () { time.Sleep(time.Second*1 ) c1<-"one" }() go func () { time.Sleep(time.Second*2 ) c2<-"two" }() for i:=0 ;i<2 ;i++{ select { case msg1:=<-c1: fmt.Println("received" ,msg1) case msg2:=<-c2: fmt.Println("received" ,msg2) } } }
按照预期,我们将接收到 “one”,然后 “two”。
注意,整个执行只需要大约 2 秒的时间,因为 1 秒和 2 秒的沉睡是并发执行的。
Timeouts 超时对于连接到外部资源的程序或其他需要绑定执行时间的程序很重要。在 Go 中,可以通过 channel 和 select 轻松而优雅的实现超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "time" import "fmt" func main () { c1:=make (chan string ,1 ) go func () { time.Sleep(time.Second*2 ) c1<-"result 1" }() select { case res:=<-c1: fmt.Println(res) case <-time.After(time.Second*1 ): fmt.Println("timeout 1" ) } c2:=make (chan string ,1 ) go func () { time.Sleep(time.Second*2 ) c2<-"result 2" }() select { case res:=<-c2: fmt.Println(res) case <-time.After(time.Second*3 ): fmt.Printnln("timeout 2" ) } }
运行这个程序,将显示第一个操作超时了,第二个则成功。
使用 select 超时模式需要在通道上进行结果通讯。一般情况下这是很好的主意,因为其他的重要 Go 特性基于通道和选择。我们将在之后看到有关的两个例子:timer 和 ticker
Non-Blocking Channel Operations channel 上简单的发送和接收是阻塞的。然而,我们可以使用 select 和 default 子句来实现非阻塞发送、接收甚至非阻塞的多路选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport "fmt" func main () { messages:=make (chan string ) signals:=make (chan bool ) select { case msg:=<-messages: fmt.Println("received message" ,msg) default : fmt.Pritln("no message received" ) } msg:="hi" select { case messages<-msg: cmt.Println("sent message" ,msg) default : fmt.Println("no message sent" ) } select { case msg:=<-messages: fmt.Println("received message" ,msg) case sig:=<-signals: fmt.Println("received signal" ,sig) default : fmt.Println("no activity" ) } }
Closing Channels 关闭通道意味着不会再有值在其上发送。这对于通道的接收方通讯完成很有帮助
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" func main () { jobs:=make (chan int ,5 ) done:=make (chan bool ) go func () { for { j,more:=<-jobs if more{ fmt.Println("received job" ,j) }else { fmt.Println("received all jobs" ) done<-true return } } }() for j:=1 ;j<=3 ;j++{ jobs<-j fmt.Println("sent job" ,j) } close (jobs) fmt.Println("sent all jobs" ) <-done }
Range over Channels 在前面的例子中,我们看到如何使用 for 和 range 来遍历基本的数据结构。我们同样可以使用这个语法来遍历通道上接收的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { queue:=make (chan string ,2 ) queue<-"one" queue<-"two" close (queue) for elem:=range queue{ fmt.Println(elem) } }
Timers 我们常想在未来的某一刻执行 Go 代码,或者在某一时间内重复执行。Go 内置的 timer 和 ticker 使这些任务十分简易。首先我们看看 timer,然后再看 ticker。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "time" import "fmt" func main () { timer1:=time.NewTimer(time.Second*2 ) <-timer1.C fmt.Println("Timer 1 expired" ) 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 秒到时,但第二个应会在其有机会到时前先行停止。
Tickers timer 用来在将来的某一时间做某事一次。而 ticker 会在一个指定时间间隔重复做某事。这里是一个 ticker 的例子,它会在我们停止之前定期触发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "time" import "fmt" func main () { ticker:=time.NewTicker(time.Millisecond*500 ) go func () { for t:=range ticker.C{ fmt.Prinltn("Tick at" ,t) } }() time.Sleep(time.Millisecond*1600 ) ticker.Stop() fmt.Println("Ticker stopped" ) }
运行这个程序,在结束之前,应该会 tick 三次。
Worker Pools 本例中我们将看到如何使用 goroutine 和 channel 来实现一个工人池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" import "time" func worker (id int ,jobs <-chan int ,results chan <- int ) { for j:=range jobs{ fmt.Println("worker" ,id,"started job" ,j) time.Sleep(time.Second) fmt.Println("worker" ,id,"finished job" ,j) results <- j*2 } }func main () { jobs:=make (chan int ,100 ) results:=make (chan int ,100 ) for w:=1 ;w<=3 ;w++{ go worker(w,jobs,results) } for j:=1 ;j<=5 ;j++{ jobs<-j } close (jobs) for a:=1 ;a<=5 ;a++{ <-results } }
运行的项目展示了有 5 个作业得以被不同的工人执行。尽管总共有 5 秒钟的时间,这个程序只需要 2 秒钟,因为有 3 名工作人员同时进行操作。
同时启动了 3 个 worker,来监听通道是否有作业发出,无作业时 worker 不会进入循环体,为空操作。从而形成工人池
Rate Limiting 速率限制是控制资源利用和维护服务质量的重要机制。Go 通过 goroutine,channel 和 ticker 可以优雅的支持速率控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport "time" import "fmt" func main () { requests:=make (chan int ,5 ) for i:=1 ;i<=5 ;i++{ request<-i } close (requests) limiter:=time.Tick(time.Millisecond*200 ) for req:=range request{ <-limiter fmt.Println("request" ,req,time.now()) } burstyLimiter:=make (chan time.Time,3 ) for i:=0 ;i<3 ;i++{ burstyLimiter<-time.Now() } go func () { for t:=range time.Tick(time.Millisecond*200 ){ burstyLimiter<-t } }() burstyRequests:=make (chan int ,5 ) for i:=1 ;i<=5 ;i++{ burstyRequest<-i } close (burstyRequests) for req:=range burstyRequests{ <-burstyLimiter fmt.Println("request" ,req,time.Now()) } }
运行我们的程序,我们看到第一批请求根据需要每 200 毫秒处理一次。
对于第二批请求,由于可突发速率限制,我们立即为前 3 个服务,然后以约 200ms 的延迟提供剩余的 2 个。
Atomic Counters Go 中管理状态的主要机制是通过渠道进行沟通。 我们以工人池为例。 还有一些管理状态的其他选项。 这里我们来看一下使用 sync / atomic 包来进行多个 goroutines 访问的原子计数器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "fmt" import "time" import "sync/atomic" func main () { var ops unit64=0 for i:=0 ;i<50 ;i++{ go func () { for { atomic.AddUnit64(&ops,1 ) time.Sleep(time.Millisecond) } }() } time.Sleep(time.Second) opsFinal:=atomic.LoadUnit64(&ops) fmt.Println("ops:" ,opsFinal) }
运行程序可以看到我们执行了大约 40,000 次操作。
Mutexes 在前面的例子中我们看到如何使用原子操作管理简单的计数器状态。为了处理更复杂的状态,我们可以使用一个mutext
来安全的访问不同 gorotine 之间的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package mainimport ( "fmt" "math/rand" "sync" "sync/atomic" "time" )func main () { var state=make (map [int ]int ) var mutex=&sync.Mutex{} var readOps unit64=0 var writeOps unit64=0 for r:=0 ;r<100 ;r++{ go func () { total:=0 for { key:=rand.Intn(5 ) mutex.Lock() total+=state[key] mutex.Unlock() atomic.AddUnit64(&readOps,1 ) time.Sleep(time.Millisecond) } }() } for w:=0 ;w<10 ;w++{ go func () { for { key:=rand.Intn(5 ) val:=rand.Intn(100 ) mutex.Lock() state[key]=val mutex.Unlock() atomic.AddUnit64(&writeOps,1 ) time.Sleep(time.Millisecond) } }() } time.Sleep(time.Second) readOpsFinal:=atomic.LoadUnit64(&readOps) fmt.Println("readOps:" ,readOpsFinal) writeOpsFinal:=atomic.LoadUnit64(&writeOps) fmt.Println("writeOps:" ,writeOpsFinal) mutex.Lock() fmt.Println("state:" ,state) mutex.Unlock() }
运行程序可以看到我们在 mutex 同步状态上执行了将近 90,000 操作。
Stateful Goroutines 在上个例子我们使用 mutex 显式锁定多个 goroutine 要同步访问的共享状态。另一个选择是使用 goroutine 和 channel 内置的同步功能来达到相同的结果。这种基于渠道的方法与 Go 通过通信和拥有完全一个 goroutine 的每个数据来共享内存的想法相一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package mainimport ( "fmt" "math/rand" "sync/atomic" "time" )type readOp struct { key int resp chan int }type writeOp struct { key int val int resp chan bool }func main () { var readOp unit64=0 var writeOps unit64=0 reads:=make (chan *readOp) writes:=make (chan *writeOp) go func () { var state=make (map [int ]int ) for { 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++{ go func () { for { read:=&readOp{ key:rand.Intn(5 ), resp:make (chan int ) } reads<-read <-read.resp atomic.AddUnit64(&readOps,1 ) time.Sleep(time.Millisecond) } }() } for w:=0 ;w<10 ;w++{ go func () { for { write:=&writeOp{ key:rand.Intn(5 ), val:rand.Intn(100 ), resp:make (chan bool ) } } }() } time.Sleep(time.Second) readOpsFinal:=atomic.LoadUnit64(&readOps) fmt.Println("readOps:" ,readOpsFinal) writeOpsFinal:=atomic.LoadUnit64(&writeOps) fmt.Println("writeOps:" ,writeOpsFinal) }
运行项目显示基于 gouroutine 的状态管理完成了大约 80,000 操作。
Sorting sort 包实现了内置和自定义类型的排序。首先看看内置类型的排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" import "sort" func main () { strs:=[]string {"c" ,"a" ,"b" } sort.Strings(strs) fmt.Println("Strings:" ,strs) ints:=[]int {7 ,2 ,4 } sort.Ints(ints) fmt.Println("Ints:" ,ints) s:=sort.IntsAreSorted(ints) fmt.Println("Sorted:" ,s) }
Sorting by Functions 有时候我们想要对一个集合进行非自然顺序的排序。例如,我们想要把字符串根据长度而非字典顺序排序,下面是一个定制排序的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" import "sort" type ByLength []string func (s ByLength) Len () int { return len (s) }func (s ByLength) Swap (i,j int ) { s[i],s[j] = s[j],s[i] }func (s ByLength) Less (i,j int ) bool { return len (s[i])<len (s[j]) }func main () { fruits:=[]string {"peach" ,"banana" ,"kiwi" } sort.Sort(ByLength(fruits)) fmt.Println(fruits) }
通过类似的模式创建自定义类型,实现三个接口方法,然后调用 sort,我们可以对集合进行任意的排序。
Panic panic
通常指发生了未曾预料的错误。大多数情况下,我们使用它来将不应当在正常操作中发生的东西快速失败,或者不准备妥善处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "os" func main () { panic ("a problem" ) _,err:=os.Create("/tmp/file" ) if err!=nil { panic (err) } }
运行这个程序将引起一个 panic,打印错误信息和 goroutine 踪迹,并以非 0 状态退出。
注意,不像一些用异常来处理大多错误的语言,在 Go 的习惯用法中,尽可能使用错误指示的返回值。
Defer defer 用于确保一个函数调用在程序执行中延迟作用,经常用于清理目的。defer
常用语其他语言的ensure
和finnaly
用的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 packgae mainimport "fmt" import "os" func main () { f:=createFile("/tmp/defer.txt" ) 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.Println(f,"data" ) }func closeFile (f *os.File) { fmt.Println("closing" ) f.Close() }
运行程序,确认这个文件的确在写之后再关闭的。
Collection Functions 我们经常需要我们的程序对数据集合执行操作,例如选择满足给定谓词的所有项目或将所有项目映射到具有自定义函数的新集合。
一些语言通常的惯用法是使用泛型数据结构和算法。 Go 不支持泛型; 在 Go 中,通常在程序和数据类型特别需要时提供集合功能。
以下是一些用于字符串切片的示例集合函数。 您可以使用这些示例来构建自己的函数。 请注意,在某些情况下,直接内联集合操作代码可能是最为清晰的,而不是创建和调用帮助函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport "strings" import "fmt" func Index (vs []string ,t string ) int { for i,v:=range vs{ if v==t{ return i } } return -1 }func Include (vs []string ,t string ) bool { return Index(vs,t)>=0 }func Any (vs []string ,f func (string ) bool ) bool { for _,v:=range vs{ if f(v){ return true } } return false }func All (vs []string ,f func (string ) bool ) bool { for _,v:=range vs{ if !f(v){ return false } } return true }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 }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)) }
String Functions 标准库的 String 包提供了许多有用的字符串相关的函数。这里有些示例让你对这个包有个初步的认识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport s "strings" import "fmt" var p=fmt.Printlnfunc main () { p("Contains:" ,s.Contains("test" ,"es" )) p("Count:" ,s.Count("test" ,"t" )) p("HasPrefix:" ,s.HasPrefix("test" ,"te" )) p("HasSuffix:" ,s.HasSuffix("test" ,"st" )) p("Index:" ,s.Index("test" ,"e" )) p("Join:" ,s.Join([]string {"a" ,"b" },"-" )) p("Repeat:" ,s.Repeat("a" ,5 )) p("Replace:" ,s.Replace("foo" ,"o" ,"0" ,-1 )) p("Replace" ,s.Replace("foo" ,"o" ,"0" ,1 )) p("Split:" ,s.Split("a-b-c-d-e" ,"-" )) p("ToLower:" ,s.ToLower("TEST" )) p("ToUpper:" ,s.ToUpper("test" )) p() p("Len:" ,len ("hello" )) p("Char:" ,"hello" [1 ]) }
注意,获取长度和索引字符是工作在字节级别上的。Go 使用 UTF-8 编码字符串。
Go 在经典的printf
上提供了优秀的字符串格式化支持。这里有一些常见格式化的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport "fmt" import "os" type point struct { x,y int }func main () { p:=point{1 ,2 } fmt.Printf("%v\n" ,p) fmt.Printf("%+v\n" ,p) fmt.Printf("%#v\n" ,p) fmt.Printf("%T\n" ,p) fmt.Printf("%t\n" ,true ) fmt.Printf("%d\n" ,123 ) fmt.Printf("%b\n" ,14 ) fmt.Printf("%c\n" ,33 ) fmt.Printf("%x\n" ,456 ) fmt.Printf("%f\n" ,78.9 ) fmt.Printf("%e\n" ,123400000.0 ) fmt.Printf("%E\n" ,123400000.0 ) fmt.Pirntf("%s\n" ,"\"string\"" ) fmt.Printf("%q\n" ,"\"string\"" ) fmt.Printf("%x\n" ,"hex this" ) fmt.Printf("%p\n" ,&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" ) s:=fmt.Sprintf("a %s" ,"string" ) fmt.Println(s) fmt.Fprintf(os.Stderr,"an %s\n" ,"error" ) }
Regular Expressions Go 为正则表达式提供了内置的支持。这里是一些常用的正则相关的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "bytes" import "fmt" import "regexp" func main () { math,_:=regexp.MatchString("p([a-z]+)ch" ,"peach" ) fmt.Printf(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" )) 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" ))) r=regexp.MustComplie("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)) }
JSON Go 内置提供了 JSON 的编码和解码是,包括内置和自定义的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package mainimport "encoding/json" import "fmt" import "os" type Response1 struct { Page int Fruits []string }type Response2 struct { Page int `json:"page"` Fruits []string `json:"fruits"` }func main () { 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)) 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)) res1D:=&Response1{ Page:1 , Fruits:[]string {"apple" ,"peach" ,"pear" } } res1B,_:=json.Marshal(res1D) fmt.Println(string (res1B)) res2D:=&Reponse2{ Page:1 , Fruits:[]string {"apple" ,"peach" ,"pear" } } res2B,_:=json.Marshal(res2D) fmt.Println(string (res2B)) byt:=[]byte (`{"num":6.13,"strs":["a","b"]}` ) var dat map [string ]interface {} if err:=json.Unmarshal(byt,&dat);err!=nil { panic (err) } fmt.Println(dat) num:dat["num" ].(float64 ) fmt.Println(num) strs:=dat["strs" ].([]interface {}) str1:=strs[0 ].(string ) fmt.Println(str1) str:=`{"page":1,"fruits":["apple","peach"]}` res:=Response2{} json.Unmarshal([]byte (str),&res) fmt.Println(res) fmt.Println(res.Fruits[0 ]) enc:json.NewEncoder(os.Stdout) d:=map [string ]int {"apple" :5 ,"lettuce" :7 } enc.Encode(d) }
Time Go 为时间和持续时间提供了额外支持。这里是一些例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport "fmt" import "time" func main () { p:=fmt.Pritnln now:=time.Now() p(now) 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)) diff:=now.Sub(then) p(diff) p(diff.Hours()) p(diff.Minutes()) p(diff.Seconds()) p(diff.Nanoseconds()) p(then.Add(diff)) p(then.Add(-diff)) }
Epoch 程序中的一个常见需求是获取秒、毫秒或微秒,自 Unix 时代,这里是 Go 的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" import "time" func main () { now:=time.Now() secs:=now.Unix() nanos:=now.UnixNano() fmt.Println(now) millis:=nanos/1000000 fmt.Println(secs) fmt.Println(millis) fmt.Println(nanos) fmt.Println(time.Unix(secs,0 )) fmt.Println(time.Unix(0 ,nanos)) }
Go 支持时间格式化和解析,根据基于模式的布局。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport "fmt" import "time" func main () { p:=fmt.Println t:=time.Now() p(t.Format(time.RFC3339)) t1,e:=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,e:=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) }
Random Numbers Go 的 math/rand 包提供产生伪随机数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport "time" import "fmt" import "math/rand" func main () { fmt.Print(rand.Intn(100 ),"," ) fmt.Print(rand.Intn(100 )) fmt.Println() fmt.Println(rand.Float64()) fmt.Print((rand.Float64()*5 )+5 ,"," ) fmt.Print((rand.Float64()*5 )+5 ) fmt.Println() s1:=rand.NewSource(time.Now().UnixNano()) r1:=rand.New(s1) fmt.Print(r1.Intn(100 ),"," ) fmt.Print(r1.Intn(100 )) fmt.Println() s2:=rand.NewSrouce(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 )) }
Number Parsing 从字符串中解析数字是一个常见的任务,这里是 Go 的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "strconv" import "fmt" func main () { f,_:=strconv.ParseFloat("1.234" ,64 ) fmt.Println(f) i,_:=strconv.ParseInt("123" ,0 ,64 ) fmt.Println(i) d,_:=strconv,Parseint("0x1c8" ,0 ,64 ) fmt.Println(d) u,_:=strconv.ParseUint("789" ,0 ,64 ) fmt.Println(u) k,_:=strconv.Atoi("135" ) fmt.Println(k) _,e:=strconv.Atoi("wat" ) fmt.Println(e) }
URL Parsing 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport "fmt" import "net" import "net/url" func main () { s:="postgres://user:pass@host.com:5432/path?k=v#f" u,err:=url.Parse(s) if err!=nil { panic (err) } fmt.Println(u.Scheme) fmt.Println(u.User) fmt.Println(u.User.Username()) p,_:=u.User.Password() fmt.Println(p) fmt.Println(u.Host) host,port,_:=net.SplitHostPort(u.Host) fmt.Println(host) fmt.Println(port) fmt.Println(u.Path) fmt.Println(u.Fragment) fmt.Println(u.RawQuery) m,_:=url.ParseQuery(u.RawQuery) fmt.Println(m) fmt.Println(m["k" ][0 ]) }
SHA1 Hashes SHA1 哈希经常用于计算二进制或者文本块的短标识。例如,git 版本控制系统使用 SHA1 来标示文本和目录。这里是 Go 如何计算 SHA1 哈希值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "crypto/sha1" import "fmt" func main () { s:="sha1 this string" h:=sha1.New() h.Write([]byte (s)) bs:=h.Sum(nil ) fmt.Println(s) fmt.Printf("%x\n" ,bs) }
Base64 Encoding 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport b64 "encoding/base64" import "fmt" func main () { data:="abc123!?$*&()'-=@~" sEnc:=b64.StdEncoding.EncodeToString([]byte (data)) fmt.Println(sEnc) sDec,_:=b64.StdEncoding.DecodeString(sEnc) fmt.Println(string (sDec)) fmt.Println() uEnc:=b64.URLEncoding.EncodeToString([]byte (data)) fmt.Println(uEnc) uDec,_:=b64.URLEncoding.DecodeString(uEnc) fmt.Println(string (uDec)) }
Reading Files 读取和写入文件是许多 Go 程序的基本任务需求。首先我们来看读取文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport ( "bufio" "fmt" "io" "io/ioutil" "os" )func check (e error) { if e!=nil { panic (e) } }func main () { dat,err:=ioutil.ReadFile("/tmp/dat" ) check(err) fmt.Print(string (dat)) f,err:=os.Open("/tmp/dat" ) check(err) b1:=make ([]byte ,5 ) n1,err:=f.Read(b1) check(err) fmt.Printf("%d bytes: %s\n" ,n1,string (b1)) o2,err:=f.Seek(6 ,0 ) check(err) b2:=make ([]byte ,2 ) n2,err:=f.Read(b2) check(err) fmt.Printf("%d bytes @[](https://hacpai.com/member/) %d: %s\n" ,n2,o2,string (b2)) 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 @[](https://hacpai.com/member/) %d: %s\n" ,n3,o3,string (b3)) _,err=f.Seek(0 ,0 ) check(err) r4:=bufio.NewReader(f) b4,err:=r4.Peek(5 ) check(err) fmt.Printf("5 bytes: %s\n" ,string (b4)) f.Close() }
Writing Files 写文件与读取的模式类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( "bufio" "fmt" "io/ioutil" "os" )func check (e error) { if e!=nil { panic (e) } }func main () { d1:=[]byte ("hello\ngo\n" ) err:=ioutil.WriteFile("/tmp/dat1" ,d1,0644 ) check(err) f,err:=os.Create("/tmp/dat2" ) check(err) defer f.Close() d2:=[]byte {115 ,111 ,109 ,101 ,10 } n2,err:=f.Write(d2) check(err) fmt.Printf("wrote %d bytes\n" ,n2) n3,err:=f.WriteString("writes\n" ) fmt.Printf("wrote %d bytes\n" ,n3) f.Sync() w:=bufio.NewWriter(f) n4,err:=w.WriteString("buffered\n" ) fmt.Printf("wrote %d bytes\n" ,n4) w.Flush() }
Line Filters 一个行过滤器经常见于读取标准输入流的输入,处理然后输出到标准输出的程序中。grep 和 sed 是常见的行过滤器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "bufio" "fmt" "os" "strings" )func main () { scanner:=bufio.NewScanner(os.Stdin) for scanner.Scan(){ ucl:=strings.ToUpper(scanner.Text()) fmt.Println(ucl) } if err:=scanner.Err();err!=nil { fmt.Fprintln(os.Stderr,"error:" ,err) os.Exit(1 ) } }
可以使用如下命令来试验这个行过滤器:
$ echo ‘hello’ > /tmp/lines $ echo ‘filter’ >> /tmp/lines $ cat /tmp/lines | go run line-filters.go
Command-Line Arguments 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "os" import "fmt" func main () { argsWithProg:=os.Args argsWithoutProg:=os.Args[1 :] arg:=os.Args[3 ] fmt.Println(argsWithProg) fmt.Println(argsWithoutProg) fmt.Println(arg) }
本例应当先 go build,然后再运行并指定参数
Command-Line Flags 命令行标志是一个指定特殊选项的常用方法。例如,在 wc -l 的 -l 就是一个命令行标志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "flag" import "fmt" func main () { 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() fmt.Println("word:" ,*wordPtr) fmt.Println("numb:" ,*numbPtr) fmt.Println("fork:" ,*boolPtr) fmt.Println("svar:" ,svar) fmt.Println("tail:" ,flag.Args()) }
测试用例:
$ go build command-line-flags.go # 省略的标志将自动设定为默认值 $ ./command-line-flags -word=opt # 位置参数可以出现在任何标志后面 $ ./command-line-flags -word=opt a1 a2 a3 # flag 包需要的所有标志出现在位置参数之前,否则标志将会被解析为位置参数 $ ./command-line-flags -word=opt a1 a2 a3 -numb=7 # 使用 -h 或者 –help标志来得到自动生成的命令行帮助文本 $ ./command-line-flags -h # 如果提供了一个没有用 flag 包指定的标志,将会得到错误信息和帮助文档 $ ./command-line-flags -wat
Environment Variables 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "os" import "strings" import "fmt" func main () { os.Setenv("FOO" ,"1" ) fmt.Println("FOO:" ,os.Getenv("FOO" )) fmt.Println("BAR:" ,os.Getenv("BAR" )) fmt.Println() for _,e:=range os.Environ(){ pair:=strings.Split(e,"=" ) fmt.Prinln(pair[0 ]) } }
Spawning Processes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport "fmt" import "io/iouitl" import "os/exec" func main () { dateCmd:=exec.Command("date" ) dateOut,err:=dateCmd.Output() if err!=nil { panic (err) } fmt.Println("> date" ) fmt.Println(string (dateOut)) grepCmd:=exec.Command("grep" ,"hello" ) grepIn,_:=grepCmd.StdinPipe() grepOut,_:=grepCmd.StdoutPipe() grepCmd.Start() grepIn.Write([]byte ("hello grep\ngoodbye grep" )) grepIn.Close() grepByte,_:=ioutil.ReadAll(grepOut) grepCmd.Wait() fmt.Println("> grep hello" ) fmt.Println(string (grepBytes)) 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.Pritnln(string (lsOut)) }
Exec’ing Processes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "syscall" import "os" import "os/exec" func main () { binary,lookErr:=exec.LookPath("ls" ) if lookErr!=nil { panic (lookErr) } args:=[]string {"ls" ,"-a" ,"-l" ,"-h" } env:=os.Environ() execErr:=syscall.Exec(binary,args,env) if execErr!=nil { panic (execErr) } }
Signals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" import "os" import "os/signal" import "syscall" func main () { sigs:=make (chan os.Signal,1 ) done:=make (chan bool ,1 ) signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) go func () { sig:=<-sigs fmt.Println() fmt.Println(sig) done<-true }() fmt.Println("awaiting signal" ) <-done fmt.Println("exiting" ) }
运行,使用 ctrl-c 发送信号
Exit 1 2 3 4 5 6 7 8 9 package mainimport "fmt" import "os" func main () { defer fmt.Println("!" ) os.Exit(3 ) }
官网:https://gobyexample.com