Go语言实战读书笔记
第二章
通道(channel)、映射(map)和切片(slice)是引用类型。引用类型的对象需要使用make函数进行构造。
在Go程序中,如果函数main()返回,整个程序就终止了。这时,Go会关闭全部goroutine。
使用for range迭代切片时,每次迭代前会返回两个值:元素在切片中的索引和元素副本。
Go支持闭包。
解析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包应当与其所在目录同名。
第三章
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,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,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.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) } }