Go語言實戰讀書筆記

Go語言實戰讀書筆記

第二章

通道(channel)、映射(map)和切片(slice)是引用類型。引用類型的對象須要使用make函數進行構造。git

在Go程序中,若是函數main()返回,整個程序就終止了。這時,Go會關閉所有goroutine。github

使用for range迭代切片時,每次迭代前會返回兩個值:元素在切片中的索引和元素副本。web

Go支持閉包。json

解析JSON示例:數組

type Feed struct {
    Name string `json:"site"`
    URI string `json:"link"`
    Type string `json:"type"`
}

file, _ := os.Open(filename)
var feeds []*Feed
json.NewDecoder(file).Decode(&feeds)

聲明接口示例:網絡

type Matcher interface {
  Search(feed *Feed, searchTerm string) ([]*Result, error)
}

使用指針做爲接受者聲明的方法,只能由指針調用。使用值做爲接受者聲明的方法,值和指針均可以調用。當使用值調用時,傳入方法的是值的副本。閉包

若是使用for range對通道進行迭代時,當通道關閉後,迭代會終止。併發

除了main包外,Go包應當與其所在目錄同名。app

第三章

Go在尋找包時,先從$GOROOT目錄下尋找,接着在$GOPATH目錄下尋找。函數

命名導入:

import myfmt "mylib/fmt"
import _ "mylib/init"

包的初始化。每一個包能夠包含多個init函數,這些函數將在main.main()以前執行。

Go工具

構建:

go build hello.go  # 構建指定文件。
go build # 構建當前目錄。
go build github.com/goinaction/code/chapter3/wordcount # 構建指定包。
go build github.com/goinaction/code/chapter3/... # 構建指定目錄下的所有包。

清理構建文件:

go clean hello.go

構建並執行:

go run hello.go

檢查代碼中的常見錯誤:

go vet
go vet main.go
go vet .

格式化代碼:

go fmt
gofmt -l -w -s .

查看文檔:

go doc tar
godoc -http=:6060

Go源代碼文檔

函數文檔示例:

// Retrieve 鏈接到配置庫,收集各類連接設置、用戶名和密碼。這個函數成功時
// 返回 config 結構,不然返回一個錯誤。
func Retrieve() (config, error) {
    // ...
}

包文檔示例:

// 包 usb 提供了用於調用 USB 設備的類型和函數。
package usb
// ...

第四章

聲明數組:

var a1 [5]int
var a2 = [3]int{1, 2, 3}
var a3 = [...]int{1, 2, 3}
var a4 = [3]*int{0: new(int), 1: new(int)}

數組賦值會複製元素:

a1 := [3]string{"a", "b", "c"}
var a2 [3]string
a2 = a1

多維數組:

var a1 [4][2]int
a2 := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
a3 := [4][2]int{1: {0: 20}, 3: {1: 41}}
var a4 [2]int = a3[1]

不要用數組做爲函數參數。這麼作會複製大量對象。要使用切片。

創建切片:

s1 := make([]int, 5)
s2 := make([]int, 3, 5)
s3 := []{1, 2, 3}
s4 := []string{99: ""}
s5 := s1[1:3] # s5和s1共享同一個底層數組
s6 := s1[2:3:4] # s6是長度爲1,容量爲2的切片

切片會包含一個底層數組。

切片和數組的區別在於,[]中沒有數字。

對於底層數組容量是k的切片s[i:j],其長度是j-i,容量是k-i。

在對切片進行迭代時,返回的是切片元素的副本。每次迭代時,值副本的地址是相同的。

多維切片:

s := [][]int{{10}, {100, 200}}

切片包含地址指針、長度和容量。在64位計算機上,一個切片佔用24字節。複製切片時不會複製底層數組,所以將切片做爲參數傳遞給函數,開銷很小。

切片函數:

cap(s) # 容量
len(s) # 長度
append(s, element)

映射使用了散列表。在每次迭代中,各元素返回的次序可能不一樣。

創建映射:

m1 := make(map[int]int)
m2 := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}

映射的鍵必須是可使用==比較的對象。函數、切片、以及包含切片的對象,因爲具備引用語義,不能做爲映射的鍵。

從映射中獲取鍵對應的值時,若是鍵不存在,會返回零值。

映射函數:

delete(m, "key")

第五章

Go是靜態類型語言。

自定義類型字面值:

type user struct {
  name string
  email string
}

type admin struct {
  person user
  level string
}

u1 := user{"Lisa", "lisa@abc.com"}
u2 := user{name: "Lisa", email: "lisa@abc.com"}
a1 := admin{
  person: user{"Lisa", "lisa@abc.com"}
  level: "super"
}

以指針爲接收者的函數只能經過指針調用。以值爲接收者的函數能夠經過值或指針調用。對於以值爲接收者的函數,函數域中的接收者是值的副本,即便經過指針調用時也是如此。

package main

import (
     "log"
)

func main() {
     u1 := user{"Tom"}
     u2 := &user{"Jerry"}

     u1.Name()
     u2.Name()

     log.Printf("%p %p", &u1, u2)
}

type user struct {
     name string
}

func (r user) Name() {
     log.Printf("%s %p %p", r.name, &r, &r.name)
}

若是函數須要修改接收者的狀態,要以指針做爲接收者。不然使用值做爲接收者。

Go中的引用類型有:切片、映射、通道、接口和函數。

接口是用於定義行爲的類型。若是一個類型實現了某個接口所聲明的所有方法,這個類型的對象就能夠賦值給作對應接口類型的變量。在賦值完成後, 會創建一個接口對象。接口對象包含兩個指針:一個指向iTable,一個指向存儲的值。iTable包含了存儲值的類型信息,以及與這個值相關聯的一組方法,稱爲方法集。方法集定義了接口的接收規則。

方法接收者
T (t T)
*T (t T) 和 (t *T)

嵌入類型:

type user struct {
  name string
  email string
}

func (r user) hello() string {
  return "hello " + r.name
}

type admin struct {
  user
  level string
}

a := admin{}
a.user.name
a.name
a.user.hello()
a.hello()

被嵌入的類型也叫內部類型。內部類型中的標誌符(成員和函數)會被提高到外部類型中。

以小寫字母開頭的標識符是包私有標識符,在包外不可見。對於未公開的內部類型,其公開成員能夠經過標識符提高,之外部類型成員的方式訪問。

第六章

Go使用的同步模型是通訊順序模型(Communicating Sequential Processes,CSP),各個goroutine經過傳遞消息通訊,而非經過鎖和共享內存來共享狀態。

Go運行時會在邏輯處理器上調度goroutine。從1.5版本起,Go運行時會爲每一個物理處理器分配一個邏輯處理器(每一個CPU一個仍是每一個核一個?)。當goroutine指定到一個阻塞的系統調用時,運行時將線程和goroutine從邏輯處理器上分離。被分離的線程會繼續阻塞,等待系統調用返回。而邏輯處理器會創建一個新線程,從隊列中選取一個goroutine,將新線程和goroutine綁定到邏輯處理器上。

在處理網絡I/O時,goroutine會集成到網絡輪詢器的運行時。

Go運行時的線程數量默認爲10000。超過這個數量時,運行時會崩潰。使用runtime/debug包中的方法SetMaxThreads()能夠提升線程數量。

併發concurrency不是並行parallelism。並行是讓不一樣的代碼片斷同時在不一樣的物理處理器上執行。並行指同時處理不少事情。併發指同時管理不少事情。

調用runtime包的方法GOMAXPROCS()能夠設置Go運行時邏輯處理器數量。

next:
  for i := 0; i < 10; i++ {
    for j := 0; j < 10; j++ {
      if cond {
        continue next
      }
    }
  }

runtime.NumCPU()返回物理處理器數量。

runtime.Gosched()從線程退出,並放回隊列。

unbuffered := make(chan int) // 無緩衝區的通道
buffered := make(chan int, 10) // 有緩衝區的通道

無緩衝區通道要求發送方和接收方同時準備好。若是一方沒有準備好,另外一方會阻塞。

package main

import (
     "fmt"
)

func main() {
     input := make(chan int)
     go func() {
             input <- 1
     }()
     foo(input, 10)
}

func foo(input chan int, end int) {
     x := <-input
     fmt.Println(x)
     if x >= end {
             return
     }
     go foo(input, end)
     input <- x + 1
}

第七章

import (
  "os"
  "os/signal"
)

signalQueue := make(chan os.Signal)
signal.Notify(signalQueue, os.Interrupt) # 接收信號
for {
  if interrupted() {
    break
  }
  // ...
}

func interrupted() bool {
   select {
   case <-signalQueue:
     signal.Stop(signalQueue) # 中止接收信號
     return true
   default:
     return false
   }
}
a := []int{1, 2}
func add(arr ...int) {
  b := append(a, arr...)
}

判斷超時和終端的示例:

interrupt := make(chan os.Signal, 1)
complete := make(chan error)
timeout := time.After(3 * time.Second)

signal.Notify(r.interrupt, os.Interrupt)
go func() {
  complete <- someFunc()
}()

select {
case err := <-complete:
  return err
case <-r.timeout:
  return "timeout"
}

每一個調用signal.Notify(signalChan, signum)的隊列,都會收到信號。

第八章

import "log"

log.SetPrefix("TRACE: ")
log.SetFlags(log.Ldata | log.Llongfile)
// Ldate Ltime Llongfile Lmicroseconds Lshortfile
// LstdFlags = Ldate | Ltime
log.Println("abc")
log.Fatalln("abc")
log.Panicln("abc")

log.New(ioutil.Discard, "TRACE: ", log.LstdFlags|log.Lshortfile)
log.New(io.MultiWriter(file, os.Stderr), "ERROR: ", log.LstdFlags|log.Lshortfile)

iota關鍵字爲每一個常量複製相同的表達式,而且每次自增1:

const (
  a = 1 << iota // 1
  b             // 2
  c             // 4
)

const (
  x = iota // 0
  y        // 1
  z        // 2
)

第九章

單元測試示例:

import "testing"

func TestFoo(t *testing.T) {
  t.Log("abc")
  t.Logf("a = %v", 2)
  t.Errorf("%v", 123)
  t.Fatal("abc")
}

測試web服務示例:

import (
  "testing"
  "net/http"
  "net/http/httptest"
)

feed := `<xml ...>`

func MockServer() *httptest.Server {
  return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Header().Set("Content-Type", "application/xml")
    fmt.Fprintln(w, feed)
  }))
}

func TestFoo(t *testing.T) {
  server := mockServer()
  defer server.Close()

  resp, err := http.Get(server.URL)
  if err != nil {
    t.Fatal(err)
  }
  defer resp.Body.Close()
  // ...
}

測試web服務示例:

http.HandleFunc("/some", func(rw http.ResponseWriter, r *http.Request) {
  // ...
})

func TestSome(t *testing.T) {
  req, _ := http.NewRequest("GET", "/some", nil)
  rw := httptest.NewRecorder()
  http.DefaultServeMux.ServeHTTP(rw, req)
}

基準測試:

func BenchmarkSprintf(b *testing.B) {
  number := 10
  b.ResetTimer()
  for i := 0; i < b.N; i++ {
    fmt.Sprintf("%d", number)
  }
}

修訂記錄

  1. 2017年08月02日 創建文檔。
  2. 2017年08月05日 增長筆記。
  3. 2017年08月07日 修正拼寫錯誤。
相關文章
相關標籤/搜索