go语言实战_Go 语言实战第一个小案例

既然是Go语言实战,那看了就必须动手敲出来,先把书本中的知识点,案例看一遍,有不懂或疑惑,感觉前后无法关联起来的地方,需要翻到前面看相关的知识点,保证能理解书中主要内容,然后在自己凭着自己的理解,重新实现一遍。

案例功能


开启多个 goroutine 去请求 xml 资源, 然后解析xml 数据并存放到 channel 中,最后将 channel 中的数据读取出来,输出到控制台中。

实现步骤


  1. 读取本地的 data.json 文件(该文件存放了需要请求的 url 等相关信息) ,读取文件需要用到 os 包,并且读取完文件后需要及时关闭文件(defer file.Close()), 然后解析 json 文件,解析 json 文件需要使用到 encoding/json 内置包,然后需要准备一个接受解析结果的结构体 Feed。

  2. 得到 Feed 数据后,循环所有的 Feed 数据进行 http 请求获取 xml 数据,每个请求开启 goroutine,为了防止主进程提前结束,此时需要使用 sync.WaitGroup 来阻塞主进程,等待所有 goroutine 结束后,再结束主进程,而 http 请求可以使用 go 提供的内置包 net/http, 需要注意的是,使用 http 请求时,记得及时关闭请求 resp.Body.Close()

  3. http 请求到 xml 数据后,需要解析 xml 数据,解析 xml 需要用到内置包 encoding/xml, 以及准备好相应的结构体(案例解析xml使用到的结构体有:image, item, Channel, RssDocument)来接收解析结果,结构体的字段需要跟返回的xml数据一一对应,写错可能会导致解析到空的数据。

  4. 得到解析后的xml数据之后,就可以根据传入的关键字进行查找了,匹配字符串的需要使用的内置的正则包 regexp, 如果匹配成功,则把相关的内容组装好,然后 append 到通道 results 中

  5. 最后遍历通道(channel) results,输出结果到控制台

  6. 完毕

案例全部逻辑代码


package mainimport (  "encoding/json"  "encoding/xml"  "fmt"  "log"  "net/http"  "os"  "regexp"  "sync")type (  Feed struct {    Name string `json:"site"`    URI string `json:"link"`    Type string `json:"type"`  }  item struct {    XMLName xml.Name `xml:"item"`    Title string `xml:"title"`    Description string `xml:"description"`    PubDate string `xml:"pubDate"`    Link string `xml:"link"`    Guid string `xml:"guid"`    ContentEncoded string `xml:"content:encoded"`    DcCreator string `xml:"dc:creator"`  }  image struct {    XMLName xml.Name `xml:"image"`    Url string `xml:"url"`    Title string `xml:"title"`    Link string `xml:"link"`  }  Channel struct {    XMLName xml.Name `xml:"channel"`    Title string `xml:"title"`    Link string `xml:"link"`    Description string `xml:"description"`    Language string `xml:"language"`    Copyright string `xml:"copyright"`    Generator string `xml:"generator"`    LastBuildDate string `xml:"lastBuildDate"`    Item []item `xml:"item"`    Image image `xml:"image"`  }  RssDocument struct {    XMLName xml.Name `xml:"rss"`    Channel Channel `xml:"channel"`  }  Result struct {    Field string    Content string  })// 用于阻塞进程,等待所有 goroutine 处理完毕,再结束主进程var wg sync.WaitGroup// 存放最终的匹配的结果var results = make(chan *Result)// 读取本地文件,解构json数据func readFile(path string) ([]*Feed, error) {  file, err := os.Open(path)  if err != nil {    return nil, err  }  // 注意:打开文件之后,记得要关闭文件  defer file.Close()  // 注意:文件读取后,需要结构体来解析json数据  var files []*Feed  json.NewDecoder(file).Decode(&files)  return files, nil}// 请求需要的数据源func retrieve(feed *Feed) (*RssDocument, error) {  // http 请求数据源  resp, err := http.Get(feed.URI)  // 请求出错  if err != nil {    return nil, err  }  // 请求结束记得 close 请求  defer resp.Body.Close()  // 状态码非200  if resp.StatusCode != 200 {    return nil, fmt.Errorf("状态码是 %d\n\n", resp.StatusCode)  }  // 解析 xml 数据  var document RssDocument  xml.NewDecoder(resp.Body).Decode(&document)  return &document, nil}// 查找需要查询的字符串func Match(searchTerm string, document *RssDocument)  {  var result Result  for _, item := range document.Channel.Item {    // 匹配标题    matched, err := regexp.MatchString(searchTerm, item.Title)    if err != nil {    }    if matched {      result = Result{        Field: "Title ",        Content: item.Title,      }      results     }    // 匹配内容    matched, err = regexp.MatchString(searchTerm, item.Description)    if err != nil {    }    if matched {      result = Result{        Field: "Description",        Content: item.Description,      }      results     }  }}// 查询func run(searchTerm string, feed *Feed) {  document, err := retrieve(feed)  if err != nil {    fmt.Println("请求出错啦!", err)    return  }  // 匹配字符串  Match(searchTerm, document)}func search(searchTerm string) {  // os内置库读取本地文件,解析 json 文件成 struct  // [https://github.com/goinaction/code/blob/master/chapter2/sample/data/data.json]  path := "./data.json"  feeds, err := readFile(path)  if err != nil {    fmt.Println("读取文件出错啦!")  }  // 需要等待的 goroutine 数量  wg.Add(len(feeds))  // 开启多个goroutine 并发请求数据源, 并 Decode 数据, 然后匹配字符串后,存放到 channel results 中  for _, feed := range feeds {    go func(feed *Feed) {      run(searchTerm, feed)      // 记得处理完进程后,执行 wg.Done 让等待的进程计数减一      wg.Done()    }(feed)  }  // 等待所有 goroutine 结束后,关闭 channel  go func() {    wg.Wait()    close(results)  }()  // 2. display:   display()}// 显示搜索结果func display() {  i := 0  for result := range results {    i++    log.Printf("[%d] %s:\n%s\n\n", i, result.Field, result.Content)  }}func main() {  fmt.Println("--------Start-------\n")  search("president")  fmt.Println("--------Done--------\n")}

相关知识点


1、如果需要声明引用类型(映射map, 切片slice, 通道chan)后赋值,需要使用 make 的方式声明,因为如果使用 var 声明引用类型,会返回默认值 nil, 后面使用赋值会导致出错。

// map// 错误var m map[string]stringm["a"] = "xxx"fmt.Println(m)// 正确var m = make(map[string]string)m["a"] = "xxx"fmt.Println(m)// slice// 错误var s []strings[0] = "xxx"fmt.Println(s)// 正确, 但是 slice 声明的使用需要写第二个参数来表示长度var s = make([]string, 1)s[0] = "xxx"fmt.Println(s)// 正确var s []strings = append(s, "xxx")fmt.Println(s)// chan // 无缓冲 channel , 一般配合 goroutine 使用,// 而且需要等待接收方准备好之后才能发送数据 // 正确var wg sync.WaitGroupwg.Add(1)var ch = make(chan string)go func() {  fmt.Println(  wg.Done()}()ch "x"wg.Wait()  // 有缓冲 channel// 第一个参数表示要存放的数据类型,第二个参数表示缓存长度var ch = make(chan string, 1)ch "xx"fmt.Println(

2、Golang 中所有的函数参数都是以传值方式传递,引用类型也是传值方式,只是传的值有点特殊,传的是指针变量所指向的内存地址,所以在函数内改变引用类型的变量,函数外部的变量也会被改变。

3、go 中并发只需要在函数前面加 go 关键字即可,但是使用 goroutine 时,为了避免主进程提前终止,需要使用 waitGrout 来阻塞,而 waitGrout 是在内置的 sync 包中,使用时,需要 waitGroup.Add(int), waitGroup.Done(), waitGroup.Wait() 配合使用,而waitGroup.Add(int) 中的 int值,一般要和waitGroup.Done() 的次数相等,而waitGroup.Done() 一般是放在 goroutine 中使用,如果 int 小于 waitGroup.Done() 的次数,则会导致主进程提前结束;而 int 大于 waitGroup.Done() 的次数,会导致主进程一直被阻塞,无法继续后续的代码。

goroutine 执行并发,以及waitGroup 配合使用如下:

package mainimport (  "fmt"  "sync")func main() {  var wg sync.WaitGroup  books := []string{"a", "b", "c"}  // len(books)  wg.Add(len(books))  for i, book := range books {    go func(i int, book string) {      fmt.Printf("执行%d %s\n", i, book)      wg.Done()    }(i, book)  }  // 阻塞主进程,等待所有 goroutine 执行完成后才继续往下执行  wg.Wait() }

4、go中闭包的使用,for 循环和闭包一起使用时,如果不将需要的参数传进函数,而是直接使用外部函数中的变量时,可能会导致使用的都是最后一个值。

var wg sync.WaitGroup  books := []string{"a", "b", "c"}  // len(books)  wg.Add(len(books))  for i, book := range books {    go func() {      // 执行结果      // 执行2 c      // 执行2 c      // 执行2 c      fmt.Printf("执行%d %s\n", i, book)      wg.Done()    }()  }  // 阻塞主进程,等待所有 goroutine 执行完成后才继续往下执行  wg.Wait()  

5、接口声明最佳实践,如果是单个方法则以 er 结尾;如果需要维护状态,方法的接受者最好声明为指针;

// 无状态的结构体,即结构体中无任何字段type Dog struct{}// 有状态的结构体type Dog struct{  Name string}


类型的接受者为类型值或类型值指针,那么类型值还是类型值指针都可以调用方法;

而接口类型值只能只能调用接收者为类型值的方法;接口类型值指针可以调用接收者为类型值或类型指针的方法。

package mainimport "log"type People struct {  Name string}// 值接收者func (p People) Hi() {  log.Print("Hi...", p.Name)}type Animal struct {  Species string}// 指针接收者func (a *Animal) yell() {  log.Print("Yelling...", a.Species)}type Skill interface {  Eat()}func (p People) Eat() {  log.Println("People is eating...")}func (a *Animal) Eat() {  log.Println("Animal is eating...")}func main()  {  // 类型调用方法  // 值接收者  //s := People{"Golang"}  //s.Hi()  //s := &People{"Golang"}  //s.Hi()  // 指针接收者  //d := Animal{"Dog"}  //d.yell()  //d := &Animal{"Dog"}  //d.yell()  // 接口实例调用方法  // 值接收者  //p := People{"Golang"}  // People 实现 Skill 接口  // 值类型赋值该接口类型  //var s Skill = p  //s.Eat() // 成功  // 指针类型赋值该接口类型  //var s Skill = &p  //s.Eat() // 成功  // 指针接收者  // c := Animal{"Cat"}  // Animal 实现 Skill 接口  // 值类型赋值该接口类型  //var s Skill = c  //s.Eat() // 直接提示有错误  // 指针类型赋值该接口类型  // var s Skill = &c  // s.Eat() // 成功}// 只要记住:// 结构体类型的方法, 不管是值类型接收者,还是指针j接收者// 结构体实例都能够调用其结构体的方法// 接口类型的方法,// 值类型接收者的实例(不管是值还是指针)赋值给接口实例,接口实例都可以调用方法// 指针接收者的实例,只有实例指针赋值给接口实例,接口实例才能调用方法

6、包中的init方法会在 main 方法之前执行,可以在这做一些初始化的工作。

7、可以使用下划线 _ 来忽略或者当做变量的占位,来处理不需要的变量或者接收未使用的导入的包(此时会执行该包中 init 方法)

8、通道 channel 的使用,channel 分为无缓冲和有缓冲,有只读和只写。

package mainimport "log"func main()  {  // 无缓冲  //var ch = make(chan string)  //ch  //log.Println(  // 有缓冲,第一个参数 存放的数据类型,第二个参数 缓冲长度  var ch = make(chan string, 2)  ch"How are u ?"  ch"I'm fine, and u?"  // 读, 第一个值:消息值,第二个值:通道是否已经关闭  //msg, ok :=   //log.Println(msg, ok)   只写缓冲  //var ch = make(chan  //ch  //log.Println(  // 只读缓冲  //var ch = make(  //ch  //log.Println(  // 关闭 channel  close(ch)}

《Go语言实战》的完整代码传送门:

https://github.com/goinaction/code/blob/master/chapter2/sample

更多精彩推荐

Go语言环境搭建详解(2020版)

Go语言 | 几种常用的排序算法图文解析

《Go语言实战》笔记(二十八) | 后记

《Go语言实战》笔记(一) | Go包管理

0ba5784a5b597a966c9c00069b9808dd.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值