【Go】2、Go语言实战
前言
本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。
前置知识
本篇将涉及到一些在命令行的输入输出操作,之前我们已经学习了fmt
包用于输出内容,下面将介绍bufio
包用于读入数据。
bufio包简介
bufio
是 Go 标准库中一个强大的带缓冲 I/O 包,它通过提供缓冲机制来优化 I/O 操作性能,特别适合处理大量小数据块的读写场景。
bufio
包主要由三个组件构成:
-
Reader - 带缓冲的读取器
-
Writer - 带缓冲的写入器
-
Scanner - 高级扫描器
下面将分别对三个组件中的关键方法进行解读。
Reader
使用场景:
-
需要精确控制读取过程时
-
处理二进制数据或特定分隔符
-
需要预读(Peek)功能时
| 方法签名 | 描述 | 返回值 | 应用场景 |
|-------------------------------------------------------------------------|--------------------------|-----------------|--------------------------|
| `func (b *Reader) Read(p []byte) (n int, err error)` | 读取数据到字节切片 | 字节数, 错误 | 读取二进制数据 |
| `func (b *Reader) ReadByte() (byte, error)` | 读取单个字节 | 字节值, 错误 | 处理二进制格式 |
| `func (b *Reader) ReadRune() (r rune, size int, err error)` | 读取单个 Unicode 字符 | 字符, 字节数, 错误 | 处理多字节编码文本 |
| `func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)` | 读取一行文本 | 行内容, 是否截断, 错误 | 处理文本文件 |
| `func (b *Reader) ReadString(delim byte) (string, error)` | 读取直到分隔符 | 字符串, 错误 | 按分隔符读取内容 |
| `func (b *Reader) ReadBytes(delim byte) ([]byte, error)` | 读取字节直到分隔符 | 字节切片, 错误 | 需要原始字节数据 |
| `func (b *Reader) Peek(n int) ([]byte, error)` | 预读而不移动指针 | 字节切片, 错误 | 查看后续内容 |
| `func (b *Reader) Buffered() int` | 获取缓冲区的字节数 | 字节数 | 检查缓冲区状态 |
| `func (b *Reader) Reset(r io.Reader)` | 重置 Reader 关联的源 | 无 | 重用 Reader 对象 |
Writer
使用场景:
-
频繁写入小量数据
-
需要高性能文件写入
-
格式化输出到控制台或文件
| 方法签名 | 描述 | 返回值 | 应用场景 |
|-------------------------------------------------------------------------|--------------------------|-----------------|--------------------------|
| `func (b *Writer) Write(p []byte) (nn int, err error)` | 写入字节切片 | 写入字节数, 错误 | 写入二进制数据 |
| `func (b *Writer) WriteByte(c byte) error` | 写入单个字节 | 错误 | 写入特定字节值 |
| `func (b *Writer) WriteRune(r rune) (size int, err error)` | 写入 Unicode 字符 | 写入字节数, 错误 | 处理多语言文本 |
| `func (b *Writer) WriteString(s string) (int, error)` | 写入字符串 | 写入字节数, 错误 | 写入文本内容 |
| `func (b *Writer) Flush() error` | 刷新缓冲区到底层 | 错误 | 确保数据写入完成 |
| `func (b *Writer) Available() int` | 获取剩余缓冲区大小 | 字节数 | 检查缓冲区容量 |
| `func (b *Writer) Buffered() int` | 获取已缓冲字节数 | 字节数 | 检查待刷新数据量 |
| `func (b *Writer) Reset(w io.Writer)` | 重置 Writer 关联目标 | 无 | 重用 Writer 对象 |
Scanner
使用场景:
-
按行处理文本文件
-
解析结构化文本数据
-
需要简单分割输入内容时
| 方法签名 | 描述 | 返回值 | 应用场景 |
|-------------------------------------------------------------------------|--------------------------|-----------------|--------------------------|
| `func (s *Scanner) Scan() bool` | 扫描下一个 token | 是否扫描成功 | 迭代处理输入 |
| `func (s *Scanner) Text() string` | 获取扫描到的文本 | 字符串 | 获取当前 token 内容 |
| `func (s *Scanner) Bytes() []byte` | 获取扫描到的字节 | 字节切片 | 需要原始字节数据 |
| `func (s *Scanner) Err() error` | 获取扫描错误 | 错误 | 检查扫描过程错误 |
| `func (s *Scanner) Split(split SplitFunc)` | 设置分割函数 | 无 | 自定义扫描逻辑 |
| `func (s *Scanner) Buffer(buf []byte, max int)` | 设置缓冲区 | 无 | 处理大 token |
案例一、猜数游戏
概述
系统在一定范围内随机一个数据,让用户进行猜数,统计用户猜测次数,直到用户猜到数字结束程序。
设计思路
-
生成一个随机数,用于让用户猜测。
-
设置一个死循环,用于持续读取用户数据。
-
将用户数据进行解析判断,看看是否等于目标值,并给出一定提示。
代码
写了两种读入方式,分别用Reader进行读入和用Scanner进行读入。
package mainimport ("bufio""fmt""math/rand""os""strconv""time"
)func main() {maxNum := 100rand.Seed(time.Now().UnixNano())secretNumber := rand.Intn(maxNum)//reader := bufio.NewReader(os.Stdin)sc := bufio.NewScanner(os.Stdin)fmt.Println("---猜数游戏已就绪----")cnt := 0for {cnt++fmt.Printf("第%v次猜数,你的选择是:\n", cnt)//a, err := reader.ReadString('\n')//if err != nil {// fmt.Println("不可预知的错误")// cnt--// continue//}//a = strings.TrimSpace(a)a := sc.Scan()if sc.Err() != nil {fmt.Println("读取输入错误:", sc.Err())cnt--continue}guess, err := strconv.Atoi(sc.Text())if err != nil {fmt.Printf("'%v' 不是有效数字,请重新输入\n", a)cnt--continue}if guess == secretNumber {fmt.Printf("恭喜你,第%v次猜对了!\n", cnt)break} else if guess > secretNumber {fmt.Println("没猜对,猜大了")} else {fmt.Println("没猜对,猜小了")}}fmt.Println("Game Over")
}
需要注意的是:
-
在测试的时候,如果编译器用到是vscode的话,不能使用那个自带的调试控制器,需要用终端进行测试。
课后作业
修改猜谜游戏中的最终代码,使用fmt.Scanf来简化代码实现
就是换了一种读入的方式,感觉Go的读入方式还是比较玄学的,后续会开个新的文章将各种读取方式进行详解。
package mainimport ("fmt""math/rand""time"
)func main() {maxNum := 100rand.Seed(time.Now().UnixNano())secretNumber := rand.Intn(maxNum)fmt.Println("---猜数游戏已就绪----")cnt := 0for {cnt++fmt.Printf("第%v次猜数,你的选择是:\n", cnt)var guess int_, err := fmt.Scanf("%d", &guess)if err != nil {fmt.Println("输入无效!请输入数字")var discard stringfmt.Scanln(&discard)continue}fmt.Scanln()if guess == secretNumber {fmt.Printf("恭喜你,第%v次猜对了!\n", cnt)break} else if guess > secretNumber {fmt.Println("没猜对,猜大了")} else {fmt.Println("没猜对,猜小了")}}fmt.Println("Game Over")
}
案例二、在线词典
概述
设计一个在线词典用于翻译。
设计思路
-
通过抓包的方式,抓取到一个在线翻译网站的API。
-
因为自己编写请求头比较麻烦,我们使用
https://curlconverter.com/go/
这个网站可以将我们复制的 cURL bash直接转化成Go的代码。 -
然后查看请求的响应,我们需要对响应和请求进行一个封装,可以通过
https://tool.lvtao.net/json2go
网站对请求快速转化为Go的结构体。 -
然后编写基础代码进行query请求即可。
代码
package mainimport ("bytes""encoding/json""fmt""io/ioutil""log""net/http""os"
)type DictRequest struct {TransType string `json:"trans_type"`Source string `json:"source"`UserID string `json:"user_id"`
}type DictResponse struct {Rc int `json:"rc"`Wiki struct {KnownInLaguages int `json:"known_in_laguages"`Description struct {Source string `json:"source"`Target interface{} `json:"target"`} `json:"description"`ID string `json:"id"`Item struct {Source string `json:"source"`Target string `json:"target"`} `json:"item"`ImageURL string `json:"image_url"`IsSubject string `json:"is_subject"`Sitelink string `json:"sitelink"`} `json:"wiki"`Dictionary struct {Prons struct {EnUs string `json:"en-us"`En string `json:"en"`} `json:"prons"`Explanations []string `json:"explanations"`Synonym []string `json:"synonym"`Antonym []string `json:"antonym"`WqxExample [][]string `json:"wqx_example"`Entry string `json:"entry"`Type string `json:"type"`Related []interface{} `json:"related"`Source string `json:"source"`} `json:"dictionary"`
}func query(word string) {client := &http.Client{}request := DictRequest{TransType: "en2zh", Source: word}buf, err := json.Marshal(request)if err != nil {log.Fatal(err)}var data = bytes.NewReader(buf)req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)if err != nil {log.Fatal(err)}req.Header.Set("Connection", "keep-alive")req.Header.Set("DNT", "1")req.Header.Set("os-version", "")req.Header.Set("sec-ch-ua-mobile", "?0")req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")req.Header.Set("app-name", "xy")req.Header.Set("Content-Type", "application/json;charset=UTF-8")req.Header.Set("Accept", "application/json, text/plain, */*")req.Header.Set("device-id", "")req.Header.Set("os-type", "web")req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")req.Header.Set("Origin", "https://fanyi.caiyunapp.com")req.Header.Set("Sec-Fetch-Site", "cross-site")req.Header.Set("Sec-Fetch-Mode", "cors")req.Header.Set("Sec-Fetch-Dest", "empty")req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")resp, err := client.Do(req)if err != nil {log.Fatal(err)}defer resp.Body.Close()bodyText, err := ioutil.ReadAll(resp.Body)if err != nil {log.Fatal(err)}if resp.StatusCode != 200 {log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))}var dictResponse DictResponseerr = json.Unmarshal(bodyText, &dictResponse)if err != nil {log.Fatal(err)}fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)for _, item := range dictResponse.Dictionary.Explanations {fmt.Println(item)}
}func main() {if len(os.Args) != 2 {fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello`)os.Exit(1)}word := os.Args[1]query(word)
}