【Go】2、Go语言实战

发布于:2025-06-03 ⋅ 阅读:(23) ⋅ 点赞:(0)

前言

        本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。


前置知识

        本篇将涉及到一些在命令行的输入输出操作,之前我们已经学习了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             |

    案例一、猜数游戏

    概述

            系统在一定范围内随机一个数据,让用户进行猜数,统计用户猜测次数,直到用户猜到数字结束程序。

    设计思路

    1. 生成一个随机数,用于让用户猜测。

    2. 设置一个死循环,用于持续读取用户数据。

    3. 将用户数据进行解析判断,看看是否等于目标值,并给出一定提示。

    代码

            写了两种读入方式,分别用Reader进行读入和用Scanner进行读入。

    package main
    
    import (
        "bufio"
        "fmt"
        "math/rand"
        "os"
        "strconv"
        "time"
    )
    
    func main() {
        maxNum := 100
        rand.Seed(time.Now().UnixNano())
        secretNumber := rand.Intn(maxNum)
        //reader := bufio.NewReader(os.Stdin)
        sc := bufio.NewScanner(os.Stdin)
        fmt.Println("---猜数游戏已就绪----")
        cnt := 0
        for {
            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 main
    
    import (
        "fmt"
        "math/rand"
        "time"
    )
    
    func main() {
        maxNum := 100
        rand.Seed(time.Now().UnixNano())
        secretNumber := rand.Intn(maxNum)
        fmt.Println("---猜数游戏已就绪----")
        cnt := 0
        for {
            cnt++
            fmt.Printf("第%v次猜数,你的选择是:\n", cnt)
            var guess int
            _, err := fmt.Scanf("%d", &guess)
            if err != nil {
                fmt.Println("输入无效!请输入数字")
                var discard string
                fmt.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")
    }
    

    案例二、在线词典

    概述

            设计一个在线词典用于翻译。

    设计思路

    1. 通过抓包的方式,抓取到一个在线翻译网站的API。

    2. 因为自己编写请求头比较麻烦,我们使用 https://curlconverter.com/go/这个网站可以将我们复制的 cURL bash直接转化成Go的代码。

    3. 然后查看请求的响应,我们需要对响应和请求进行一个封装,可以通过https://tool.lvtao.net/json2go网站对请求快速转化为Go的结构体。

    4. 然后编写基础代码进行query请求即可。

    代码

    package main
    
    import (
        "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 DictResponse
        err = 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)
    }