├── README.md ├── golang101_02_map.md ├── golang101_Web02_docker.md ├── golang101_Web01_TCP_Server.md ├── golang101_05_routine.md ├── golang101_03_struct_interface.md ├── golang101_06_channel.md ├── golang101_DB01_RabbitMQ.md ├── golang101_01_array_slice.md └── golang101_04_functions.md /README.md: -------------------------------------------------------------------------------- 1 | # Golang Notes 2 | 3 | ## :books: Golang学习笔记。 4 | 5 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_500/v1521526971/gopher1_dtlosg.jpg) 6 | 7 | 笔者使用Go主要目的是方便研究分布式和存储,为以后的职场挑战做准备。因此我觉得有必要开一个系列慢慢记录我的学习过程,既然是笔记性质的,所以可能有些地方不详细,欢迎fork和补充。 8 | 9 | 本Golang系列的笔记,范围从Golang基础,Go web和Network的应用,相关Tech Talk点评总结,著名项目源码和架构解读再到Toy Project的开发设计。 10 | 11 | 积跬步,至千里。一起努力~ :rocket: 12 | 13 | 更多信息, 欢迎阅读我的blog: [Happy Isotope](http://www.happyisotope.com) :yum: 14 | 15 | ## :memo: 目录导航 16 | ### Basics: 17 | 18 | [Golang 101系列基础篇 String, Array, Slice](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_01_array_slice.md) 19 | 20 | [Golang 101系列基础篇-Map](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_02_map.md) 21 | 22 | [Golang 101系列基础篇——Struct和Interface](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_03_struct_interface.md) 23 | 24 | [Golang 101系列基础篇——Useful functions](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_04_functions.md) 25 | 26 | [Golang 101系列基础篇——初识Goroutine](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_05_routine.md) 27 | 28 | [Golang 101系列网络篇——初识Channel](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_06_channel.md) 29 | 30 | 31 | ### Database Topics: 32 | [Golang和RabbitMQ](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_01_array_slice.md) 33 | 34 | ### Web Topics: 35 | [Golang 101系列网络篇——TCP Server](https://github.com/gongbaochicken/Golang-Notes/blob/master/golang101_Web01_TCP_Server.md) 36 | 37 | (Keep updating...) 38 | -------------------------------------------------------------------------------- /golang101_02_map.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列基础篇-Map 2 | 3 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_500/v1521526971/gopher1_dtlosg.jpg) 4 | 5 | ## Map 6 | Golang中的map是用hashmap来实现的,源码在runtime/hashmap.go。 7 | 8 | map声明的方法主要有两种,一种是直接赋值,预先塞进一些值在Map中,另一种方法是使用make先预留出memory,再一个个的key-value插入map。 9 | 10 | 11 | ``` go 12 | // This is wrong way to assign after 'declare' like this, which cause panic: assignment to entry in nil map 13 | // var m1 map[int]string 14 | // m1[2] = "Jeff" 15 | 16 | //Method 1 17 | m1 := map[int]string{2: "Jeff", 4: "Gao"} 18 | 19 | //Method 2, equivalent to m := map[int]string{} 20 | m2 := make(map[int]string) 21 | m2[2] = "Jeff" 22 | m2[4] = "Gao" 23 | 24 | ``` 25 | 26 | map的用法也相对比较简单,主要就是查询,插入和删除K-V值,具体如下: 27 | 28 | ``` go 29 | m1 := map[int]string{2: "Jeff", 4: "Gao"} 30 | 31 | //Retrive 32 | employee1 := m1[2] 33 | fmt.Println(employee1) //"Jeff" 34 | employee2 := m1[3] 35 | fmt.Println(employee2) //nil, because its k-v pair doesn't exist 36 | 37 | //Iterate 38 | for k, v := range m1 { 39 | fmt.Println("key = ", k, "value = ", v) 40 | } 41 | 42 | //Delete 43 | delete(m1, 2) 44 | fmt.Println(m1[2]) //nil 45 | fmt.Println(len(m1)) //1 46 | 47 | ``` 48 | 49 | 其中delete是内置的函数,用来从map中删除key。如果map本身是空的或者无需要删除的key,那么就直接退出什么都不做。 50 | 51 | ## make vs new 52 | 这里想比较一下make和new的用法。 53 | 54 | 当我们声明一个变量但是没有赋值时,变量会保留默认的零值。对于引用类型,他们零值是nil。所以对于一个引用类型的变量,我们直接赋值就会出现runtime panic(如果你尝试过官网的map里面的那个例子,你就会秒懂)。 55 | 56 | 那么对于引用类型(map, slice, channel)的初始化,除了声明,我们还需要在Heap堆上面给他们分配内存空间,在Golang里面会用到new或者make。 57 | 58 | 那么他们区别是什么呢?主要是区别是使用的对象不同,make只用于slice, map, channel,返回的是引用类型的本身;而new主要是用于值类型的变量或者struct需要引用时分配内存,返回指向类型的指针。 59 | 60 | ``` go 61 | //make 62 | //allocates and init a hash map data structure and returns a map value that points to it. 63 | m := make(map[string]int) 64 | 65 | //new 66 | var i *int 67 | i = new(int) 68 | *i = 6 69 | 70 | //or var i = new(int), omit *int because it can be referred from right side 71 | ``` 72 | 73 | ## Set? 74 | 细心的你,一定很快会发现,在Golang的标准库里面没有set这种数据结构的,这让很多人感觉不太适应。Golang不支持set, 主要理由是Golang没有泛型。 75 | 76 | 好吧,那么怎么表示set这种数据结构的呢?答案是用map来代替, 要点是key-value中的value使用bool值代表key是否存在: 77 | 78 | ``` go 79 | s := make(map[string]bool) 80 | s["John"] = true 81 | s["Sam"] = true 82 | fmt.Println(s["John"]) //true 83 | fmt.Println(s["Sam"]) //true 84 | fmt.Println(s["Kim"]) //false 85 | 86 | delete(s, "Sam") 87 | fmt.Println(s["Sam"]) //false 88 | ``` 89 | -------------------------------------------------------------------------------- /golang101_Web02_docker.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列网络篇——Docker化一个go web app 2 | 3 | 本文尝试使用两种Dockerfile讲一个简单的Golang Web app打包成Container运行。 4 | 5 | ## A basic go web app 6 | 7 | 首先先写一个最简单的HTTP go app,通过HTTP监听,让http response writer写入一个hardcode信息。通过登录http://localhost:8080可以看到该信息。该文件命名为main.go. 8 | 9 | ``` 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "net/http" 15 | ) 16 | 17 | //Anyting has ServeHTTP method is a Handler interface responds to an HTTP request. 18 | type anyInterface int 19 | 20 | //ServeHTTP should write reply headers and data to ResponseWriter and then return 21 | func (a anyInterface) ServeHTTP(w http.ResponseWriter, r *http.Request) { 22 | fmt.Fprintf(w, "Any thing server writes back to response writer") 23 | } 24 | func main() { 25 | var notSure anyInterface 26 | http.ListenAndServe(":8080", notSure) 27 | } 28 | 29 | ``` 30 | 再确认无误之后,我们可以开始Docker化该app. 31 | 32 | ## Use Onbuild Dockerfile 33 | 主要步骤就是先写Dockerfile, 然后Docker build, 最后在Docker run。 34 | 35 | Dockerfile一定使用的是FROM开头,意味着Docker必须构建于已经存在的Docker架构之上。对于这种简单的应用和我们这里的情况,我们直接可以使用一个单纯的Golang的Dockerfile来build。这里Dockerfile有各种不同版本的选择。 36 | 37 | 我们可以先使用一种最简单的Dockerfile来玩玩。 38 | 39 | ``` 40 | # docker file test 41 | From golang:1.8-onbuild 42 | ``` 43 | 44 | Onbuild这种版本假设我们的Go App是按照最标准的结构写的,因此wrap了一些必要的额操作,当我们build之后,可以直接使用。 45 | 46 | Docker build: 47 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1528180519/docker1_tqpcvr.jpg) 48 | 49 | Docker run: 50 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1528180519/docker2_clsrmi.jpg) 51 | 52 | 通过浏览器登陆本地8080端口,可以看到如下信息,证明我们的Docker化已经成功。 53 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1528180519/docker3_r2c9kt.jpg) 54 | 55 | 这里使用了 -p A:B 指定了端口A是机器和浏览器中的等待的端口,而B是程序APP服务的端口。比如,如果使用了 -p 3000:8080, 因为main.go程序里面指明了8080,而这时打开浏览器,可以在localhost:3000找到我们期待的页面。 56 | 57 | 58 | ## Use Standard Dockerfile 59 | 对于标准的Dockerfile,可以从官网上找到对应所需的版本号,在第一行进行FROM引用。同时需要创建一个新的workdir, 并需要在Dockerfile中指明编译(当然也可以在执行时,而不是在Docker build时)。最后需要指明Docker run时,使用的运行命令。 60 | 61 | ``` 62 | From golang:1.9-stretch 63 | MAINTAINER jiazhuo0528@gmail.com 64 | 65 | RUN mkdir /app 66 | ADD . /app/ 67 | WORKDIR /app 68 | RUN go build -o main . 69 | CMD ["/app/main"] 70 | ``` 71 | 其他步骤跟之前一样。 72 | 73 | ## alpine vs stretch 74 | 在众多Dockerfile版本中,你会发现有一个系列的标记为alpine。Alpine是一个轻量级的Linux发行版,用来减小系统的体积和运行时资源消耗,因此得到开源社区越来越多的青睐。 75 | 76 | 我们可以直接采用golang:1.9-alpine3.7和我们之前使用的golang:1.9-stretch来编译Dockerfile来进行比较,可发现alpine真的减少了很多! 77 | 78 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1528180519/docker4_zvwpny.jpg) 79 | 80 | 这种体积的精简,意味着可以在更精简的Linux server(比如CoreOS)上面运行,对于实际生产中的作用不言而喻。 81 | 82 | ## 小结 83 | 通过Dockerfile,我们可以将应用打包成一个个Docker image,每个image可以通过指定使Container运行在某一个端口。在实际生产中,部分的Container可以运行在不同的server,实现了Microservices的基础结构。 -------------------------------------------------------------------------------- /golang101_Web01_TCP_Server.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列网络篇——TCP Server 2 | 3 | 本文将从回顾TCP的基本概念出发,到了解Golang下网络Socket编程的理念,再到使用Golang搭建几个简单的TCP Server Demo。 4 | 5 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522303664/index_ifnqdy.jpg) 6 | 7 | ## TCP简述 8 | TCP位于传输层,在TCP/IP五层协议中,处于网络层之上,应用层之下。具体来看,TCP链接是一个典型的C-S模型,也就是客户端(Client)-服务端(Server)模型。客户端发出连接请求,服务端接受请求,并返回一个ACK,客服端收到ACK之后,明白了Server已经接受了自己的请求,然后发送一个ACK回去,告知Server,“我知道你接受我了,我们可以开始传输数据了。”至此,TCP三次握手完成。 9 | 10 | 11 | ![](http://7.blog.xuite.net/7/e/c/d/12333836/blog_143465/txt/16475480/0.jpg) 12 | 13 | TCP通过传输序列有序的数据并附加ACK的方式,来保证其稳定性,来防止连接过程中发生丢包。除此之外,有一些有名的TCP改进机制,比如TCP-Reno,这个以后有机会再重新开一篇文章详细聊。 14 | 15 | #### 题外话:TCP vs UDP 16 | 直接搬出学校的笔记 2333: 17 | 18 | * TCP is connection-oriented protocol(3-Way handshake), UDP is connection less (multicast). 19 | * TCP is reliable, delivery guaranteed(resend), UDP is unreliable. 20 | * TCP is ordered (has sequence), UDP may not in the order. 21 | * TCP is slow and heavy weight(20 bytes header), UDP is fast and light-weight (8 b header). 22 | * TCP has flow control and congestion control. 23 | * TCP is used by HTTP, HTTPS, FTP, SMP; UDP is used by DHCP, DNS, NFS... 24 | 25 | ## Golang的网络编程理念 26 | 以前在学校做Project的时候,遇到用C++写TCP的时候有时还挺头痛的,体验不好。使用Golang写TCP/IP层,相比C++,简直不要简单太多。难能可贵的是在简单的同时,Golang还保持着不俗的性能。 27 | 28 | 29 | ## Golang下写TCP Server 30 | 基于Golang的I/O机制和并发原语的原生支持,再加上对网络API的封装,我们可以比较轻松地实现一个高效的服务端或者客户端程序。一般的实现就是调用net.Listen(“tcp4”, address)得到一个net.Listener,然后无限循环调用net.Listener.Accept,之后就可以得到一个net.Conn,可以调用net.Conn的接口设置发送和接收缓冲区大小,可以设置KEEPALIVE等。因为TCP的双工特性,所以可以针对一个net.Conn可以专门启动一个goroutine去无限循环接收对端发来的数据,然后解包等。 31 | 32 | ### Echo Server 33 | 我们下面来写一个简单的回音服务器(Echo Server),就是client发送一个信息,server稍微处理一下,就把原信息返回来给client。整个过程是,Server在监听,拿到监听到的信息后,通过写网络文件传回给Client,然后继续监听接下来的信息。 34 | 35 | ``` go 36 | package main 37 | 38 | import ( 39 | "bufio" 40 | "fmt" 41 | "log" 42 | "net" 43 | ) 44 | 45 | func main() { 46 | //Server gets a listener, listening to localhost:8080 47 | li, err := net.Listen("tcp", ":8080") 48 | if err != nil { 49 | log.Panic(err) 50 | } 51 | //Don't forget to close later, otherwise resourse will leak. 52 | defer li.Close() 53 | 54 | //Keep listening 55 | for { 56 | //Listener accepts the request, and build the connection 57 | conn, err := li.Accept() 58 | if err != nil { 59 | log.Panic(err) 60 | } 61 | //Do the job. 62 | go handle(conn) 63 | } 64 | } 65 | 66 | func handle(conn net.Conn) { 67 | defer conn.Close() 68 | 69 | //returns a new Scanner to read from connection io.Reader. 70 | scanner := bufio.NewScanner(conn) 71 | //Scan will get next token, which will then be available through the Bytes or Text method. 72 | for scanner.Scan() { 73 | //Get most recent token generated by a call to Scan 74 | ln := scanner.Text() 75 | //Server prints the info from the connection 76 | fmt.Println(ln) 77 | //Write back to connection: 78 | fmt.Fprintf(conn, "I heard you said: %s\n", ln) 79 | } 80 | } 81 | ``` -------------------------------------------------------------------------------- /golang101_05_routine.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列基础篇——初识Goroutine 2 | 3 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522303339/gophercomplex1_duq7fh.jpg) 4 | 5 | 本文终于来到了多线程的一个主题:Goroutine。在Golang中,Goroutine相当于是一个轻量级的线程,context switch的代价小。我们来看看Goroutine的特点和基本使用,在后期的文章和学习中,我再继续探讨更深入的内容。 6 | 7 | 8 | ## 特点 9 | 在Golang中,Goroutine相当于是一个轻量级的线程(协程),就是一些函数,可以同时运行(Concurrency)。当我们使用go这个关键词来运行一个Goroutine的时候,程序为他专门在Heap专门开辟一片2Kb空间(一般线程大概1Mb),因此在程序中可以使用大量goroutine(前提是你知道你在做什么),来提高运行效率,充分利用资源。 10 | 11 | 同时,这也体现其最大的优势:在并发开发中实现对线程池的动态扩展,不会由于某个任务的阻塞而导致死锁。因为当某个goroutine被阻塞,runtime会重新开始一个线程来处理其他goroutine,直到阻塞消失。 12 | 13 | ## Example 14 | 下面给一个很常见的例子: 15 | 16 | ``` go 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | ) 23 | 24 | func say(s string) { 25 | for i := 0; i < 4; i++ { 26 | fmt.Println(s) 27 | } 28 | } 29 | 30 | func main() { 31 | go say("Hi") 32 | go say("Hola") 33 | fmt.Println("End") 34 | } 35 | 36 | ``` 37 | 我的目的是在主程序里面启动两个goroutine,同时分别执行打印对应的字符串,每个goroutine打印四次。 38 | 39 | 但是结果只出现了“End”,这是为什么呢?因为在两个goroutine在运行的时候,main已经运行结束了,整个程序也就终止了,所以goroutine的内容什么也没有打印出来。 40 | 41 | 这个很好办,我们让主程序先休息一下,等等goroutine再结束。同时为了看到goroutine的交叉结果(Interleaving), 我们让goroutine在每次打印前也稍微缓一缓。 42 | 43 | ``` go 44 | package main 45 | 46 | import ( 47 | "fmt" 48 | "time" 49 | ) 50 | 51 | func say(s string) { 52 | for i := 0; i < 4; i++ { 53 | time.Sleep(10 * time.Millisecond) 54 | fmt.Println(s) 55 | } 56 | } 57 | 58 | func main() { 59 | go say("Hi") 60 | go say("Hola") 61 | time.Sleep(100 * time.Millisecond) 62 | fmt.Println("End") 63 | } 64 | 65 | ``` 66 | 在某次运行后,我得到了: 67 | 68 | ``` 69 | Hi 70 | Hola 71 | Hola 72 | Hi 73 | Hi 74 | Hola 75 | Hola 76 | Hi 77 | End 78 | ``` 79 | 很显然,goroutine相互之间出现了interleaving.符合我们对多线程的一般认知。 80 | 81 | ## WaitGroup 82 | 上面这个例子,通过主程序sleep的方法等待两个goroutine的执行结果,未免显得太Naive。这里我们可以引入一个更优雅的方式,使用WaitGroup。 83 | 84 | WaitGroup属于sync这个包,这个包顾名思义,就是提供synchronization基本件的一个包。这里面有Mutex, lock, condition, WaitGroup, 读写锁等等,都是比较底层的部件。 85 | 86 | 87 | [WaitGroup](https://golang.org/pkg/sync/#WaitGroup)是主程序用来等待goroutine执行完毕的。通过使用Add来添加需要等待goroutine的个数,通过Wait来进行阻塞等待goroutine完事,goroutine通过Done来通知主程序说我完事儿了。 88 | 89 | 于是上面的那个例子,可以进化为: 90 | 91 | ``` go 92 | var wg sync.WaitGroup 93 | 94 | func say(s string) { 95 | defer wg.Done() //Delta-1 96 | for i := 0; i < 4; i++ { 97 | time.Sleep(100 * time.Millisecond) 98 | fmt.Println(s) 99 | } 100 | } 101 | 102 | func main() { 103 | wg.Add(1) 104 | go say("Hi") 105 | wg.Add(1) 106 | go say("Hola") 107 | wg.Wait() //Delta = 2, so need to wait 2 goroutines to finish. 108 | 109 | wg.Add(1) 110 | go say("Yeah") 111 | wg.Wait() 112 | } 113 | ``` 114 | 115 | 某次运行的结果如下: 116 | ``` 117 | Hola 118 | Hi 119 | Hi 120 | Hola 121 | Hola 122 | Hi 123 | Hi 124 | Hola 125 | Yeah 126 | Yeah 127 | Yeah 128 | Yeah 129 | ``` 130 | 131 | 可以看到say("Hi")和say("Hola")被添加进了第一次wg,所以wait阻塞结束是在两者都交叉运行完了之后,再继续往下执行say("Yeah")。所以我们会看到Hi, Hola交叉运行,而Yeah是单独连续print的。 132 | 133 | 可以看到WaitGroup相当于是内部有个计数器delta, Add是+1, Done是-1,当Delta重新回到0时,Wait结束。按照我的理解,画个草图相当于这样: 134 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_948/v1522302921/1.pic_xsnlt0.jpg) 135 | 136 | 后面我们结合上面的例子结构再讨论讨论lock, mutex相关的内容,预告里面还有channel,之后可能会深入分析一下routine源码和scheduler。 -------------------------------------------------------------------------------- /golang101_03_struct_interface.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列基础篇——Struct和Interface 2 | 3 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522650631/1.pic_enaskv.jpg) 4 | 5 | 这篇Blog里面我们来聊一聊Golang的结构体和接口(Interface)。前者跟C系的编程语言比较接近比较好理解,后者接口被称为Golang里面一个很精妙的设计,所以比较奇特。是的,就像Go创始人之一Rob Pike描述的朴素真理一样,空的接口啥都没有说。Hhhhhh. 6 | 7 | ## struct 8 | 结构体定义了成员,让各个成员组成一个有机的、可以更好被理解的数据对象结构。比如,一个立方体拥有长、宽、高三个变量,那么通过关键词type......struct 可以定义为如下: 9 | 10 | ``` go 11 | 12 | type Cube struct{ 13 | l float64 14 | w float64 15 | h float64 16 | } 17 | 18 | //or a collapse way: 19 | type Cube struct{ 20 | l, w, h float64 21 | } 22 | ``` 23 | 24 | 使用的时候也是挺简单: 25 | 26 | ``` go 27 | c1 := Cube{2, 3, 4} 28 | fmt.Println(c1.l, c1.w, c1.h) //2, 3, 4 29 | 30 | func GetVolume(c Cube) float64 { 31 | return c.l * c.w * c.h 32 | } 33 | 34 | fmt.Println(GetVolume(c1)) //24 35 | ``` 36 | 37 | 在这个例子当中,我们把struct传入给GetVolume函数求体积。这里struct其实是被复制了一遍的,所以在可以的情况,我们可以使用别的办法避免复制,节约资源。第一个想法就是使用指针, 让函数接受指针,传参的时候使用地址,效果跟前一种方法是一样的: 38 | 39 | ``` go 40 | 41 | func GetVolume(c *Cube) float64 { 42 | return c.l * c.w * c.h 43 | } 44 | 45 | fmt.Println(GetVolume(&c1)) //24 46 | ``` 47 | 48 | 当然,在类似于面向对象这样的思想,我们希望struct可以主动调用这个函数。于是,该函数可以一个指向Cube的指针,传入作为pointer receiver: 49 | 50 | ``` go 51 | func (c *Cube) GetVolume() float64 { 52 | return c.l * c.w * c.h 53 | } 54 | 55 | fmt.Println(c1.GetVolume()) //24 56 | ``` 57 | 58 | 下面再给出一个练习时的完整例子,这里contactInfo struct是套在person struct里面的,使用起来也蛮方便。 59 | 60 | ``` go 61 | package main 62 | 63 | import "fmt" 64 | 65 | type contactInfo struct { 66 | email string 67 | zip int 68 | } 69 | 70 | type person struct { 71 | firstName string 72 | lastName string 73 | contact contactInfo 74 | } 75 | 76 | func (p *person) updateName(firstName string) { 77 | (*p).firstName = firstName 78 | } 79 | 80 | func (p person) print() { 81 | fmt.Printf("%+v\n", p) 82 | } 83 | 84 | func main() { 85 | alex := person{firstName: "Alex", lastName: "Hammer", contact: contactInfo{email: "1@123.com", zip: 94568}} 86 | alex.print() 87 | 88 | alex.updateName("GG") 89 | alex.print() 90 | } 91 | ``` 92 | 93 | 这里值得注意的还是updateName使用了pointer ceceiver, *person。这里主要原因是一是避免复制,二是因为我们将改变原有对象(Person)的属性值,所以需要用指针,跟C++里面reference那一套道理很像。因此,将上述updateName下面这个函数是不会起作用的: 94 | 95 | ``` go 96 | func (p person) updateName(firstName string) { 97 | p.firstName = firstName 98 | } 99 | ``` 100 | 101 | ## interface 102 | 接口定义了一系列的方法集合(collection of method signatures),任何实现方法则被称为“实现了该接口“。这里的哲学被称为是Duck Type: “If it walks like duck, swims like a duck and quacks like a duck, it’s a duck”(如果一个东西走路像鸭子,游泳像鸭子,叫声像鸭子,那么它就是鸭子.) 103 | 104 | 在下面的例子中,我的两个struct各自有自己的GetSalary()和GetWorkContent()实现,做不同的事情。那么我则可以提供一个interface,interface内声明有GetSalary()和GetWorkContent()两个方法,那么其他函数中,我可以传该interface来代替任何一个struct,这样可以增加代码的复用,使代码更加简洁。 105 | 106 | ``` go 107 | package main 108 | 109 | import ( 110 | "fmt" 111 | ) 112 | 113 | type Employee interface { 114 | GetSalary() int 115 | GetWorkContent() string 116 | } 117 | 118 | type Engineer struct { 119 | base int 120 | tech string 121 | } 122 | 123 | type SalesPerson struct { 124 | base, bonus int 125 | activity string 126 | } 127 | 128 | func (e Engineer) GetSalary() int { 129 | return e.base 130 | } 131 | 132 | func (s SalesPerson) GetSalary() int { 133 | return s.base + s.bonus 134 | } 135 | 136 | func (e Engineer) GetWorkContent() string { 137 | return "Coding with " + e.tech 138 | } 139 | 140 | func (s SalesPerson) GetWorkContent() string { 141 | return s.activity 142 | } 143 | 144 | func ShowJobDescription(em Employee) { 145 | fmt.Println(em) 146 | fmt.Println("Income :", em.GetSalary()) 147 | fmt.Println(em.GetWorkContent()) 148 | } 149 | 150 | func main() { 151 | Tony := Engineer{5000, "C++"} 152 | Sarah := SalesPerson{3000, 2000, "business"} 153 | 154 | ShowJobDescription(Tony) 155 | ShowJobDescription(Sarah) 156 | } 157 | 158 | ``` -------------------------------------------------------------------------------- /golang101_06_channel.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列网络篇——初识Channel 2 | 3 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522303664/index_ifnqdy.jpg) 4 | 5 | 之前我们初次聊了一下[goroutine](https://happyisotope.herokuapp.com/golang101_routine),本文来聊聊相关的channel这个神奇的东西。channel主要用于多协程同步的情况,提供了一个交流机制,方便goroutine之间相互沟通。 6 | 7 | ## 声明 8 | channel分三种,双向,只读,只写。他们的声明方式都是通过make来声明的,具体语法分别是: 9 | 10 | ``` 11 | ch0 := make(chan int, 3)//双向, buffered 12 | ch1 := make(chan int)//双向, unbuffered 13 | 14 | ch2 := make(<-chan int) //只读,且读出来的是int 15 | 16 | ch3 := make(chan-< int) //只写,且直往里面写int 17 | ch4 := make(chan<- chan int) //只写,且直往里面写另一个int的channel 18 | ``` 19 | 20 | 当make的时候,在Heap上面分配了一个结构体的空间,并返回指向它的指针。所以其实得到的channel其实是指针,这也就是解释了为什么可以在函数间轻松地传来传去,而不需要指明使用指针,因为本身它就是指针。 21 | 22 | 如果声明时,指明了箭头,那么就代表要么只读,要么只写,具体看箭头方向是指向channel,还是从channel指出。同时箭头,本身也代表了读写操作。 23 | 24 | ``` go 25 | ch <- true //写值 26 | isDead := <-ch //读值并赋值 27 | ``` 28 | 29 | ## Buffered Channel 30 | 在探讨channel之前,我们需要了解他的结构。以buffer channel为例子,它的结构体内有一个buffer队列Q,大小为buffer size, 有一个锁,还有一些Index。 31 | 32 | 每当只定了channel的size, 也就同时指定了buffer queue的size。当传入channel的个数超过队列size时,会block掉当前goroutine,直到有receiver来接收channel里面的信息,每接收一个信息,channel就会pop出该信息,然后被阻塞的goroutine即可继续运行(channel内信息数量没有超过size)。看一个例子: 33 | 34 | ``` go 35 | func main() { 36 | message := make(chan string, 2) // no buffer 37 | count := 4 38 | 39 | go func() { 40 | for i := 0; i < count; i++ { 41 | fmt.Println("Send message") 42 | message <- fmt.Sprintf("Message %d", i) 43 | } 44 | }() 45 | 46 | time.Sleep(time.Second * 5) 47 | 48 | for i := 0; i < count; i++ { 49 | fmt.Println(<-message) 50 | } 51 | } 52 | 53 | ``` 54 | 55 | 运行结果有可能会出现: 56 | ``` 57 | Send message 58 | Send message 59 | Send message 60 | Message 0 61 | Message 1 62 | Message 2 63 | Send message 64 | Message 3 65 | ``` 66 | 67 | 首先往channel里面扔了3个message,还没到主进程读取,主进程在睡觉。当第四个message传入channel时,整个goroutine被堵塞,正好又恰逢主程序刚刚醒了,于是输出channel里面已有的Message内容。同时channel里面buffer空了出来,整个routine可以继续下去,channel可以接收第四个message,之后再主进程读取打印。 68 | 69 | 如果主进程需要从channel输出的信息个数大于我们从goroutine中往channel扔进去的信息个数呢?这时会出现deadlock! 70 | 71 | ## 一个死锁的例子 72 | 还有一个初学者常会误会导致的死锁问题。我们来看一个例子: 73 | 74 | ``` go 75 | func main() { 76 | ch := make(chan int) 77 | 78 | ch <- 999 // write to a channel 79 | fmt.Println(<-ch) 80 | } 81 | ``` 82 | 当运行时,我们会得到"fatal error: all goroutines are asleep - deadlock!"这么一个死锁错误。原因是因为整个主程序都是单线程,当写入channel时,必须要有一个接收方在另一个协程,不然会一直block。 83 | 84 | 为了让程序work,我们可以直接在goroutine里面写,在主程序中接收: 85 | ``` go 86 | func main() { 87 | ch := make(chan int) 88 | 89 | go func() { 90 | ch <- 999 // write to a channel 91 | }() 92 | 93 | println(<-ch) // read from a channel 94 | } 95 | ``` 96 | 这样就可以通过了。 97 | 98 | 99 | ## range 100 | channel里面的内容是可以通过range来遍历的。 101 | 102 | ``` go 103 | package main 104 | 105 | import ( 106 | "fmt" 107 | "time" 108 | ) 109 | 110 | func getMsg(count int, message chan string) { 111 | for i := 0; i < count; i++ { 112 | fmt.Println("Send message") 113 | message <- fmt.Sprintf("Message %d", i) 114 | } 115 | //close a channel to indicate that no more values will be sent. 116 | close(message) 117 | } 118 | 119 | func main() { 120 | message := make(chan string, 3) // no buffer 121 | 122 | go getMsg(cap(message), message) 123 | 124 | time.Sleep(time.Second * 4) 125 | 126 | fmt.Println("The cap of channel now is", cap(message)) 127 | 128 | for i := range message { 129 | fmt.Println(i) 130 | } 131 | } 132 | 133 | ``` 134 | 135 | 如果我们往已经关闭的channel里面继续发送,会产生panic。比如将上面程序改成: 136 | 137 | ``` go 138 | package main 139 | 140 | import ( 141 | "fmt" 142 | "time" 143 | ) 144 | 145 | func getMsg(count int, message chan string) { 146 | for i := 0; i < count; i++ { 147 | fmt.Println("Send message") 148 | message <- fmt.Sprintf("Message %d", i) 149 | } 150 | // 151 | close(message) 152 | } 153 | 154 | func method2(message chan string) { 155 | message <- "ABC" 156 | } 157 | 158 | func main() { 159 | message := make(chan string, 3) // no buffer 160 | 161 | go getMsg(cap(message), message) 162 | 163 | time.Sleep(time.Second * 4) 164 | 165 | //Cause panic 166 | go method2(message) 167 | 168 | fmt.Println("The cap of channel now is", cap(message)) 169 | 170 | for i := range message { 171 | fmt.Println(i) 172 | } 173 | 174 | } 175 | 176 | ``` 177 | 178 | 179 | ## 推荐资料 180 | [Youtube GopherCon 2017: Kavya Joshi - Understanding Channels 181 | ](https://www.youtube.com/watch?v=KBZlN0izeiY) 182 | -------------------------------------------------------------------------------- /golang101_DB01_RabbitMQ.md: -------------------------------------------------------------------------------- 1 | # Golang和RabbitMQ 2 | 3 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_517/v1522740717/rabbitmq_ezbhgc.png) 4 | 5 | 在微服务称霸的今日,Message Broker和消息队列被各大公司广泛应用于业务解耦、可靠投递、广播等等。主流的消息队列有Kafka, ActiveMQ, RabbitMQ, RocketMQ, NSQ等等。本期主要总结一些RabbitMQ的一些基本理论和Golang的基本操作。 6 | 7 | 我最早接触RabbitMQ其实使用的是CloudAMQP,一套RabbitMQ云服务。云服务的好处是它帮你做好了host等工作,你只需要掌握相关API,并专注于自己的业务逻辑即可,使用非常方便。当时我使用它(同时使用的Python和Pika)的目的是,将短时间大量产生的消息,存入消息队列,等待某些服务从队列中Consume,这样一来可以解耦不同的服务,二来可以为消耗方减缓系统压力。 8 | 9 | CloudAMQP是基于RabbitMQ的, 而RabbitMQ则是基于AMQP(Advanced Message Queuing Protocol),一套由Chase银行制定和研发的国际化信息通道传递的标准。这套标准设计了关于消息队列的安全、可靠性、交互性等等一些基本特质,是消息队列的一个重要里程碑。CloudAMQP可以在之后专门再总结一下,本文还是专注于RabbitMQ和Golang。 10 | 11 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522740717/camqp_ymawip.png) 12 | 13 | ## 特点 14 | RabbitMQ有以下主要特点: 15 | 16 | * 支持多语言开发,支持多工具部署。 17 | * 支持多协议,包括不同版本的AMQP,MQTT, HTTP。 18 | * 支持分布式和高可用。 19 | * 支持企业级和云端服务。 20 | * 良好的工具链和监控平台。 21 | 22 | 而作为Message Broker, RabbitMQ有以下几种信息交换的模式: 23 | 24 | * Direct Exchange: 直接通过信息中携带的routing key对应的binding key来决定传向具体的某个队列。 25 | * Fanout: 复制接收到的消息,并传入所有绑定了的队列中,而无视routing key。 26 | * Topic Exchange: 消息将初入一个或者多个队列,通过消息的routing key的模式匹配(比如定义了前缀)。这样一个消息将会被同一个主题的多个队列所消耗。 27 | * Headers: 消息通过头部信息进行分类。 28 | 29 | 关于交换方式,你可以在[这里](https://www.cloudamqp.com/blog/2015-09-03-part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html)找到一个更详细的教程。 30 | 31 | ## Mac安装 32 | 使用万能的homebrew: 33 | > brew install rabbitmq 34 | 35 | 成功之后,找到安装目录下的sbin文件夹(比如我的位置在:/usr/local/Cellar/rabbitmq/3.7.4/sbin)。启动server之后,默认是运行在http://localhost:15672/。 36 | 37 | 通过浏览器,你会进入登陆界面,默认用户名和密码都是guest。成功登陆之后,你会看到整个RabbitMQ的管理界面,里面你可以看到所选的exchange模式,channel具体的内容等等。 38 | 39 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1522743577/64.pic_jqexc7.jpg) 40 | 41 | ## Go的第一个例子 42 | 第一次使用Golang来操作RabbitMQ,有点小激动,哈哈。直接研究了一个官方例子,并附上一些必要的注解。这个是非常基础的例子: 43 | 44 | ``` go 45 | package main 46 | 47 | import ( 48 | "log" 49 | 50 | "github.com/streadway/amqp" 51 | ) 52 | 53 | //Helper dealing with error message 54 | func failOnError(err error, msg string) { 55 | if err != nil { 56 | log.Fatalf("%s: %s", msg, err) 57 | } 58 | } 59 | 60 | func main() { 61 | //Dial creates a connection 62 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 63 | failOnError(err, "Failed to connect to RabbitMQ") 64 | defer conn.Close() 65 | 66 | //conn.Channel creates a new channel, which may create multiple queues later 67 | ch, err := conn.Channel() 68 | failOnError(err, "Failed to open a channel") 69 | defer ch.Close() 70 | 71 | //Make a new queue, with name as "New Queue" and durable. 72 | q, err := ch.QueueDeclare( 73 | "New Queue", // name 74 | true, // durable 75 | false, // delete when unused 76 | false, // exclusive 77 | false, // no-wait 78 | nil, // arguments 79 | ) 80 | failOnError(err, "Failed to declare a queue") 81 | log.Println("Now we create a new queue:", q.Name) 82 | 83 | //Publish sends a Publishing from the client to an exchange on the server. 84 | body := "hello2" 85 | for i := 0; i < 5; i++ { 86 | err = ch.Publish( 87 | "", // exchange 88 | q.Name, // routing key 89 | false, // mandatory 90 | false, // immediate 91 | amqp.Publishing{ 92 | ContentType: "text/plain", 93 | Body: []byte(body), 94 | }) 95 | log.Printf(" [x] Sent %s", body) 96 | failOnError(err, "Failed to publish a message") 97 | } 98 | log.Println("Now we have", q.Messages, "messages in our queue.") 99 | } 100 | ``` 101 | 102 | 其中Queue比较简单,只有三个成员。 103 | ``` go 104 | type Queue struct { 105 | Name string // server confirmed or generated name 106 | Messages int // count of messages not awaiting acknowledgment 107 | Consumers int // number of consumers receiving deliveries 108 | } 109 | ``` 110 | 111 | 得到的log结果: 112 | 113 | ``` 114 | jiazhuos-MBP:rabbitMQ jiazhuo$ go run main.go 115 | 2018/04/03 23:51:16 Now we create a new queue: New Queue 116 | 2018/04/03 23:51:16 [x] Sent hello2 117 | 2018/04/03 23:51:16 [x] Sent hello2 118 | 2018/04/03 23:51:16 [x] Sent hello2 119 | 2018/04/03 23:51:16 [x] Sent hello2 120 | 2018/04/03 23:51:16 [x] Sent hello2 121 | 2018/04/03 23:51:16 Now we have 10 messages in our queue. 122 | ``` 123 | 同时查看之前启动的RabbitMQ的Dashboard,我们可以查到新建的queue已经成功,同时已经有多个Message已经准备好(Ready状态)。 124 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_1208/v1522825653/1.pic_m080sq.jpg) 125 | -------------------------------------------------------------------------------- /golang101_01_array_slice.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列基础篇 String, Array, Slice 2 | 3 | ## 前言 4 | 2017年年底,我才开始正式接触GO语言,这是一个偶尔机会的必然相遇。在最初认识Golang的时候,我开始犯我的老毛病,犹豫这是否是一个值得投入宝贵时间和精力的技术,犹豫学习Golang之后所带给我的好处。其实这种犹豫是大部分人都有的,毕竟现代人都需要认识到精力的有限,只不过每个人对这样的犹豫程度不同罢了。 5 | 6 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1521526970/images2_lcl5sf.png) 7 | 8 | 事实上,在最初的时候,我跟很多人想法不一样。很多人觉得Golang背后有Google,感觉靠山很强,很好棒棒哒,而我反而觉得当一门编程语言被标上Google的标签后,并不见得是一件好事。一旦因为公司政策和方向改变,说不定未来的发展会发生变数。况且以Google一贯的作风(Angular 2,3,4)很难说之后的维护会是怎么样的。在各种中外论坛上,也有很多人对Golang嗤之以鼻,其中比较有名的驳斥,当然是王垠称Golang在设计上很垃圾。 9 | 10 | 我在研究过一小段时间后,发现Golang实在亮点突出:简单。想起《神雕侠侣》中的玄铁重剑,`“重剑无锋,大巧不工”`。也许从大神们的角度和学术的角度来说,Golang有诸多缺点,但是从工程的角度上,它的设计是优秀的:简洁的语法,剔除古怪无用的概念,完整的工具链,强大的社区支持,高性能。 11 | 12 | 至少我从C++开始学起入门的经历,我觉得Golang的上手实在轻松,写着写着竟有写Python的感觉,想想学C++的苦,眼泪快掉下来。所以国内现在Golang一片欣欣向荣,老板招了人,即便从来没有写过Go,只要基础不错,稍微培训一下也能干活。国外也有很多公司慢慢开始转移技术栈,或者一些新产品直接基于Golang。我们公司SAP在Big Data Hub这个大部门下的新产品,很多都是基于Go和K8的。 13 | 14 | 我使用Go主要目的是方便研究分布式和存储,为以后的职场挑战做准备。我觉得需要开一个系列慢慢记录我的学习过程,既然是笔记性质的,所以可能有些地方不详细,望谅解。Golang系列的笔记,范围从Golang基础,web和Network的应用,著名项目源码和架构解读再到Toy Project的开发。积跬步,至千里。 15 | 16 | ## 环境 17 | 安装过程就略了。 18 | 配置环境,我就直接使用Visual Studio Code和Go的插件,可以自动完成排版,纠错等功能。 19 | 20 | ## 基础语法 21 | ### 类型 22 | 基本类型主要有`string`, `bool`, `int(8,16,32或者64位)`, `float`, `byte(uint8)`, `rune(32位)`,`uint(8,16,32或者64位)`,`complex32`,`complex64`。 23 | 24 | 复合类型,主要有`struct`, `slice`, `array`, `union`, `pointer`, `map`, `channel`, `interface`。至于每个复合类型具体是干什么的,我们来一一探究。 25 | 26 | ### string 27 | Go中,字符串string相当于只读的byte slice,以下是一些字符串的常见用法。 28 | 29 | ``` go 30 | package main 31 | 32 | import "fmt" 33 | 34 | func main() { 35 | //the way to declare a var, which is suggested in standard 36 | var s1 string 37 | s1 = "Apple" 38 | fmt.Println(s1) //"Apple" 39 | fmt.Println(s1[3], string(s1[3]), s1[1:3], s1[1:], s1[:2], len(s1)) 40 | // 108 l pp pple Ap 5 41 | 42 | //another way to declare 43 | s2 := "Pear" 44 | 45 | //Concat 46 | fmt.Println(s1 + " " + s2) //print concat string "Apple Pear" 47 | fmt.Println(s1, s2) //print with multiple items: "Apple Pear" 48 | } 49 | ``` 50 | 51 | ### array和slice 52 | 在Golang里面,array和slice感觉是很容易混淆的,不管是形式上,还是从我们的直觉认知上。我目前认为array和slice最大的区别是array作为参数传给函数的时候传的copy, 而slice传的是reference。显然,大多数情况slice更有效,因为slice本身就可以看成对应某个array的一部分(或者全部)reference。 53 | 54 | ``` go 55 | //声明array的时候,需要制定size: 56 | id := [3]int{503, 402, 222} 57 | products := [100]string{} 58 | names := [...]string{"Ben", "Terry"} //让编译器来数个数 59 | 60 | //声明slice的时候不需要指定size: 61 | id := []int{503, 402, 222} 62 | products := make([]string, 100) 63 | 64 | ``` 65 | 66 | 数值Array的未初始化的值是零值,字符串array的未初始化的值是空字符。 67 | 68 | slice除了上述声明方法,还可以使用built-in函数make。 69 | > func make([]T, len, cap) []T 70 | 71 | 比如: 72 | ``` go 73 | slice1 := make([]byte, 10) //len = 10, cap = 10 74 | ``` 75 | 76 | 传入想生成的类型,长度和capacity,可生成需要的零值slice。如果省略cap值,那么cap将默认和len一样。根据我们第一段的描述,slice和array大致关系可以表示为下图: 77 | 78 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,w_528/v1521356443/slice_k7ubqc.jpg) 79 | 80 | 通过图示,可以看出capacity其实就是slice对应的array可以访问的末尾,到slice指向array位置之间的距离。 81 | 82 | 83 | 以下是常见的slice用法: 84 | 85 | ``` go 86 | 87 | package main 88 | 89 | import ( 90 | "fmt" 91 | "strings" 92 | ) 93 | 94 | func main() { 95 | s := []int{1, 4, 52, 3, 5, 4, -2} 96 | fmt.Println(len(s)) //7 97 | fmt.Println(cap(s)) //7 98 | 99 | n1 := s[:4] 100 | fmt.Println(n1) //[1 4 52 3] 101 | fmt.Println(cap(n1)) //7 = 7-0 102 | 103 | n2 := s[3:] 104 | fmt.Println(n2) //[3 5 4 -2] 105 | fmt.Println(cap(n2)) //4 = 7-3 106 | 107 | n3 := s[1:4] 108 | fmt.Println(n3) //[4 52 3] 109 | fmt.Println(len(n3)) //3 110 | fmt.Println(cap(n3)) //6 = 7 -1 111 | 112 | n4 := s[1:4:5] //input[low:high:max], max defines where the limits that slice can reach. 113 | fmt.Println(n4) //[4 52 3] 114 | fmt.Println(len(n4)) //3 115 | fmt.Println(cap(n4)) //4 = 5-1 116 | 117 | //join string list 118 | strList := []string{"This", "is", "simple"} 119 | var aggregateStr string 120 | aggregateStr = strings.Join([]string(strList), "@") 121 | fmt.Println(aggregateStr) //This@is@simple 122 | 123 | //append 124 | ap := []int{1, 4, 5} 125 | ap = append(ap, 2, 3, 4, 4) 126 | fmt.Println(ap) //[1 4 5 2 3 4 4] 127 | 128 | //matrix or 2D array 129 | mat := make([][]int, 3) 130 | count := 0 131 | for i := 0; i < 3; i++ { 132 | mat[i] = make([]int, 2) 133 | for j := 0; j < 2; j++ { 134 | mat[i][j] = count 135 | count++ 136 | } 137 | } 138 | fmt.Println(mat) //[[0 1] [2 3] [4 5]] 139 | } 140 | 141 | ``` 142 | 143 | ### copy 144 | slice的复制,可以直接使用内置的函数copy。 145 | 146 | ``` go 147 | func copy(dst, src []Type) int 148 | ``` 149 | 150 | copy将src里面的内容,复制到dst里面,并返回复制的长度。复制长度是src和dst之间的最小值。 151 | 152 | ``` go 153 | origin := []string{"bfs", "dfs", "dp"} 154 | new1 := make([]string, 3) //just copy the all 3 elements 155 | copy(new1, origin) 156 | fmt.Println(new1) //[bfs dfs dp] 157 | 158 | new2 := make([]string, 2) //just copy the first 2 elements 159 | copy(new2, origin) 160 | fmt.Println(new2) //[bfs dfs] 161 | 162 | new3 := make([]string, 5) //just copy the all 3 elements, and rest of dst will be empty. 163 | copy(new3, origin) 164 | fmt.Println(new3) //[bfs dfs dp ] 165 | ``` 166 | 167 | ## Take away 168 | 这篇Blog主要讲述了我入坑Golang的心路历程和我的初步印象,我的Golang环境,数组array和切片slice, 外加一个built-in copy。接下来近期会记录map, struct, pointer和interface的概念和使用。 169 | 170 | ## Reference: 171 | [1] [The Go Blog: Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) -------------------------------------------------------------------------------- /golang101_04_functions.md: -------------------------------------------------------------------------------- 1 | # Golang 101系列基础篇——Useful functions 2 | 3 | 在本篇笔记中,我将总结一些比较有用的函数,有些是原生内置(built-in),有些则是需要引用外库。这些函数就像是建造一栋楼的基石或者水泥一样,能够完成一些很重要的功能。 4 | 5 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/v1521526970/de4daf20b7e43fc4bca3450d86a1a32c_alreuy.png) 6 | 7 | 8 | ## Copy 9 | 在前面的几期中,我们使用到了一个copy函数,今天我们更深入地来探究一下。copy函数的形式如下: 10 | 11 | > func copy(dst, src []Type) int 12 | 13 | 主要作用是将src slice里面的元素复制到dst slice里面,返回值是成功复制的元素个数。当src比dst长时,只复制前面部分信息,超过的部分不进行复制。我们来看一个例子: 14 | 15 | 16 | ``` go 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | func main() { 24 | d := make([]int, 4) 25 | s := []int{3, 4, 5} 26 | fmt.Println("Before copying: ", d) //Before copying: [0 0 0 0] 27 | fmt.Println("Copy length is: ", copy(d, s)) //Copy length is: 3 28 | fmt.Println("After copying: ", d) //After copying: [3 4 5 0] 29 | 30 | d = make([]int, 4) 31 | s = []int{3, 4, 5, 6, 7, 2} 32 | fmt.Println("Before copying: ", d) //Before copying: [0 0 0 0] 33 | fmt.Println("Copy length is: ", copy(d, s)) //Copy length is: 4 34 | fmt.Println("After copying: ", d) //After copying: [3 4 5 6] 35 | } 36 | 37 | ``` 38 | 39 | 看起来非常Easy,但是如果两个slice是相互交叉的,又会是什么情况呢。下面是两个例子: 40 | 41 | ``` go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | ) 47 | 48 | func main() { 49 | //Example 1 50 | d := []int{1, 2, 3} 51 | s := d[1:] 52 | fmt.Println("Before src:", s, "dest:", d) //Before src: [2 3] dest: [1 2 3] 53 | fmt.Println("Copy length is: ", copy(d, s)) //Copy length is: 2 54 | fmt.Println("After src:", s, "dest:", d) //After src: [3 3] dest: [2 3 3] 55 | 56 | //Example 2 57 | s = []int{1, 2, 3} 58 | d = s[1:] 59 | fmt.Println("Before src:", s, "dest:", d) //Before src: [1 2 3] dest: [2 3] 60 | fmt.Println("Copy length is: ", copy(d, s)) //Copy length is: 2 61 | fmt.Println("After src:", s, "dest:", d) //After src: [1 1 2] dest: [1 2] 62 | 63 | ``` 64 | 注意slice其实都是对应array的reference,所以我们可以画一个图来表示: 65 | 66 | ![](http://res.cloudinary.com/dxdsd8err/image/upload/c_scale,q_70,w_861/v1522050620/2_q2cdyc.jpg) 67 | 68 | 在第一个例子中,dest slice指向array的头array[0],src slice指向array[1],这时要求src往dest slice里面复制,则其实是让 69 | > array[0] = array[1], arrray[1] = array[1] 70 | 71 | 于是array变成了[2, 3, 3],所以dest slice就变成了[2,3,3], src slice从array[1]开始算,即[3,3]。 72 | 73 | 同理可以推论第二个例子,再次就不再赘述。 74 | 75 | ## Sort 76 | 77 | 实用的sort函数来自于Golang中的一个sort包,通过import来引用。 78 | 79 | 首先来看两个简单的例子,分别递增排序和降序排列string slice和int slice。 80 | 81 | ``` go 82 | package main 83 | 84 | import ( 85 | "fmt" 86 | "sort" 87 | ) 88 | 89 | func main() { 90 | //sort string slice 91 | s := []string{"shcool", "cats", "apple", "beats"} 92 | sort.Strings(s) 93 | fmt.Println(s) //[apple beats cats shcool] 94 | fmt.Println("Sorted:", sort.StringsAreSorted(s)) //Sorted: true 95 | 96 | //Deceding sort string slice 97 | sort.Sort(sort.Reverse(sort.StringSlice(s))) 98 | fmt.Println(s) //[shcool cats beats apple] 99 | fmt.Println("Sorted:", sort.StringsAreSorted(s)) //Sorted: false 100 | 101 | //sort int slice 102 | i := []int{1, 44, 2, 32, 25, -1} 103 | sort.Ints(i) 104 | fmt.Println(i) //[-1 1 2 25 32 44] 105 | fmt.Println("Sorted:", sort.IntsAreSorted(i)) //Sorted: true 106 | 107 | //Deceding sort int slice 108 | sort.Sort(sort.Reverse(sort.IntSlice(i))) 109 | fmt.Println(i) //[44 32 25 2 1 -1] 110 | fmt.Println("Sorted:", sort.IntsAreSorted(i)) //Sorted: false 111 | } 112 | ``` 113 | 114 | 115 | 在[源码](https://golang.org/src/sort/sort.go)中, 可以通过实现以下interface来自定义一些排序方法,需要实现的方法有Len, Less, Swap这三个函数,完成之后则既是实现了该接口。 116 | ``` go 117 | // A type, typically a collection, that satisfies sort.Interface can be 118 | // sorted by the routines in this package. The methods require that the 119 | // elements of the collection be enumerated by an integer index. 120 | 121 | type Interface interface { 122 | 123 | // Len is the number of elements in the collection. 124 | Len() int 125 | 126 | // Less reports whether the element with 127 | // index i should sort before the element with index j. 128 | Less(i, j int) bool 129 | 130 | // Swap swaps the elements with indexes i and j. 131 | Swap(i, j int) 132 | } 133 | ``` 134 | 135 | 下面是一个简单的自定义排序例子,通过结构体中的age来排序。 136 | 137 | ``` go 138 | //Define people struct 139 | type People struct { 140 | name string 141 | age int 142 | } 143 | 144 | //Define interface as slice of People 145 | type NameBook []People 146 | 147 | func (nb NameBook) Len() int { return len(nb) } 148 | 149 | func (nb NameBook) Less(i, j int) bool { return nb[i].age < nb[j].age } 150 | 151 | func (nb NameBook) Swap(i, j int) { nb[i], nb[j] = nb[j], nb[i] } 152 | 153 | //Sort by age 154 | nb := []People{ 155 | People{"Tony", 24}, 156 | People{"Staney", 29}, 157 | People{"Witz", 11}, 158 | } 159 | 160 | fmt.Println(nb) //[{Tony 24} {Staney 29} {Witz 11}] 161 | sort.Sort(NameBook(nb)) 162 | fmt.Println(nb) //[{Witz 11} {Tony 24} {Staney 29}] 163 | 164 | ``` 165 | 166 | ## defer 167 | defer在Golang里还挺常见的,主要作用根据名字就能猜到,可以用来延迟函数的执行。实际用处上,主要是用在刚申请完资源(lock, connection等)后,使用defer清理过程来保证在程序结束后,一定会执行清理资源,从而避免因为程序员的疏忽 OR 程序提前中断而没有释放资源。 168 | 169 | 第一个例子是拿到锁之后,立马defer锁的开锁,避免在程序后面,因为各种原因忘记释放锁。 170 | 171 | ``` go 172 | package main 173 | 174 | import ( 175 | "fmt" 176 | "sync" 177 | ) 178 | 179 | var mu sync.Mutex 180 | var m = make(map[string]int) 181 | 182 | //Use mutex to prevent condition race, so no one else can write, when this get method is reading. 183 | func read(key string) int { 184 | mu.Lock() 185 | defer mu.Unlock() //defer the lock, in case exception 186 | return m[key] 187 | } 188 | 189 | func main() { 190 | m["Jack"] = 24 191 | fmt.Println(read("Jack")) //24 192 | } 193 | 194 | ```` 195 | 196 | 另一个例子是在TCP连接中,当TCP建立后,一个listener监听在特定端口,那么在拿到listener之后,一般会选择使用 197 | > defer listener.Close() 198 | 来避免忘记关链接。 199 | 200 | 201 | 当有多个defer同时存在时,defer延迟函数执行的顺序是跟stack一样的“后入先出“,请看下面这个小例子: 202 | 203 | ``` go 204 | package main 205 | 206 | import "fmt" 207 | 208 | func main() { 209 | defer fmt.Println("1") 210 | defer fmt.Println("2") 211 | defer fmt.Println("3") 212 | } 213 | ``` 214 | 215 | 上述代码所得到的结果是: 216 | ``` 217 | 3 218 | 2 219 | 1 220 | ``` --------------------------------------------------------------------------------