一上来接来个大程序,新手能接得住么
这个程序从不同的数据源拉取数据,将数据内容与一组搜索项做对比,然后将匹配的内容显示在终端窗口。这个程序会读取文本文件,
进行网络调用,解码XML 和JSON 成为结构化类型数据,并且利用Go 语言的并发
机制保证这些操作的速度source code
2.1 程序架构
1 | - sample |
2.2 main 包
1 | package main |
有以下几点需要注意:
- main()是程序的入口,没有main函数,构建程序不会生成可执行文件
- 一个包定义一组编译通过的代码,包的名字类似命名空间,可以用来直接访问包内生命的标识符, 可以报不同包中定义的同名标识符区别开
- 下划线开头的包,是为了进行包的初始化操作,GO不允许声明导入包却不使用,下划线让编译器接受这种到日,并且调用对应包内所有文件代码里定义的init函数,init函数的执行在main函数之前
2.3 search 包
serach 包包含了程序使用的框架和业务逻辑
2.3.1 serach.go
serach文件先获取数据源,然后对每个数据源获取的数据进行匹配,每一个匹配启用一个goroutine。使用sync.WaitGroup控制任务是否完成。
sync.WaitGroup是一个计数信号量,主要有三个方法Add、Done和Wait,每增加一个任务就Add一次,每完成一个任务就Done一次,调用Wait的时候程序会阻塞,直到所有任务完成。
1 | package search |
对于上面代码,有以下问题需要明确下:
- feeds, err := RetrieveFeeds() 这种一个函数返回两个值,第一个参数返回值,第二个返回错误信息,是GO中常用的模式
- 声明运算符(:=),这个运算符在声明变量的同时,给变量赋值
- feeds 是一个切片,可以理解为Go里面的动态数组,是一种引用类型
- results是一个无缓冲的channel,和map、slice一样,都是引用类型,channel内置同步机制,从而保证通信安全
- Go中,如果main函数返回,整个程序也就终止了,终止时,会关闭所有之前启动而且还在运行的goroutine
- for range对feeds切片做迭代,和python里面的 for in一样的道理,每次迭代会返回两个值(index,value),value是一个副本,下划线_是一个占位符
- 使用go关键字启动一个goroutine,并对这个goroutine做并发调度。上面程序中go启动了一个匿名函数作为goroutine
- 在Go语言中,所有的变量都是以值的方式传递。所以想要修改真正的值,可以传递指针
- Go语言支持闭包,匿名函数中访问searchTerm、results就是通过闭包的形势访问的。注意matcher、feed这两个变量并没有使用闭包的形式访问
2.3.2 feed.go
feed会从本地的data/data.json中读取Json数据,并将数据反序列化为feed切片,defer会安排随后的函数调用在函数返回时才执行, 使用defer可以缩短打开文件和关闭文件的代码间隔
1 | package search |
2.3.3 match.go/default.go
1 | package search |
func (m defaultMatcher) Search 意味着search和defaultMatcher的值绑定在了一起,我们可以使用defaultMatcher 类型的值或者指向这个类型值的指针来调用Search 方
法。无论我们是使用接收者类型的值来调用这个方,还是使用接收者类型值的指针来调用这个
方法,编译器都会正确地引用或者解引用对应的值,作为接收者传递给Search 方法
1 | // 方法声明为使用defaultMatcher 类型的值作为接收者 |
与直接通过值或者指针调用方法不同,如果通过接口类型的值调用方法,规则有很大不同,
如代码清单2-38 所示。使用指针作为接收者声明的方法,只能在接口类型的值是一个指针的时
候被调用。使用值作为接收者声明的方法,在接口类型的值为值或者指针时,都可以被调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 方法声明为使用指向defaultMatcher 类型值的指针作为接收者
func (m *defaultMatcher) Search(feed *Feed, searchTerm string)
// 通过interface 类型的值来调用方法
var dm defaultMatcher
var matcher Matcher = dm // 将值赋值给接口类型
matcher.Search(feed, "test") // 使用值来调用接口方法
> go build
cannot use dm (type defaultMatcher) as type Matcher in assignment
// 方法声明为使用defaultMatcher 类型的值作为接收者
func (m defaultMatcher) Search(feed *Feed, searchTerm string)
// 通过interface 类型的值来调用方法
var dm defaultMatcher
var matcher Matcher = &dm // 将指针赋值给接口类型
matcher.Search(feed, "test") // 使用指针来调用接口方法
> go build
Build Successful
match创建不同类型的匹配器,Matcher其实是一个接口,对于每种匹配器又有不同的具体实现。
下面的代码中,Matcher接口定义了一个Search方法,每个实现了Search方法的类型都实现了Matcher接口
1 | package search |
Display方法会迭代results这个channel,有数据时会打印,没数据时会阻塞,当main.go中的close(result)后,for range循环结束
注意到default.go有init函数,这个函数会在main中通过下划线导入包的时候执行,init的功能是初始化匹配器
2.4 RSS 匹配器
rss.go篇幅过长,这里不贴代码了,其中有几个关注的点说下:
在init中注册了一个rssMatcher,这个match和之前的defaultMatcher一样,绑定了Search方法,即实现了Matcher接口1
2
3
4func init() {
var matcher rssMatcher
search.Register("rss", matcher)
}
rss.go主要有两个方法retrieve和Search,retrieve负责抓取网略资源,search负责匹配,具体匹配方法这里不表了
2.5 小结
- 每个代码文件都属于一个包,而包名应该与代码文件所在的文件夹同名。
- Go 语言提供了多种声明和初始化变量的方式。如果变量的值没有显式初始化,编译器会将变量初始化为零值。
- 使用指针可以在函数间或者goroutine 间共享数据。
- 通过启动goroutine 和使用通道完成并发和同步。
- Go 语言提供了内置函数来支持Go 语言内部的数据结构。
- 标准库包含很多包,能做很多很有用的事情。
- 使用Go 接口可以编写通用的代码和框架。