Go 學(xué)習(xí)示例

沉默是金

Hello World

我們第一個(gè)程序就是打印經(jīng)典的“hello world”,下面是完整的代碼

package main
import "fmt"
func main(){
  fmt.Println("hello world")
}

要運(yùn)行這個(gè)程序,將代碼保存為 hello-world.go,然后使用go run

有時(shí)候我們想讓程序編譯成二進(jìn)制文件,可以使用go build,然后就可以直接運(yùn)行了。

Values

Go 有多種值的類型,包括 string,integer,float,boolean 等。如下是幾個(gè)基本例子。

package main
import "fmt"
func main(){
  //string 可以使用 + 連接在一起
  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 中,變量被編譯器顯式的聲明和使用,例如檢查函數(shù)調(diào)用類型的正確性。

package main
import "fmt"
func main(){
  // 聲明一個(gè)或多個(gè)變量
  var a string="initial"
  fmt.Println(a)
  var b,c int = 1,2
  fmt.Println(b,c)
  //Go 將推斷變量的初始化類型
  var d = true
  fmt.Println(d)
  // 聲明沒(méi)有初始值的變量將被初始化為零值,如 int 的零值是 0
  var e int
  fmt.Println(e)
  //var f string = "short" 的簡(jiǎn)化寫法
  f:="short"
  fmt.Println(f)
}

Constants

Go 支持的常量有字符、字符串、布爾值以及數(shù)值

package main
import "fmt"
import "math"
const s string = "constant"
func main(){
  fmt.Println(s)
  //const 聲明可以出現(xiàn)在任何 var 聲明出現(xiàn)的地方
  const n = 50000000
  // 常量表達(dá)式可以任意精度進(jìn)行運(yùn)算
  const d = 3e20 / n
  fmt.Println(d)
  // 數(shù)值常量沒(méi)有類型,除非進(jìn)行了顯式轉(zhuǎn)換等類型賦予
  fmt.Println(int64(d))
  // 數(shù)值在使用環(huán)境上下文中會(huì)得到類型,如變量賦值或者方法調(diào)用,如 math.Sin 需要的是一個(gè) float64
  fmt.Println(math.Sin(n))
}

For

for 是 Go 中唯一的循環(huán)結(jié)構(gòu),下面是三種基本的 for 循環(huán)類型。

package main
import "fmt"
func main(){
  i := 1
  // 最基本的類型,只有一個(gè)條件
  for i<=3{
    fmt.Println(i)
    i = i+1
  }
  // 經(jīng)典的 初始化 / 條件 / 循環(huán)后 for 循環(huán)
  for j:=7;j<=9;j++{
    fmt.Println(j)
  }
  // 沒(méi)有條件的 for 語(yǔ)句將無(wú)限循環(huán),除非在內(nèi)部的方法中使用了 break 或者 return
  for{
    fmt.Println("loop")
    break
  }
  // 也可以使用 continue 來(lái)直接到下一個(gè)循環(huán)
  for n:=0;n<=5;n++ {
    if n%2 == 0{
      continue
    }
    fmt.Println(n)
  }
}

If/Else

package main
import "fmt"
func main(){
  if 7%2==0{
    fmt.Println("7 is even")
  }else{
    fmt.Println("7 is odd")
  }
  // 可以單獨(dú)使用 if
  if 8%4==0{
    fmt.Println("8 is divisible by 4")
  }
  // 條件之前可以有語(yǔ)句,聲明在該語(yǔ)句中的任何變量可以用于所有分支
  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 中條件周圍不需要圓括號(hào),但是花括號(hào)是必要的。

Go 中沒(méi)有三目 if 語(yǔ)句,所以對(duì)于最基本的條件也需要寫完整的 if 語(yǔ)句。

沉默是金

Switch

package main
import "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")
  }
  // 可以在同一個(gè) case 語(yǔ)句中使用逗號(hào)來(lái)分隔多個(gè)表達(dá)式
  switch time.Now().weekday(){
  case time.Saturday,time.Sunday:
    fmt.Println("it's the weekend")
  // 這里也使用了 default case
  default:
    fmt.Println("it's a weekday")
  }
  t:=time.Now()
  // 沒(méi)有表達(dá)式的 switch 是另一種實(shí)現(xiàn) if/else 邏輯的路子,同時(shí)這也展示了 case 表達(dá)式可以是非常量值。
  switch{
  case t.Hour()<12:
    fmt.Println("it's before noon")
  default:
    fmt.Println("it's after noon")
  }
  // 類型 switch 比較了類型而非值
  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 中,數(shù)組是特定長(zhǎng)度元素的編號(hào)序列。

package main
import "fmt"
func main(){
  // 這里創(chuàng)建了一個(gè) 5 個(gè) int 元素的數(shù)組。默認(rèn)是零值,對(duì)于 int 而言就是 0
  var a [5]int
  fmt.Println("emp:",a)
  // 可以通過(guò) array[index]=value 語(yǔ)法設(shè)值,或者通過(guò) array[index] 取值
  a[4]=100
  fmt.Println("set:",a)
  fmt.Println("get:",a[4])
  // 內(nèi)置的 len 函數(shù)返回?cái)?shù)組的長(zhǎng)度
  fmt.Println("len:",len(a))
  // 聲明并初始化數(shù)組
  b:=[5]int{1,2,3,4,5}
  fmt.Prinln("dc1:",b)
  // 數(shù)組是一維的,但你可以組合類型來(lái)構(gòu)建多維數(shù)組結(jié)構(gòu)
  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)
}

當(dāng)使用 fmt.Println 方法打印時(shí),數(shù)組將以 [v1 v2 v3 …] 的形式展現(xiàn)

Slices

slice 是一個(gè)重要的數(shù)據(jù)類型,對(duì)于序列提供了比數(shù)組更強(qiáng)大的接口

package main
import "fmt"
func main(){
  // 與數(shù)組不同,切片僅由其包含的元素(不是元素的數(shù)量)鍵入。 
  // 要?jiǎng)?chuàng)建一個(gè)非零長(zhǎng)度的空切片,請(qǐng)使用內(nèi)置的 make。 這里我們制作長(zhǎng)度為 3 的字符串(最初為零值)。
  s:=make([]string,3)
  fmt.Println("emp:",s)
  // 可以像數(shù)組一樣 set 和 get
  s[0]="a"
  s[1]="b"
  s[2]="c"
  fmt.Println("set:",s)
  fmt.Println("get:",s[2])
  //len 能夠返回 slice 的長(zhǎng)度
  fmt.Println("len:",len(s))
  // 作為這些基本操作的補(bǔ)充,slice 支持一些令其比數(shù)組更豐富的東西。
  // 其中一個(gè)是 append 函數(shù),其返回一個(gè)包含一個(gè)或多個(gè)新值的 slice
  // 注意需要接受 append 的返回值來(lái)獲取新的 slice 值
  s=append(s,"d")
  s=append(s,"e","f")
  fmt.Println("apd:",s)
  //slice 可以復(fù)制
  c:=make([]string,len(s))
  copy(c,s)
  fmt.Println("cpy:",c)
  //slice 支持切片操作,語(yǔ)法為 slice[low:high]。如下將得到元素 s[2],s[3] 和 s[4]
  l:=s[2:5]
  fmt.Println("sl1:",l)
  // 如下切片截止到(不包含)s[5]
  l=s[:5]
  fmt.Pritnln("sl2:",l)
  // 如下切片從 s[2] 開始(包含)
  l=s[2:]
  fmt.Println("sl3:",l)
  // 聲明并初始化 slice
  t:=[]string{"g","h","i"}
  fmt.Println("dcl:",t)
  //slice 也可以組織成一個(gè)多為數(shù)據(jù)結(jié)構(gòu),內(nèi)部的 slice 長(zhǎng)度可以變化,這與多維數(shù)組不同
  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 的展示結(jié)果很相似

Maps

Map 是 Go 內(nèi)置的關(guān)聯(lián)數(shù)據(jù)類型(其他語(yǔ)言可能成為哈希或者字典)

package main
import "fmt"
func main(){
  // 要?jiǎng)?chuàng)建一個(gè)空的 map,使用內(nèi)置 make 函數(shù):make(map[key-type]val-type)
  m:=make(map[string]int)
  // 通過(guò)經(jīng)典的 name[key]=val 語(yǔ)法來(lái)設(shè)置 key/value 對(duì)
  m["k1"]=7
  m["k2"]=13
  fmt.Println("map:",m)
  // 通過(guò) name[key] 來(lái)獲取一個(gè) value
  v1:=m["k1"]
  fmt.Println("v1:",v1)
  //len 函數(shù)返回 map 中鍵值對(duì)的個(gè)數(shù)
  fmt.Println("len:",len(m))
  // 內(nèi)置的 delete 函數(shù)將移除 map 中的鍵值對(duì)
  delete(m,"k2")
  fmt.Println("map:",m)
  // 第一個(gè)值是該 key 的 value,但此處不需要,故使用空白占位符“_”忽略
  // 第二個(gè)可選的返回值表明該鍵是否在 map 中,這樣可以消除不存在的鍵,和鍵值為 0 或者 "" 的歧義
  _,prs:=m["k2"]
  fmt.Println("prs:",prs)
  // 聲明并初始化 map
  n:=map[string]int{"foo":1,"bar":2}
  fmt.Println("map:",n)
}

map 將以 [k:v k:v] 的形式打印

Range

range 可以遍歷各種數(shù)據(jù)結(jié)構(gòu)中的元素。

package main
import "fmt"
func main(){
  // 這里使用 range 來(lái)計(jì)算元素的和,數(shù)組也是類似的用法
  nums:=[]int{2,3,4}
  sum:=0
  for _,num:=range nums{
    sum+=num
  }
  fmt.Println("sum:",sum)
  // 在 slice 和 array 上的 range 均為每個(gè)條目提供了索引和值
  for i,num:=range nums{
    if num==3{
      fmt.Pritnln("index",i)
    }
  }
  //map 上的 range 通過(guò) key/value 對(duì)進(jìn)行遍歷
  kvs:=map[string]string{"a":"apple","b":"banana"}
  for k,v:=range kvs{
    fmt.Printf("%s -> %s\n",k,v)
  }
  //range 可以僅通過(guò) key 進(jìn)行遍歷
  for k:= range kvs{
    fmt.Println("key:",k)
  }
  //range 作用在 string 上將得到 unicode code points,第一個(gè)值是字符的起始字節(jié)索引,第二個(gè)值是字符本身
  for i,c:range "go" {
    cmt.Println(i,c)
  }
}

Functions

package main
import "fmt"
// 這個(gè)函數(shù)接收兩個(gè) int 并以 int 類型返回他們的和
func plus(a int,b int) int{
  //Go 需要顯式的 return 語(yǔ)句,它不會(huì)自動(dòng)返回最后一個(gè)表達(dá)式的值
  return a+b
}
// 如果有多個(gè)連續(xù)的相同類型的參數(shù),可以忽略前面的類型聲明
func plusPlus(a,b,c int) int{
  return a+b+c
}
func main(){
  // 如你所想的那樣調(diào)用函數(shù)
  res := plus(1,2)
  fmt.Println("1+2=",res)
  res = plusPlus(1,2,3)
  fmt.Println("1+2+3=",res)
}

Multiple Return Values

Go 內(nèi)置支持了返回多個(gè)值。這一特點(diǎn)經(jīng)常用于 Go 的習(xí)慣用法,例如同時(shí)返回結(jié)果和錯(cuò)誤值

package main
import "fmt"
// 方法簽名中的 (int,int) 表明它將返回兩個(gè) int 值
func vals()(int,int){
  return 3,7
}
func main(){
  // 這里我們通過(guò)多重賦值來(lái)使用兩個(gè)不同的返回值
  a,b := vals()
  fmt.Println(a)
  fmt.Println(b)
  // 如果只需要返回結(jié)果的子集,使用空白占位符 _
  _,c := vals()
  fmt.Println(c)
}

Variadic Functions

變參函數(shù),可以使用任意數(shù)量的參數(shù)來(lái)進(jìn)行調(diào)用。例如 fmt.Println 是一個(gè)常見(jiàn)的變參函數(shù)。

package main
import "fmt"
// 這是一個(gè)接收任意數(shù)量的 int 值的函數(shù)
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)
  // 如果你已經(jīng)在一個(gè) slice 中定義了多個(gè)參數(shù),可以使用 func(slice...) 來(lái)直接應(yīng)用到變參函數(shù)中
  nums :=[]int{1,2,3,4}
  sum(nums...)
}

Closures

Go 支持匿名函數(shù),它可以形成閉包。當(dāng)你想要定義一個(gè)不記名的內(nèi)部函數(shù)時(shí),匿名函數(shù)就很有用了。

package main
import "fmt"
//intSeq 函數(shù)返回另一個(gè)函數(shù),它定義在了 intSeq 函數(shù)內(nèi)部,并且是匿名的。
// 返回的函數(shù)關(guān)閉變量 i 以形成閉包
func intSeq() func int{
  i := 0
  return func() int{
    i+=1
    return i
  }
}
func main(){
  // 調(diào)用 intSeq,并將結(jié)果 (一個(gè)函數(shù)) 賦予 nextInt
  // 這個(gè)函數(shù)持有一個(gè)自己的 i 值,每次調(diào)用 nextInt 時(shí)都會(huì)更新
  nextInt:=intSeq()
  // 多次調(diào)用 nextInt 函數(shù)可以看到閉包的效果
  //zephyr: 如非閉包寫法,每次函數(shù)都會(huì)進(jìn)行初始化變量 i,反復(fù)調(diào)用 intSeq 不會(huì)有這個(gè)效果
  fmt.Println(nextInt())
  fmt.Println(nextInt())
  fmt.Pritnln(nextInt())
  // 要確認(rèn)該狀態(tài)對(duì)該特定函數(shù)是唯一的,請(qǐng)創(chuàng)建并測(cè)試一個(gè)新函數(shù)。
  newInts:=intSeq()
  fmt.Println(newInts())
}

Recursion

Go 支持遞歸函數(shù)。下面是一個(gè)經(jīng)典的斐波那契數(shù)列示例

package main
import "fmt"
func fact(n int) int {
  if n==0 {
    return 1
  }
  //fact 函數(shù)調(diào)用自身,直到 n 為 0
  return n * fact(n-1)
}
func main(){
  fmt.Println(fact(7))
}

Pointers

Go 可以使用指針,讓你在程序中傳遞值或記錄的引用。

下面通過(guò)兩種方式的對(duì)比來(lái)展示指針的使用:zerovalzeroptr

package main
import "fmt"
//zeroval 將取得 ival 的值的拷貝,與調(diào)用方不同
func zeroval(ival int){
  ival = 0
}
//zeroptr 有一個(gè) *int 類型的參數(shù),代表它接收的是一個(gè)指針
func zeroptr(iptr *int){
  //*iptr 解引用,從內(nèi)存指定地址中獲取存放的值
  // 對(duì)解引用指針的賦值將改變指定地址上的值
  *iptr = 0
}
func main(){
  i:=1
  fmt.Println("initial:",i)
  zeroval(i)
  fmt.Println("zeroval:",i)
  //&i 語(yǔ)法將獲得變量 i 的內(nèi)存地址,也就是指向變量 i 的指針
  zeroptr(&i)
  fmt.Println("zeroptr:",i)
  // 指針也可以被打印
  fmt.Println("pointer:",&i)
}

zeroval 沒(méi)有改變 main 函數(shù)中 i 的值,而 zeroptr 會(huì),因?yàn)樗鼡碛兄赶蜃兞?i 的內(nèi)存地址。

Structs

Go 的 Struct 結(jié)構(gòu)是字段類型的集合。對(duì)于從記錄中將數(shù)據(jù)組織到一起很有幫助。

package main
import "fmt"
type person struct{
  name string
  age int
}
func main(){
  // 這個(gè)語(yǔ)法創(chuàng)建了一個(gè)新的 struct
  fmt.Println(person{"Bob",20})
  // 在初始化 struct 時(shí),可以指定字段名
  fmt.Println(person{name:"Alice",age:30})
  // 被忽略的字段將會(huì)被初始化為零
  fmt.Println(person{name:"Fred"})
  // 一個(gè) & 將產(chǎn)生 struct 的指針
  fmt.Println(&person{name:"Ann",age:40})
  s:=person{name:"Sean",age:50}
  // 通過(guò)點(diǎn)號(hào)訪問(wèn)結(jié)構(gòu)體中的字段
  fmt.Println(s.name)
  sp:=&s
  fmt.Println(sp.age)
  // 對(duì)于結(jié)構(gòu)體的指針也可以使用點(diǎn)號(hào)操作符,指針將會(huì)自動(dòng)解引用
  sp.age=51
  // 結(jié)構(gòu)體是可變的
  fmt.Println(sp.age)
}

沉默是金

Methods

Go 支持在結(jié)構(gòu)體上定義方法。

package main
import "fmt"
type rect struct{
  width,height int
}
//area 方法有一個(gè) rect 指針類型的接收器
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}
  // 調(diào)用定義的方法
  fmt.Println("area:",r.area())
  fmt.Println("perim:",r.perim())
  //Go 為方法調(diào)用自動(dòng)處理了值和引用的轉(zhuǎn)換。使用指針接收器可以避免獲得方法調(diào)用的拷貝 (?) 或允許方法修改接收到的 struct 值
  rp:=&r
  fmt.Println("area:",rp.area())
  fmt.Println("perim:",rp.perim())
}

Interfaces

接口是方法簽名的命名集合。

package main
import "fmt"
import "math"
// 這是一個(gè) geometry 的基本接口,本例中將在 rect 類型和 circle 類型中實(shí)現(xiàn)這個(gè)接口
type geometry interface{
  area() float64
  perim() float64
}
type rect struct{
  width,height float64
}
type circle struct{
  radius float64
}
// 在 Go 中,實(shí)現(xiàn)一個(gè)接口只需要實(shí)現(xiàn)其中定義的所有方法即可
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
}
// 如果變量是接口類型,那么它可以調(diào)用接口內(nèi)定義的方法
// 這是一個(gè)通用的 measure 方法,利用它能夠工作在任何 geometry 上
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}
  //rect 和 circle 均實(shí)現(xiàn)了 geometry 接口,所以可以作為 measure 的參數(shù)
  measure(r)
  measure(c)
}

Errors

在 Go 中,傳遞錯(cuò)誤的慣用法是通過(guò)明確的,分離的返回值。這和 Java 活 Ruby 中的 exception 以及 C 中重載使用單個(gè)的結(jié)果 / 錯(cuò)誤值不同。Go 的方法使得很容易看出哪些函數(shù)返回錯(cuò)誤,并使用與任何其他非錯(cuò)誤任務(wù)相同的語(yǔ)言結(jié)構(gòu)來(lái)處理它們。

package main
import "errors"
import "fmt"
// 按照慣例,錯(cuò)誤是最后一個(gè)返回值,類型為 error,一個(gè)內(nèi)置的接口。
func f1(arg int)(int,error){
  if arg==42{
    //errors.New 使用給定的錯(cuò)誤信息構(gòu)建了一個(gè)基本的 error 值
    return -1,errors.New("can't work with 42")
  }
  // 在 error 位置上放 nil 值代表沒(méi)有錯(cuò)誤
  return arg+3,nil
}
// 通過(guò)實(shí)現(xiàn) Error() 方法,可以自定義錯(cuò)誤類型。這里有一個(gè)上例的變種
// 使用一個(gè)自定義類型來(lái)顯式地展示一個(gè)參數(shù)錯(cuò)誤
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{
    // 這里我們使用 &argError 語(yǔ)法來(lái)創(chuàng)建一個(gè)新的結(jié)構(gòu)體,提供了 arg 和 prob 兩個(gè)域的值
    return -1,&argError{arg,"can't work with it"}
  }
  return arg+3,nil
}
func main(){
  // 下面的兩個(gè)循環(huán)測(cè)試了每個(gè)返回 error 的函數(shù)
  // 注意在 if 中的錯(cuò)誤檢查是 Go 代碼中的習(xí)慣用法
  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)
    }
  }
  // 如果要以編程方式使用自定義錯(cuò)誤中的數(shù)據(jù),
  // 則需要通過(guò)類型斷言將錯(cuò)誤作為自定義錯(cuò)誤類型的實(shí)例獲取。
  _,e:=f2(42)
  if ae,ok := e.(*argError);ok{
    fmt.Println(ae.arg)
    fmt.Println(ae.prob)
  }
}

Goroutines

goroutine 是一個(gè)輕量級(jí)的執(zhí)行線程。

package main
import "fmt"
func f(from string){
  for i:=0;i<3;i++{
    fmt.Println(from,";",i)
  }
}
func main(){
  // 假設(shè)我們有個(gè) f(s) 的函數(shù)調(diào)用。這里我們通過(guò)一般方法調(diào)用,令其同步執(zhí)行
  f("direct")
  // 要想讓這個(gè)函數(shù)在 goroutine 中觸發(fā),使用 go f(s)。這個(gè)新的 goroutine 將會(huì)與調(diào)用它的并行執(zhí)行
  go f("goroutine")
  // 我們也可以啟動(dòng)一個(gè)調(diào)用匿名函數(shù)的 goroutine
  go func(msg string){
  fmt.Println(msg)
  }("going")
  // 現(xiàn)在,這兩個(gè)方法調(diào)用在獨(dú)立的 goroutine 中異步執(zhí)行了,故方法執(zhí)行直接落到了這里
  //Scanln 代碼需要在程序退出前按下一個(gè)鍵
  var input string
  fmt.Scanln(&input)
  fmt.Println("done")
}

當(dāng)我們運(yùn)行這個(gè)程序的時(shí)候,我們將首先看到阻塞調(diào)用,然后是兩個(gè) goroutine 的交錯(cuò)輸出。這個(gè)交錯(cuò)反應(yīng)了 goroutine 在 Go 運(yùn)行時(shí)是并發(fā)執(zhí)行的。

Channels

Channel 是連接并發(fā)執(zhí)行的 goroutine 的管道。你可以從一個(gè) goroutine 傳遞值到 channel 中,再在另一個(gè) goroutine 接收它。

package main
import "fmt"
func main(){
  // 通過(guò) make(chan val-type) 創(chuàng)建新的 channel
  //channel 的類型依賴于它們要傳遞的值
  messages:=make(chan string)
  // 向 channel 傳遞值使用 channel <- 語(yǔ)法。在這里我們從一個(gè)新的 goroutine 中發(fā)送了一個(gè) "ping" 到 message 通道中
  go func(){messages<-"ping"}()
  //<-channel 語(yǔ)法從 channel 中獲取值。這里我們接收了上面發(fā)送的 "ping" 信息并打印
  msg:=<-messages
  fmt.Println(msg)
}

當(dāng)我們運(yùn)行這個(gè)程序時(shí),"ping" 信息成功的通過(guò)我們的 channel 從一個(gè) goroutine 傳遞到了另一個(gè)。

默認(rèn)情況下,發(fā)送和接收在發(fā)送者和接受者都準(zhǔn)備好之前阻塞。這個(gè)特性允許我們?cè)诔绦蚪Y(jié)尾等待 "ping" 信息而無(wú)需使用其他的同步手段

Channel Buffering

默認(rèn)下 channel 沒(méi)有緩沖區(qū),這意味著他們將只有在響應(yīng)的接收者 (<-chan) 準(zhǔn)備好時(shí),才能允許發(fā)送 (chan<-)。具有緩沖區(qū)的 channel,接受有限個(gè)數(shù)的值,而無(wú)需相應(yīng)的接收者。

package main
import "fmt"
func main(){
  // 這里我們創(chuàng)建了一個(gè)能夠緩沖 2 個(gè)字符串值的 channel
  messages:=make(chan string,2)
  // 由于 channel 帶有緩沖區(qū),我們可以發(fā)送值,無(wú)需響應(yīng)的并發(fā)接收
  messages<-"buffered"
  messages<-"channel"
  // 稍后,我們像往常一樣,接收了這兩個(gè)值
  fmt.Println(<-messages)
  fmt.Println(<-messages)
}

Channel Synchronization

我們可以使用 channel 來(lái)跨 goroutine 同步執(zhí)行。這里是一個(gè)使用阻塞接收來(lái)等待 goroutine 結(jié)束的例子。

package main
import "fmt"
import "time"
// 這個(gè)方法將在一個(gè) goroutine 中運(yùn)行。
//done channel 用來(lái)通知其他的 goroutine 這個(gè)方法執(zhí)行完畢
func worker(done chan bool){
  fmt.Print("working...")
  time.Sleep(time.Second)
  fmt.Println("done")
  // 發(fā)送一個(gè)值來(lái)通知這里已經(jīng)做完
  done<-true
}
func main(){
  啟動(dòng)一個(gè)worker goroutine,賦予它用以通知的channel
  done:=make(chan bool,1)
  go worker(done)
  // 在 channel 接收到來(lái)自 worker 的通知前,保持阻塞
  <-done
}

如果你移除了 <-done 行,這個(gè)程序可能會(huì)在 worker 開始前就結(jié)束。

Channel Directions

當(dāng)把 channel 用作函數(shù)的參數(shù)時(shí),你可以指定一個(gè) channel 是否只發(fā)送或者只接收數(shù)據(jù)。這種特異性增加了程序的類型安全性。

package main
import "fmt"
//ping 函數(shù)只接受一個(gè)發(fā)送數(shù)據(jù)的 channel,如果試圖在其上獲取數(shù)據(jù),將會(huì)引發(fā)編譯時(shí)異常
func ping(pings chan<- string,msg string){
  pings<-msg
}
//pong 函數(shù)接受一個(gè)通道用于接收 (pings),另一個(gè)用于發(fā)送 (pongs)
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 讓你能夠等待多個(gè) channel 操作。通過(guò) select 結(jié)合 goroutine 和 channel 是 Go 的重要特色。

package main
import "time"
import "fmt"
func main(){
  // 本例中我們將在兩個(gè)通道中進(jìn)行選擇
  c1:=make(chan string)
  c2:=make(chan string)
  // 每個(gè)通道都會(huì)在一定時(shí)間后接收到一個(gè)值,在并發(fā)的 goroutine 中模擬阻塞 RPC 操作執(zhí)行
  go func(){
    time.Sleep(time.Second*1)
    c1<-"one"
  }()
  go func(){
    time.Sleep(time.Second*2)
    c2<-"two"
  }()
  // 我們將使用 select 來(lái)同時(shí)等待這兩個(gè)值,當(dāng)它們到達(dá)時(shí)打印。
  for i:=0;i<2;i++{
    select{
    case msg1:=<-c1:
      fmt.Println("received",msg1)
    case msg2:=<-c2:
      fmt.Println("received",msg2)
    }
  }
}

按照預(yù)期,我們將接收到 "one",然后 "two"。

注意,整個(gè)執(zhí)行只需要大約 2 秒的時(shí)間,因?yàn)?1 秒和 2 秒的沉睡是并發(fā)執(zhí)行的。

Timeouts

超時(shí)對(duì)于連接到外部資源的程序或其他需要綁定執(zhí)行時(shí)間的程序很重要。在 Go 中,可以通過(guò) channel 和 select 輕松而優(yōu)雅的實(shí)現(xiàn)超時(shí)。

package main
import "time"
import "fmt"
func main(){
  // 在本例中,假設(shè)我們執(zhí)行了一個(gè)外部調(diào)用,它在兩秒后將結(jié)果返回到通道 c1 上
  c1:=make(chan string,1)
  go func(){
    time.Sleep(time.Second*2)
    c1<-"result 1"
  }()
  // 這里是用 select 實(shí)現(xiàn)超時(shí)。res:=<-c1 等待一個(gè)結(jié)果,而 <-Time.After 等待超時(shí) 1 秒后發(fā)送一個(gè)值。
  // 由于 select 將在有第一個(gè)準(zhǔn)備就緒的接收時(shí)繼續(xù),我們會(huì)在操作超過(guò)允許的 1 秒時(shí)進(jìn)入超時(shí)事件。
  select{
  case res:=<-c1:
    fmt.Println(res)
  case <-time.After(time.Second*1):
    fmt.Println("timeout 1")  
  }
  // 如果我們?cè)试S一個(gè)更長(zhǎng)的超時(shí)時(shí)間 3 秒,則將能成功得到 c2 的值,并打印
  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")
  }
}

運(yùn)行這個(gè)程序,將顯示第一個(gè)操作超時(shí)了,第二個(gè)則成功。

使用 select 超時(shí)模式需要在通道上進(jìn)行結(jié)果通訊。一般情況下這是很好的主意,因?yàn)槠渌闹匾?Go 特性基于通道和選擇。我們將在之后看到有關(guān)的兩個(gè)例子:timer 和 ticker

沉默是金 - MarkHoo

Non-Blocking Channel Operations

channel 上簡(jiǎn)單的發(fā)送和接收是阻塞的。然而,我們可以使用 select 和 default 子句來(lái)實(shí)現(xiàn)非阻塞發(fā)送、接收甚至非阻塞的多路選擇。

package main
import "fmt"
func main(){
  messages:=make(chan string)
  signals:=make(chan bool)
  // 這是一個(gè)非阻塞的接收。如果 message 的值可以獲取,select 將隨值進(jìn)入 <-message 子句
  // 否則將立刻進(jìn)入 default 事件
  select{
  case msg:=<-messages:
    fmt.Println("received message",msg)
  default:
    fmt.Pritln("no message received")
  }
  msg:="hi"
  // 類似的有非阻塞發(fā)送
  select{
  case messages<-msg:
    cmt.Println("sent message",msg)
  default:
    fmt.Println("no message sent")
  }
  // 我們可以在 default 上使用多個(gè)事件來(lái)實(shí)現(xiàn)多路非阻塞 select。
  // 這里我們?cè)噲D在 message 和 signal 上均進(jìn)行非阻塞接收
  select{
  case msg:=<-messages:
    fmt.Println("received message",msg)
  case sig:=<-signals:
    fmt.Println("received signal",sig)
  default:
    fmt.Println("no activity")
  }
}

Closing Channels

關(guān)閉通道意味著不會(huì)再有值在其上發(fā)送。這對(duì)于通道的接收方通訊完成很有幫助

package main
import "fmt"
// 在這個(gè)例子中,我們將使用一個(gè)作業(yè)通道將工作從主函數(shù) goroutine 傳遞給工人 goroutine。當(dāng)沒(méi)有更多作業(yè)給工人時(shí),我們將關(guān)閉工作渠道。
func main(){
  jobs:=make(chan int,5)
  done:=make(chan bool)
  // 下面是工人 goroutine。它通過(guò) j,more:=<-jobs 反復(fù)獲取作業(yè)
  // 在這個(gè) 2 返回值的接收中,如果作業(yè)關(guān)閉,所有值都已接收,more 會(huì)變?yōu)?false
  // 我們用其在完成所有作業(yè)時(shí)進(jìn)行已完成通知
  go func(){
    for{
      j,more:=<-jobs
      if more{
        fmt.Println("received job",j)
      }else{
        fmt.Println("received all jobs")
        done<-true
        return
      }
    }
  }()
  // 此處向工人發(fā)送了 3 個(gè)作業(yè),然后關(guān)閉它
  for j:=1;j<=3;j++{
    jobs<-j
    fmt.Println("sent job",j)
  }
  close(jobs)
  fmt.Println("sent all jobs")
  // 使用前面見(jiàn)過(guò)的同步機(jī)制來(lái)等待工人、
  <-done
}

Range over Channels

在前面的例子中,我們看到如何使用 for 和 range 來(lái)遍歷基本的數(shù)據(jù)結(jié)構(gòu)。我們同樣可以使用這個(gè)語(yǔ)法來(lái)遍歷通道上接收的值。

package main
import "fmt"
func main(){
  // 遍歷 queue 通道上的 2 個(gè)值
  queue:=make(chan string,2)
  queue<-"one"
  queue<-"two"
  close(queue)
  //range 遍歷通道上接收的每個(gè)值。由于在上面關(guān)閉了通道,這個(gè)遍歷將在接收 2 個(gè)值后結(jié)束。
  for elem:=range queue{
    fmt.Println(elem)
  }
}

Timers

我們常想在未來(lái)的某一刻執(zhí)行 Go 代碼,或者在某一時(shí)間內(nèi)重復(fù)執(zhí)行。Go 內(nèi)置的 timer 和 ticker 使這些任務(wù)十分簡(jiǎn)易。首先我們看看 timer,然后再看 ticker。

package main
import "time"
import "fmt"
func main(){
  //timer 代表未來(lái)的一個(gè)單獨(dú)事件。你要告訴它要等待多久,它提供一個(gè)通道,在指定時(shí)間發(fā)出通知。下面這個(gè) timer 將等待 2 秒鐘
  timer1:=time.NewTimer(time.Second*2)
  // 定時(shí)器通道由于操作 <-timer1.c 發(fā)生阻塞,直到它發(fā)送了一個(gè)值來(lái)表明定時(shí)器到時(shí)
  <-timer1.C
  fmt.Println("Timer 1 expired")
  // 如果你僅僅想等待一段時(shí)間,可以用 time.Sleep,使用 timer 的一個(gè)原因是,你可以在計(jì)時(shí)結(jié)束前取消,如下例:
  timer2:=time.NewTimer(time.Second)
  go func(){
    <-timer2.C
    fmt.Println("Timer 2 expired")
  }()
  stop2:=timer2.Stop()
  if stop2{
    fmt.Println("Timer 2 stopped")
  }
}

第一個(gè) timer 將在啟動(dòng)程序后大約 2 秒到時(shí),但第二個(gè)應(yīng)會(huì)在其有機(jī)會(huì)到時(shí)前先行停止。

Tickers

timer 用來(lái)在將來(lái)的某一時(shí)間做某事一次。而 ticker 會(huì)在一個(gè)指定時(shí)間間隔重復(fù)做某事。這里是一個(gè) ticker 的例子,它會(huì)在我們停止之前定期觸發(fā)。

package main
import "time"
import "fmt"
func main(){
  //ticker 與 timer 的機(jī)制相似,都是一個(gè)發(fā)送值的通道
  // 這里我們使用 channel 內(nèi)置的 range 來(lái)遍歷每 500ms 到達(dá)的值
  ticker:=time.NewTicker(time.Millisecond*500)
  go func(){
    for t:=range ticker.C{
    fmt.Prinltn("Tick at",t)
    }
  }()
  //ticker 可以像 timer 一樣停止。一旦 ticker 停止,將不會(huì)在其通道上接收到任何信息。我們將在 1600ms 后結(jié)束。
  time.Sleep(time.Millisecond*1600)
  ticker.Stop()
  fmt.Println("Ticker stopped")
}

運(yùn)行這個(gè)程序,在結(jié)束之前,應(yīng)該會(huì) tick 三次。

Worker Pools

本例中我們將看到如何使用 goroutine 和 channel 來(lái)實(shí)現(xiàn)一個(gè)工人池

package main
import "fmt"
import "time"
// 這里是我們將要運(yùn)行多個(gè)并發(fā)實(shí)例的工人。他們將從 jobs 通道獲取任務(wù),并將相應(yīng)的結(jié)果返回到 results 上。我們將在每個(gè)作業(yè)沉睡 1 秒,來(lái)模擬一個(gè)復(fù)雜的任務(wù)。
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(){
  // 為了使用工人池,我們需要派發(fā)任務(wù)并且回收結(jié)果。我們使用兩個(gè)通道來(lái)做這件事
  jobs:=make(chan int,100)
  results:=make(chan int,100)
  // 這里啟動(dòng)了 3 個(gè)工人,初始時(shí)阻塞,因?yàn)楫?dāng)前沒(méi)有作業(yè)
  for w:=1;w<=3;w++{
    go worker(w,jobs,results)
  }
  // 添加 5 個(gè)作業(yè),然后關(guān)閉通道來(lái)表明這就是所有的工作
  for j:=1;j<=5;j++{
    jobs<-j
  }
  close(jobs)
  
  for a:=1;a<=5;a++{
    <-results
  }
}

運(yùn)行的項(xiàng)目展示了有 5 個(gè)作業(yè)得以被不同的工人執(zhí)行。盡管總共有 5 秒鐘的時(shí)間,這個(gè)程序只需要 2 秒鐘,因?yàn)橛?3 名工作人員同時(shí)進(jìn)行操作。

同時(shí)啟動(dòng)了 3 個(gè) worker,來(lái)監(jiān)聽通道是否有作業(yè)發(fā)出,無(wú)作業(yè)時(shí) worker 不會(huì)進(jìn)入循環(huán)體,為空操作。從而形成工人池

Rate Limiting

速率限制是控制資源利用和維護(hù)服務(wù)質(zhì)量的重要機(jī)制。Go 通過(guò) goroutine,channel 和 ticker 可以優(yōu)雅的支持速率控制。

package main
import "time"
import "fmt"
func main(){
  // 首先,我們看下基本的速率控制。
  // 假設(shè)我們想要控制處理的輸入請(qǐng)求,我們通過(guò)同一個(gè)通道來(lái)為這些請(qǐng)求提供服務(wù)
  requests:=make(chan int,5)
  for i:=1;i<=5;i++{
    request<-i
  }
  close(requests)
  //limiter 通道將每過(guò) 200 毫秒接收一次數(shù)據(jù)。這是速率控制策略中的調(diào)節(jié)器
  limiter:=time.Tick(time.Millisecond*200)
  // 在服務(wù)每個(gè)請(qǐng)求之前,通過(guò) limiter 通道阻塞接收,我們將自己限制在每 200ms 處理 1 個(gè)請(qǐng)求上。
  for req:=range request{
    <-limiter
    fmt.Println("request",req,time.now())
  }
  // 我們可能希望在我們的速率限制方案中允許短時(shí)間的請(qǐng)求,同時(shí)保留整體速率限制。 
  // 我們可以通過(guò)緩沖限制器通道來(lái)實(shí)現(xiàn)這一點(diǎn)。
  // 這個(gè) burstyLimiter 通道將允許多達(dá) 3 個(gè)事件的突發(fā)。
  burstyLimiter:=make(chan time.Time,3)
  // 填充通道,來(lái)展示可允許的突發(fā)
  for i:=0;i<3;i++{
    burstyLimiter<-time.Now()
  }
  // 每過(guò) 200 毫秒將試圖添加一個(gè)新值到 burstyLimiter,最多 3 個(gè)
  go func(){
    for t:=range time.Tick(time.Millisecond*200){
      burstyLimiter<-t
    }
  }()
  // 模擬 5 個(gè)輸入請(qǐng)求。前 3 個(gè)將受益于 burstyLimiter 的突發(fā)能力
  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())
  }
}

運(yùn)行我們的程序,我們看到第一批請(qǐng)求根據(jù)需要每 200 毫秒處理一次。

對(duì)于第二批請(qǐng)求,由于可突發(fā)速率限制,我們立即為前 3 個(gè)服務(wù),然后以約 200ms 的延遲提供剩余的 2 個(gè)。

Atomic Counters

Go 中管理狀態(tài)的主要機(jī)制是通過(guò)渠道進(jìn)行溝通。 我們以工人池為例。 還有一些管理狀態(tài)的其他選項(xiàng)。 這里我們來(lái)看一下使用 sync / atomic 包來(lái)進(jìn)行多個(gè) goroutines 訪問(wèn)的原子計(jì)數(shù)器。

package main
import "fmt"
import "time"
import "sync/atomic"

func main(){
  // 我們使用無(wú)符號(hào)整數(shù)來(lái)代表我們的計(jì)數(shù)器
  var ops unit64=0
  // 為了模擬并發(fā)更新操作,我們將啟動(dòng) 50 個(gè) goroutine,每個(gè)都會(huì)在每過(guò) 1 毫秒時(shí)增長(zhǎng)一次計(jì)數(shù)器
  for i:=0;i<50;i++{
    go func(){
      for{
        // 為了原子地增加計(jì)數(shù)器,我們使用 AddUint64,使用&語(yǔ)法給它我們的 ops 計(jì)數(shù)器的內(nèi)存地址。
        atomic.AddUnit64(&ops,1)
        // 在兩個(gè)增長(zhǎng)之間稍作等待
        time.Sleep(time.Millisecond)
      }
    }()
  }
  // 等一下讓一些操作積累起來(lái)。
  time.Sleep(time.Second)
  // 為了安全地使用計(jì)數(shù)器,當(dāng)它仍被其他 goroutine 更新時(shí),我們通過(guò) LoadUint64 將當(dāng)前值的副本提取到 opsFinal 中。
  // 如上所述,我們需要給出這個(gè)函數(shù)來(lái)獲取值的內(nèi)存地址和操作。
  opsFinal:=atomic.LoadUnit64(&ops)
  fmt.Println("ops:",opsFinal)
}

運(yùn)行程序可以看到我們執(zhí)行了大約 40,000 次操作。

Mutexes

在前面的例子中我們看到如何使用原子操作管理簡(jiǎn)單的計(jì)數(shù)器狀態(tài)。為了處理更復(fù)雜的狀態(tài),我們可以使用一個(gè)mutext來(lái)安全的訪問(wèn)不同 gorotine 之間的數(shù)據(jù)。

package main
import(
  "fmt"
  "math/rand"
  "sync"
  "sync/atomic"
  "time"
)
func main(){
  // 這個(gè)例子中,狀態(tài)是一個(gè) map
  var state=make(map[int]int)
  // 這個(gè) mutext 將同步訪問(wèn)狀態(tài)值
  var mutex=&sync.Mutex{}
  // 我們將記錄做了多少讀寫操作
  var readOps unit64=0
  var writeOps unit64=0
  // 這里我們啟動(dòng)了 100 個(gè) goroutine 來(lái)重復(fù)讀取狀態(tài)值
  // 每毫秒在每個(gè) goroutine 執(zhí)行一次
  for r:=0;r<100;r++{
    go func(){
      total:=0
      for{
        // 每次讀取時(shí)我們將獲得一個(gè)進(jìn)入的鑰匙
        //Lock() 住 mutex 來(lái)保證獨(dú)家訪問(wèn)狀態(tài)值
        // 在選中的鑰匙上讀取值,Unlock() 掉 mutext
        // 對(duì) readOps 計(jì)數(shù)加 1
        key:=rand.Intn(5)
        mutex.Lock()
        total+=state[key]
        mutex.Unlock()
        atomic.AddUnit64(&readOps,1)
        // 每次操作等待一下
        time.Sleep(time.Millisecond)
      }
    }()
  }
  // 再啟動(dòng) 10 個(gè)模擬寫的 goroutine,與讀取類似的模式
  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)
      }
    }()
  }
  // 讓這 10 個(gè) go 協(xié)程在 state 和 mutext 上工作一會(huì)兒
  time.Sleep(time.Second)
  // 獲取并且報(bào)告最終操作的個(gè)數(shù)。
  readOpsFinal:=atomic.LoadUnit64(&readOps)
  fmt.Println("readOps:",readOpsFinal)
  writeOpsFinal:=atomic.LoadUnit64(&writeOps)
  fmt.Println("writeOps:",writeOpsFinal)
  // 隨著最后一次鎖住狀態(tài),展示它是如何結(jié)束的
  mutex.Lock()
  fmt.Println("state:",state)
  mutex.Unlock()
}

運(yùn)行程序可以看到我們?cè)?mutex 同步狀態(tài)上執(zhí)行了將近 90,000 操作。

Stateful Goroutines

在上個(gè)例子我們使用 mutex 顯式鎖定多個(gè) goroutine 要同步訪問(wèn)的共享狀態(tài)。另一個(gè)選擇是使用 goroutine 和 channel 內(nèi)置的同步功能來(lái)達(dá)到相同的結(jié)果。這種基于渠道的方法與 Go 通過(guò)通信和擁有完全一個(gè) goroutine 的每個(gè)數(shù)據(jù)來(lái)共享內(nèi)存的想法相一致。

package main
import(
  "fmt"
  "math/rand"
  "sync/atomic"
  "time"
)
// 在這個(gè)例子中,狀態(tài)值將被一個(gè)單獨(dú)的 goroutine 擁有
// 這保證了數(shù)據(jù)不會(huì)受到并發(fā)訪問(wèn)的影響
// 為了讀或?qū)憼顟B(tài)值,其它 goroutine 將向擁有它的 goroutine 發(fā)送一個(gè)消息,然后接收其回復(fù)。
//readOp 和 writeOp 結(jié)構(gòu)封裝了這些請(qǐng)求和響應(yīng)
type readOp struct{
  key int
  resp chan int
}
type writeOp struct{
  key int
  val int
  resp chan bool
}
func main() {
  // 像之前一樣我們記錄執(zhí)行了多少次操作
  var readOp unit64=0
  var writeOps unit64=0
  //reads 和 writes 通道將被用于其它 goroutine 分別發(fā)送讀寫請(qǐng)求
  reads:=make(chan *readOp)
  writes:=make(chan *writeOp)
  // 這里便是擁有狀態(tài)值的 goroutine,與之前一樣是個(gè) map,但被私有化
  // 這個(gè) goroutine 反復(fù)選擇 reads 和 writes 通道,響應(yīng)到達(dá)的請(qǐng)求。
  // 首先執(zhí)行所請(qǐng)求的操作然后在響應(yīng)通道上發(fā)送值來(lái)表示成功執(zhí)行響應(yīng)
  //(或者 reads 期望的數(shù)據(jù))
  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
      }
    }
  }()
  // 啟動(dòng) 100 個(gè) goroutine,通過(guò)讀取通道來(lái)讀取有狀態(tài)的 goroutine
  // 每次讀取需要構(gòu)建一個(gè) readOp,通過(guò) reads 發(fā)送給它
  // 再通過(guò)所提供的 resp 通道獲取結(jié)果
  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)
      }
    }()
  }
  // 啟動(dòng) 10 個(gè)寫操作
  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)
}

運(yùn)行項(xiàng)目顯示基于 gouroutine 的狀態(tài)管理完成了大約 80,000 操作。

沉默是金 - MarkHoo

Sorting

sort 包實(shí)現(xiàn)了內(nèi)置和自定義類型的排序。首先看看內(nèi)置類型的排序。

package main
import "fmt"
import "sort"
func main(){
  //sort 改變了給定的 slice,而不是返回一個(gè)新的
  strs:=[]string{"c","a","b"}
  sort.Strings(strs)
  fmt.Println("Strings:",strs)
  ints:=[]int{7,2,4}
  sort.Ints(ints)
  fmt.Println("Ints:",ints)
  // 可以使用 sort 檢查一個(gè) slice 是不是已經(jīng)排好序了
  s:=sort.IntsAreSorted(ints)
  fmt.Println("Sorted:",s)
}

Sorting by Functions

有時(shí)候我們想要對(duì)一個(gè)集合進(jìn)行非自然順序的排序。例如,我們想要把字符串根據(jù)長(zhǎng)度而非字典順序排序,下面是一個(gè)定制排序的例子。

package main
import "fmt"
import "sort"
// 為了根據(jù)自定義函數(shù)排序,我們需要相應(yīng)的類型
// 這里我們創(chuàng)建了一個(gè) ByLength 類型
// 這就是一個(gè) []string 類型的別名
type ByLength []string
// 我們?cè)?ByLength 上實(shí)現(xiàn)了 sort 接口的 Len、Less 和 Swap 方法
// 這里我們想要按照字符串長(zhǎng)度的增序排列
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])
}
// 通過(guò)將原有的 fruits 片段轉(zhuǎn)換為 ByLength
// 就可以使用 sort.Sort 來(lái)進(jìn)行自定義排序了。
func main(){
  fruits:=[]string{"peach","banana","kiwi"}
  sort.Sort(ByLength(fruits))
  fmt.Println(fruits)
}

通過(guò)類似的模式創(chuàng)建自定義類型,實(shí)現(xiàn)三個(gè)接口方法,然后調(diào)用 sort,我們可以對(duì)集合進(jìn)行任意的排序。

Panic

panic通常指發(fā)生了未曾預(yù)料的錯(cuò)誤。大多數(shù)情況下,我們使用它來(lái)將不應(yīng)當(dāng)在正常操作中發(fā)生的東西快速失敗,或者不準(zhǔn)備妥善處理。

package main
import "os"
func main(){
  // 我們將在整個(gè)網(wǎng)站使用 panic 來(lái)檢查意外的錯(cuò)誤。
  // 這是該網(wǎng)站上唯一旨在 panic 的程序。
  panic("a problem")
  //panic 的一個(gè)常見(jiàn)作用是終止一個(gè)函數(shù)返回了一個(gè)不知道如何處理或者不想處理的錯(cuò)誤。
  // 這里是一個(gè) panic 的例子,在創(chuàng)建一個(gè)新文件時(shí)發(fā)生意外錯(cuò)誤
  _,err:=os.Create("/tmp/file")
  if err!=nil{
    panic(err)
  }
}

運(yùn)行這個(gè)程序?qū)⒁鹨粋€(gè) panic,打印錯(cuò)誤信息和 goroutine 蹤跡,并以非 0 狀態(tài)退出。

注意,不像一些用異常來(lái)處理大多錯(cuò)誤的語(yǔ)言,在 Go 的習(xí)慣用法中,盡可能使用錯(cuò)誤指示的返回值。

Defer

defer 用于確保一個(gè)函數(shù)調(diào)用在程序執(zhí)行中延遲作用,經(jīng)常用于清理目的。defer常用語(yǔ)其他語(yǔ)言的ensurefinnaly用的地方。

packgae main
import "fmt"
import "os"
// 假設(shè)我們想要?jiǎng)?chuàng)建并寫一個(gè)文件,在完成后關(guān)閉它。
func main(){
  // 在獲取一個(gè)文件對(duì)象后立即使用 defer 并關(guān)閉這個(gè)文件
  // 這個(gè)將在 main 函數(shù)末尾關(guān)閉的時(shí)候執(zhí)行,在 writeFile 完成后
  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()
}

運(yùn)行程序,確認(rèn)這個(gè)文件的確在寫之后再關(guān)閉的。

Collection Functions

我們經(jīng)常需要我們的程序?qū)?shù)據(jù)集合執(zhí)行操作,例如選擇滿足給定謂詞的所有項(xiàng)目或?qū)⑺许?xiàng)目映射到具有自定義函數(shù)的新集合。

一些語(yǔ)言通常的慣用法是使用泛型數(shù)據(jù)結(jié)構(gòu)和算法。 Go 不支持泛型; 在 Go 中,通常在程序和數(shù)據(jù)類型特別需要時(shí)提供集合功能。

以下是一些用于字符串切片的示例集合函數(shù)。 您可以使用這些示例來(lái)構(gòu)建自己的函數(shù)。 請(qǐng)注意,在某些情況下,直接內(nèi)聯(lián)集合操作代碼可能是最為清晰的,而不是創(chuàng)建和調(diào)用幫助函數(shù)。

package main
import "strings"
import "fmt"
// 返回目標(biāo)字符串 t 的第一個(gè)索引,如果沒(méi)有找到則返回 -1
func Index(vs []string,t string) int{
  for i,v:=range vs{
    if v==t{
      return i
    }
  }
  return -1
}
// 如果字符串在切片中,則返回 true
func Include(vs []string,t string) bool{
  return Index(vs,t)>=0
}
// 如果有一個(gè)字符串滿足期望的 f 則返回 true
func Any(vs []string,f func(string) bool) bool{
  for _,v:=range vs{
    if f(v){
      return true
    }
  }
  return false
}
// 所有的字符串滿足期望的 f 則返回 true
func All(vs []string,f func(string) bool) bool{
  for _,v:=range vs{
    if !f(v){
      return false
    }
  }
  return true
}
// 返回一個(gè)滿足給定方法的所有字符串
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
}
// 返回一個(gè)包含將 f 函數(shù)應(yīng)用于原始切片中每個(gè)字符串的結(jié)果的新切片。
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")
  }))
  // 以上示例全部使用匿名函數(shù),但也可以使用正確類型的命名函數(shù)。
  fmt.Println(Map(strs,strings.ToUpper))
}

String Functions

標(biāo)準(zhǔn)庫(kù)的 String 包提供了許多有用的字符串相關(guān)的函數(shù)。這里有些示例讓你對(duì)這個(gè)包有個(gè)初步的認(rèn)識(shí)。

package main
import s "strings"
import "fmt"
// 鑒于后文大量用到,我們給字符串輸出函數(shù)起一個(gè)別名
var p=fmt.Println
func main(){
  // 這里是字符串可以用的函數(shù)示例
  // 由于這些函數(shù)是包中的而不是字符串對(duì)象本身的方法,我們需要給他們傳遞一個(gè)字符串參數(shù)
  // 你可以在 strings 包下的 doc 找到更多的函數(shù)
  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()
  // 并非 strings 包的一部分,但是這里值得一提
  // 即獲取字符串長(zhǎng)度以及字符串中的某個(gè)字符
  p("Len:",len("hello"))
  p("Char:","hello"[1])
}

注意,獲取長(zhǎng)度和索引字符是工作在字節(jié)級(jí)別上的。Go 使用 UTF-8 編碼字符串。

String Formatting

Go 在經(jīng)典的printf上提供了優(yōu)秀的字符串格式化支持。這里有一些常見(jiàn)格式化的例子。

package main
import "fmt"
import "os"
type point struct{
  x,y int
}
func main(){
  //Go 提供了一些修飾符來(lái)格式化一般的 Go 數(shù)值
  // 例如,這里輸出了一個(gè) point 結(jié)構(gòu)體的實(shí)例
  p:=point{1,2}
  fmt.Printf("%v\n",p)
  // 如果值是結(jié)構(gòu)體,%+v 變量將輸出結(jié)構(gòu)體中的域名稱
  fmt.Printf("%+v\n",p)
  //%#v 將打印 Go 語(yǔ)法形式
  fmt.Printf("%#v\n",p)
  // 要想打印值的類型,使用 %T
  fmt.Printf("%T\n",p)
  // 格式化輸出布爾值
  fmt.Printf("%t\n",true)
  // 整數(shù)有許多格式化選項(xiàng),使用 %d 來(lái)進(jìn)行標(biāo)準(zhǔn)十進(jìn)制格式化
  fmt.Printf("%d\n",123)
  // 這樣打印了二進(jìn)制形式
  fmt.Printf("%b\n",14)
  // 這里打印了十進(jìn)制數(shù)字對(duì)應(yīng)的字符
  fmt.Printf("%c\n",33)
  //%x 提供了十六進(jìn)制編碼
  fmt.Printf("%x\n",456)
  // 浮點(diǎn)數(shù)也有許多格式化選項(xiàng),標(biāo)準(zhǔn)十進(jìn)制使用 %f
  fmt.Printf("%f\n",78.9)
  //%e 和 %E 格式化浮點(diǎn)數(shù)為科學(xué)計(jì)數(shù)法
  fmt.Printf("%e\n",123400000.0)
  fmt.Printf("%E\n",123400000.0)
  // 基本的字符串輸出,使用 %s
  fmt.Pirntf("%s\n","\"string\"")
  // 將 Go 代碼中的字符串加引號(hào)輸出 (等同參數(shù)形式)
  fmt.Printf("%q\n","\"string\"")
  // 將字符串十六進(jìn)制編碼化
  fmt.Printf("%x\n","hex this")
  // 打印一個(gè)指針
  fmt.Printf("%p\n",&p)
  // 控制數(shù)字輸出的寬度和精度,在 % 后面跟上數(shù)字可以控制寬度,. 后面跟上數(shù)字控制精度
  // 默認(rèn)靠右顯示,用空格填充
  fmt.Printf("|%6d|%6d|\n",12,345)
  fmt.Printf("|%6.2f|%6.2f|\n",1.2,3.45)
  // 靠左對(duì)齊使用 -
  fmt.Printf("|%-6.2f|%-6.2f|\n",1.2,3.45)
  // 你可能也需要控制字符串輸出的格式,特別是要以表格形式輸出的時(shí)候
  fmt.Printf("|%6s|%6s|\n","foo","b")
  fmt.Printf("|%-6s|%-6s|\n","foo","b")
  //Printf 將格式化的字符串打印到標(biāo)準(zhǔn)輸出上
  //Sprintf 格式化并返回這個(gè)字符串而不打印
  s:=fmt.Sprintf("a %s","string")
  fmt.Println(s)
  // 你也可以格式化并打印到 io.Writers 而非 os.StdOut
  fmt.Fprintf(os.Stderr,"an %s\n","error")
}

Regular Expressions

Go 為正則表達(dá)式提供了內(nèi)置的支持。這里是一些常用的正則相關(guān)的任務(wù)。

package main
import "bytes"
import "fmt"
import "regexp"
func main(){
  // 測(cè)試模式是否符合字符串
  math,_:=regexp.MatchString("p([a-z]+)ch","peach")
  fmt.Printf(match)
  // 上面我們直接使用了字符串模式。但是對(duì)于其他正則任務(wù),你需要先編譯一個(gè)正則表達(dá)式結(jié)構(gòu)體
  r,_:=regexp.Compile("p([a-z]+)ch")
  // 這種結(jié)構(gòu)體上有許多方法。這里是一個(gè)如同上面的匹配測(cè)試
  fmt.Println(r.MatchString("peach"))
  // 這里找到一個(gè)匹配
  fmt.Println(r.FindString("peach punch"))
  // 這里也是尋找第一個(gè)匹配,但返回起止的索引而非字符串
  fmt.Println(r.FindStringIndex("peach punch"))
  //submatch 包含整串匹配,也包含內(nèi)部的匹配。
  fmt.Println(r.FindStringSubmatch("peach punch"))
  // 與上面類似,返回整串匹配和內(nèi)部匹配的索引信息
  fmt.Println(r.FindStringSubmatchIndex("peach punch"))
  // 這些函數(shù)的 All 修飾將返回輸入中所有匹配的,不僅僅是第一個(gè)。例如找到所有匹配正則表達(dá)式的
  fmt.Println(r.FindAllString("peach punch pinch",-1))
  fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch",-1))
  // 第二個(gè)參數(shù)如果是非負(fù)數(shù),則將限制最多匹配的個(gè)數(shù)
  fmt.Println(r.FindAllString("peach punch pinch",2))
  // 例子中都是字符串參數(shù),并且用了類似于 MatchString 這樣的名字
  // 我們可以提供 []byte 參數(shù),并將函數(shù)名中的 String 去掉
  fmt.Println(r.Match([]byte("peach")))
  // 當(dāng)用正則表達(dá)式創(chuàng)建一個(gè)常量的時(shí)候你可以使用 MustCompile。
  // 一個(gè)純 Compile 不能用于常量,因?yàn)樗?2 個(gè)返回值。
  r=regexp.MustComplie("p([a-z]+)ch")
  fmt.Println(r)
  //regexp 包也能用于使用其他值替換字符串的子集
  fmt.Println(r.ReplaceAllString("a peach","<fruit>"))
  //Func 修飾允許你使用一個(gè)給定的函數(shù)修改匹配的字符串
  in:=[]byte("a peach")
  out:=r.ReplaceAllFunc(in,bytes.ToUpper)
  fmt.Println(string(out))
}

JSON

Go 內(nèi)置提供了 JSON 的編碼和解碼是,包括內(nèi)置和自定義的數(shù)據(jù)類型。

package main
import "encoding/json"
import "fmt"
import "os"
// 我們使用這兩個(gè)結(jié)構(gòu)體來(lái)展示自定義類型的編碼和解碼
type Response1 struct{
  Page int
  Fruits []string
}
type Response2 struct{
  Page int `json:"page"`
  Fruits []string `json:"fruits"`
}
func main(){
  // 首先我們來(lái)看編碼基本數(shù)據(jù)類型到 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))
  // 這里有些 slice 和 map 的例子,他們將按需編碼成 JSON 數(shù)組和對(duì)象。
  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 包將自動(dòng)編碼你的自定義數(shù)據(jù)類型
  // 他將在編碼后的輸出中只包含對(duì)外的域并且用名字作為 JSON 鍵
  res1D:=&Response1{
    Page:1,
    Fruits:[]string{"apple","peach","pear"}
  }
  res1B,_:=json.Marshal(res1D)
  fmt.Println(string(res1B))
  // 你可以在結(jié)構(gòu)體的域聲明上定制編碼后的 JSON 鍵名。
  // 可以看上面 Reponse2 的定義
  res2D:=&Reponse2{
    Page:1,
    Fruits:[]string{"apple","peach","pear"}
  }
  res2B,_:=json.Marshal(res2D)
  fmt.Println(string(res2B))
  // 我們需要提供一個(gè)變量來(lái)放 JSON 包編碼的值。
  //map[string]interface{} 將承載一個(gè)字符串?dāng)?shù)據(jù)類型的 map
  byt:=[]byte(`{"num":6.13,"strs":["a","b"]}`)
  var dat map[string]interface{}
  // 這里是相應(yīng)的解碼,以及相關(guān)錯(cuò)誤的檢查
  if err:=json.Unmarshal(byt,&dat);err!=nil{
    panic(err)
  }
  fmt.Println(dat)
  // 為了使用解碼的 map 的值,我們需要將它們轉(zhuǎn)換為合適的類型
  // 例如這里我們將 num 中餓值轉(zhuǎn)變?yōu)?float64 類型
  num:dat["num"].(float64)
  fmt.Println(num)
  // 訪問(wèn)內(nèi)部數(shù)據(jù)需要一系列的轉(zhuǎn)換
  strs:=dat["strs"].([]interface{})
  str1:=strs[0].(string)
  fmt.Println(str1)
  // 我們也可以將 JSON 解碼到定制的數(shù)據(jù)類型
  // 這樣為程序添加了額外的類型安全,并不再需要在訪問(wèn)數(shù)據(jù)的時(shí)候進(jìn)行類型斷言
  str:=`{"page":1,"fruits":["apple","peach"]}`
  res:=Response2{}
  json.Unmarshal([]byte(str),&res)
  fmt.Println(res)
  fmt.Println(res.Fruits[0])
  // 在上面的例子中我們經(jīng)常使用 bytes 和字符串在標(biāo)準(zhǔn)輸出上來(lái)進(jìn)行數(shù)據(jù)和 JSON 形式的交互
  // 我們也可以將 JSON 編碼流入到 os.Writers 甚至 HTTP 響應(yīng)體
  enc:json.NewEncoder(os.Stdout)
  d:=map[string]int{"apple":5,"lettuce":7}
  enc.Encode(d)
}

Time

Go 為時(shí)間和持續(xù)時(shí)間提供了額外支持。這里是一些例子。

package main
import "fmt"
import "time"
func main(){
  p:=fmt.Pritnln
  // 首先獲取當(dāng)前時(shí)間
  now:=time.Now()
  p(now)
  // 你可以創(chuàng)建一個(gè)時(shí)間結(jié)構(gòu)體,提供年月日等。
  // 時(shí)間經(jīng)常與地區(qū),例如時(shí)區(qū)關(guān)聯(lián)
  then:=time.Date(2009,11,17,20,34,58,651387237,time.UTC)
  p(then)
  // 可以按照需要抽取時(shí)間變量中的不同部分
  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))
  //sub 方法返回兩個(gè)時(shí)間中的時(shí)間長(zhǎng)度
  diff:=now.Sub(then)
  p(diff)
  // 我們可以計(jì)算不同單位的持續(xù)時(shí)間長(zhǎng)度
  p(diff.Hours())
  p(diff.Minutes())
  p(diff.Seconds())
  p(diff.Nanoseconds())
  // 可以使用 Add 來(lái)向前推進(jìn)一個(gè)給定的時(shí)間
  // 或者使用減號(hào)來(lái)倒退
  p(then.Add(diff))
  p(then.Add(-diff))
}

Epoch

程序中的一個(gè)常見(jiàn)需求是獲取秒、毫秒或微秒,自 Unix 時(shí)代,這里是 Go 的做法。

package main
import "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))
}

Time Formatting / Parsing

Go 支持時(shí)間格式化和解析,根據(jù)基于模式的布局。

package main
import "fmt"
import "time"
func main(){
  p:=fmt.Println
  // 這里是一個(gè)根據(jù) RFC3339 基本的格式化時(shí)間的例子,使用響應(yīng)的布局常量
  t:=time.Now()
  p(t.Format(time.RFC3339))
  // 時(shí)間解析使用格式化相同的布局值
  t1,e:=time.Parse(
    time.RFC3339,
    "2012-11-01T22:08:41+00:00"
  )
  p(t1)
  // 格式化和解析使用基于示例的布局
  // 通常你使用常量在進(jìn)行布局。但你也可以提供自定義的格式。
  // 但你必須使用 Mon Jan 2 15:04:05 MST 2006 來(lái)作為示例
  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)
  // 純數(shù)字展示你可以使用標(biāo)準(zhǔn)的字符串格式化及響應(yīng)部分的時(shí)間值
  fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
    t.Year(),t.Month(),t.Day(),
    t.Hour(),t.Minute(),t.Second()
  )
  // 解析返回一個(gè)錯(cuò)誤來(lái)說(shuō)明是什么問(wèn)題
  ansic:="Mon Jan _2 15:04:05 2006"
  _,e=time.Parse(ansic,"8:41PM")
  p(e)
}

沉默是金 - MarkHoo

Random Numbers

Go 的 math/rand 包提供產(chǎn)生偽隨機(jī)數(shù)。

package main
import "time"
import "fmt"
import "math/rand"
func main(){
  // 例如,rand.Intn 產(chǎn)生一個(gè)隨機(jī)的整數(shù) n,0<=n<100
  fmt.Print(rand.Intn(100),",")
  fmt.Print(rand.Intn(100))
  fmt.Println()
  //rand.Float64 返回一個(gè)隨機(jī)的浮點(diǎn)數(shù),0.0<=f<1.0
  fmt.Println(rand.Float64())
  // 這個(gè)可以用于產(chǎn)生其他區(qū)間的浮點(diǎn)數(shù),如 5.0<=f<10.0
  fmt.Print((rand.Float64()*5)+5,",")
  fmt.Print((rand.Float64()*5)+5)
  fmt.Println()
  // 默認(rèn)的數(shù)字產(chǎn)生器是 deterministic,故它每次善生的數(shù)字序列是固定的
  // 為了產(chǎn)生不同的序列,賦予它一個(gè)變化的種子
  // 注意,隨即數(shù)字用來(lái)加密并不安全,用 crypto/rand 來(lái)做
  s1:=rand.NewSource(time.Now().UnixNano())
  r1:=rand.New(s1)
  // 調(diào)用產(chǎn)生的 rand.Rand
  fmt.Print(r1.Intn(100),",")
  fmt.Print(r1.Intn(100))
  fmt.Println()
  // 如果你給 Source 設(shè)置了相同的數(shù)字種子,他將產(chǎn)生相同的隨即數(shù)字序列。
  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

從字符串中解析數(shù)字是一個(gè)常見(jiàn)的任務(wù),這里是 Go 的做法。

package main
// 內(nèi)置的 strconv 包提供了數(shù)字解析
import "strconv"
import "fmt"
func main(){
  //64 代表要解析的浮點(diǎn)數(shù)精度
  f,_:=strconv.ParseFloat("1.234",64)
  fmt.Println(f)
  //0 代表根據(jù)字符串推斷基數(shù),64 要求結(jié)果要適應(yīng) 64 位
  i,_:=strconv.ParseInt("123",0,64)
  fmt.Println(i)
  //ParseInt 可以識(shí)別十六進(jìn)制
  d,_:=strconv,Parseint("0x1c8",0,64)
  fmt.Println(d)
  u,_:=strconv.ParseUint("789",0,64)
  fmt.Println(u)
  //atoi 是十進(jìn)制整數(shù)解析的簡(jiǎn)便函數(shù)
  k,_:=strconv.Atoi("135")
  fmt.Println(k)
  // 不合法的輸入將導(dǎo)致解析函數(shù)返回一個(gè)錯(cuò)誤
  _,e:=strconv.Atoi("wat")
  fmt.Println(e)
}

URL Parsing

package main
import "fmt"
import "net"
import "net/url"
func main(){
  // 我們解析這個(gè)示例 URL,包含一個(gè)協(xié)議,授權(quán)信息,地址,端口,路徑,查詢參數(shù)以及查詢拆分
  s:="postgres://user:pass@host.com:5432/path?k=v#f"
  // 解析這個(gè) URL 并且保證沒(méi)有錯(cuò)誤
  u,err:=url.Parse(s)
  if err!=nil{
    panic(err)
  }
  // 可以直接訪問(wèn)協(xié)議
  fmt.Println(u.Scheme)
  //User 包含所有授權(quán)信息,調(diào)用 Username 和 Password 可以得到單獨(dú)的值
  fmt.Println(u.User)
  fmt.Println(u.User.Username())
  p,_:=u.User.Password()
  fmt.Println(p)
  //Host 包含地址和端口。使用 SplitHostPort 來(lái)抽取他們
  fmt.Println(u.Host)
  host,port,_:=net.SplitHostPort(u.Host)
  fmt.Println(host)
  fmt.Println(port)
  fmt.Println(u.Path)
  fmt.Println(u.Fragment)
  // 為了以 k=v 的格式得到查詢參數(shù),使用 RawQuery
  // 你也可以將查詢參數(shù)解析到一個(gè) map 中
  // 解析的查詢參數(shù)是從字符串到字符串的片段,故索引 0 可以只得到第一個(gè)值
  fmt.Println(u.RawQuery)
  m,_:=url.ParseQuery(u.RawQuery)
  fmt.Println(m)
  fmt.Println(m["k"][0])
}

SHA1 Hashes

SHA1 哈希經(jīng)常用于計(jì)算二進(jìn)制或者文本塊的短標(biāo)識(shí)。例如,git 版本控制系統(tǒng)使用 SHA1 來(lái)標(biāo)示文本和目錄。這里是 Go 如何計(jì)算 SHA1 哈希值。

package main
//Go 實(shí)現(xiàn)了多種哈希函數(shù),在 crypto 包下
import "crypto/sha1"
import "fmt"
func main(){
  s:="sha1 this string"
  // 產(chǎn)生一個(gè)哈希的模式是 sha1.New(),sha1.Write(bytes) 然后 sha1.Sum([]bytes{})
  h:=sha1.New()
  h.Write([]byte(s))
  // 這里得到最終的哈希結(jié)果字節(jié)片段值。參數(shù)用于向已存在的字節(jié)片段追加,通常不需要
  bs:=h.Sum(nil)
  //SHA1 值經(jīng)常用于打印成十六進(jìn)制,如 git 提交時(shí)。使用 %x 格式參數(shù)來(lái)轉(zhuǎn)換為十六進(jìn)制
  fmt.Println(s)
  fmt.Printf("%x\n",bs)
}

Base64 Encoding

package main
// 這里為引入的包起了名稱,來(lái)節(jié)省下面代碼的空間
import 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 程序的基本任務(wù)需求。首先我們來(lái)看讀取文件。

package main
import(
  "bufio"
  "fmt"
  "io"
  "io/ioutil"
  "os"
)
// 讀取文件需要檢查大多數(shù)調(diào)用是否錯(cuò)誤,這個(gè) helper 可以流水化錯(cuò)誤檢查
func check(e error){
  if e!=nil{
    panic(e)
  }
}
func main(){
  // 最基本的一個(gè)文件讀取任務(wù)是將所有內(nèi)容放入內(nèi)存中
  dat,err:=ioutil.ReadFile("/tmp/dat")
  check(err)
  fmt.Print(string(dat))
  // 如果你想對(duì)文件的那部分如何進(jìn)行讀取有更多的控制
  // 首先你需要打開它
  f,err:=os.Open("/tmp/dat")
  check(err)
  // 從文件的開頭讀取一些字節(jié)。允許到 5,同時(shí)也是實(shí)際讀取了的字節(jié)。
  b1:=make([]byte,5)
  n1,err:=f.Read(b1)
  check(err)
  fmt.Printf("%d bytes: %s\n",n1,string(b1))
  // 你也可以找到一個(gè)已知的位置并從那里開始讀取
  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))
  //io 包提供了一些函數(shù),對(duì)于文件讀取可能很有幫助
  // 例如上面的繼續(xù)讀取的例子,可以使用 ReadAtLeast 更穩(wěn)定的實(shí)現(xiàn)
  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))
  // 沒(méi)有內(nèi)置的退回,但是 Seek(0,0) 完成了這個(gè)事情
  _,err=f.Seek(0,0)
  check(err)
  //bufio 包實(shí)現(xiàn)了一個(gè)帶緩沖區(qū)的讀取,它對(duì)于一些小的讀取以及由于它所提供的額外方法會(huì)很有幫助
  r4:=bufio.NewReader(f)
  b4,err:=r4.Peek(5)
  check(err)
  fmt.Printf("5 bytes: %s\n",string(b4))
  // 在完成時(shí)關(guān)閉文件 (通常會(huì)在打開時(shí)通過(guò) defer 計(jì)劃執(zhí)行)
  f.Close()
}

Writing Files

寫文件與讀取的模式類似。

package main
import(
  "bufio"
  "fmt"
  "io/ioutil"
  "os"
)
func check(e error){
  if e!=nil{
    panic(e)
  }
}
func main(){
  // 這里將一個(gè)字符串 (或者僅僅是字節(jié)) 寫入到一個(gè)文件。
  d1:=[]byte("hello\ngo\n")
  err:=ioutil.WriteFile("/tmp/dat1",d1,0644)
  check(err)
  // 打開一個(gè)文件以供寫入
  f,err:=os.Create("/tmp/dat2")
  check(err)
  // 這是一個(gè)慣用法,在打開時(shí)立刻通過(guò) defer 關(guān)閉
  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

一個(gè)行過(guò)濾器經(jīng)常見(jiàn)于讀取標(biāo)準(zhǔn)輸入流的輸入,處理然后輸出到標(biāo)準(zhǔn)輸出的程序中。grep 和 sed 是常見(jiàn)的行過(guò)濾器。

// 下面這個(gè)行過(guò)濾器示例將所有輸入的文字轉(zhuǎn)換為大寫的版本
package main
import (
  "bufio"
  "fmt"
  "os"
  "strings"
)
func main(){
  // 使用一個(gè)帶緩沖的 scanner 可以方便的使用 Scan 方法來(lái)直接讀取一行
  // 每次調(diào)用該方法可以讓 scanner 讀取下一行
  scanner:=bufio.NewScanner(os.Stdin)
  //Text 方法返回當(dāng)前的 token,現(xiàn)在是輸入的下一行
  for scanner.Scan(){
    ucl:=strings.ToUpper(scanner.Text())
    // 輸出大寫的行
    fmt.Println(ucl)
  }
  // 檢查 scanner 的錯(cuò)誤,文件結(jié)束符不會(huì)被當(dāng)作是一個(gè)錯(cuò)誤
  if err:=scanner.Err();err!=nil{
    fmt.Fprintln(os.Stderr,"error:",err)
    os.Exit(1)
  }
}

可以使用如下命令來(lái)試驗(yàn)這個(gè)行過(guò)濾器:

echo 'hello' > /tmp/lines echo 'filter' >> /tmp/lines
$ cat /tmp/lines | go run line-filters.go

Command-Line Arguments

package main
import "os"
import "fmt"
func main(){
  //os.Args 提供原始命令行參數(shù)訪問(wèn)功能
  // 切片的第一個(gè)值是程序的路徑
  argsWithProg:=os.Args
  //os.Args[1:] 保存程序的所有參數(shù)
  argsWithoutProg:=os.Args[1:]
  // 你可以通過(guò)自然索引獲取到每個(gè)單獨(dú)的參數(shù)
  arg:=os.Args[3]
  fmt.Println(argsWithProg)
  fmt.Println(argsWithoutProg)
  fmt.Println(arg)
}

本例應(yīng)當(dāng)先 go build,然后再運(yùn)行并指定參數(shù)

Command-Line Flags

命令行標(biāo)志是一個(gè)指定特殊選項(xiàng)的常用方法。例如,在 wc -l 的 -l 就是一個(gè)命令行標(biāo)志。

package main
//flag 包支持基本的命令行標(biāo)志解析
import "flag"
import "fmt"
func main(){
  // 基本的標(biāo)志聲明僅支持字符串、整數(shù)和布爾值選項(xiàng)
  // 這里聲明了一個(gè)默認(rèn)值為 foo 的字符串標(biāo)志 word,并帶有一個(gè)簡(jiǎn)短的描述。flag.String 返回一個(gè)字符串指針。
  wordPtr:=flag.String("word","foo","a string")
  // 類似聲明一個(gè)整數(shù)和布爾值標(biāo)志。
  numbPtr:=flag.Int("numb",42,"an int")
  boolPtr:=flag.Bool("fork",false,"a bool")
  // 可以使用程序中已有的參數(shù)聲明一個(gè)標(biāo)志,聲明時(shí)需要指定該參數(shù)的指針
  var svar string
  flag.StringVar(&svar,"svar","bar","a string var")
  // 所有標(biāo)志聲明完成后,調(diào)用 flag.Parse() 來(lái)執(zhí)行命令行解析
  flag.Parse()
  // 通過(guò)對(duì)指針解引用來(lái)獲取選項(xiàng)的實(shí)際值
  fmt.Println("word:",*wordPtr)
  fmt.Println("numb:",*numbPtr)
  fmt.Println("fork:",*boolPtr)
  fmt.Println("svar:",svar)
  fmt.Println("tail:",flag.Args())
}

測(cè)試用例:

go build command-line-flags.go \# 省略的標(biāo)志將自動(dòng)設(shè)定為默認(rèn)值 ./command-line-flags -word=opt
# 位置參數(shù)可以出現(xiàn)在任何標(biāo)志后面
./command-line-flags -word=opt a1 a2 a3 \# flag 包需要的所有標(biāo)志出現(xiàn)在位置參數(shù)之前,否則標(biāo)志將會(huì)被解析為位置參數(shù) ./command-line-flags -word=opt a1 a2 a3 -numb=7
# 使用 -h 或者 --help標(biāo)志來(lái)得到自動(dòng)生成的命令行幫助文本
./command-line-flags -h \# 如果提供了一個(gè)沒(méi)有用 flag 包指定的標(biāo)志,將會(huì)得到錯(cuò)誤信息和幫助文檔 ./command-line-flags -wat

Environment Variables

package main
import "os"
import "strings"
import "fmt"
func main(){
  // 使用 os.Setenv 來(lái)設(shè)置一個(gè)鍵值對(duì)
  // 使用 os.Getenv 來(lái)獲取一個(gè)環(huán)境變量,如果不存在,返回空字符串
  os.Setenv("FOO","1")
  fmt.Println("FOO:",os.Getenv("FOO"))
  fmt.Println("BAR:",os.Getenv("BAR"))
  fmt.Println()
  // 使用 os.Environ 來(lái)列出所有環(huán)境變量鍵值對(duì)
  for _,e:=range os.Environ(){
    pair:=strings.Split(e,"=")
    fmt.Prinln(pair[0])
  }
}

Spawning Processes

package main
import "fmt"
import "io/iouitl"
import "os/exec"
func main(){
  //exec.Command 函數(shù)幫助我們創(chuàng)建一個(gè)表示這個(gè)外部進(jìn)程的對(duì)象
  dateCmd:=exec.Command("date")
  //Output 等待命令運(yùn)行完成,并收集命令的輸出
  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()
  // 運(yùn)行進(jìn)程,寫入輸入信息,讀取輸出結(jié)果,等待程序運(yùn)行結(jié)束
  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))
  // 通過(guò) bash 命令的 -c 選項(xiàng)來(lái)執(zhí)行一個(gè)字符串包含的完整命令
  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

package main
import "syscall"
import "os"
import "os/exec"
func main(){
  // 通過(guò) LookPath 得到需要執(zhí)行的可執(zhí)行文件的絕對(duì)路徑
  binary,lookErr:=exec.LookPath("ls")
  if lookErr!=nil{
    panic(lookErr)
  }
  //Exec 需要的參數(shù)是切片形式的,第一個(gè)參數(shù)為執(zhí)行程序名
  args:=[]string{"ls","-a","-l","-h"}
  env:=os.Environ()
  execErr:=syscall.Exec(binary,args,env)
  if execErr!=nil{
    panic(execErr)
  }
}

Signals

package main
import "fmt"
import "os"
import "os/signal"
import "syscall"
func main(){
  //Go 通過(guò)向一個(gè)通道發(fā)送 os.Signal 值來(lái)進(jìn)行信號(hào)通知
  sigs:=make(chan os.Signal,1)
  // 同時(shí)創(chuàng)建一個(gè)用于在程序可以結(jié)束時(shí)進(jìn)行通知的通道
  done:=make(chan bool,1)
  // 注冊(cè)給定通道用于接收特定信號(hào)
  signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM)
  //Go 協(xié)程執(zhí)行一個(gè)阻塞的信號(hào)接收操作,當(dāng)它得到一個(gè)值時(shí),打印并通知程序可以退出
  go func(){
    sig:=<-sigs
    fmt.Println()
    fmt.Println(sig)
    done<-true
  }()
  fmt.Println("awaiting signal")
  <-done
  fmt.Println("exiting")
}

運(yùn)行,使用 ctrl-c 發(fā)送信號(hào)

Exit

package main
import "fmt"
import "os"
func main(){
  // 當(dāng)使用 os.Exit 時(shí),defer 將不會(huì)執(zhí)行
  defer fmt.Println("!")
  // 退出,并且狀態(tài)為 3
  os.Exit(3)
}

官網(wǎng):https://gobyexample.com
沉默是金 - MarkHoo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容