云原生系列Go语言篇-标准库 Part 1

本文聚焦Go标准库,介绍了I/O、时间和JSON处理相关内容。I/O部分讲解了核心接口及使用方法;时间部分介绍了时间表示类型、单调时间和计时器;JSON部分涵盖结构体标签使用、序列化与反序列化、数据流编解码及自定义解析等,还提醒避免使用gob协议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用Go进行开发的最大优势之一是其标准库。与Python类似,Go也采取了“内置电池”的理念,提供了构建应用程序所需的许多工具。由于Go是一种相对较新的语言,它附带了一个专注于现代编程环境中遇到的问题的库。

我们无法涵盖所有标准库包,所幸也不需要,因为有许多优秀的信息源可以了解标准库,比如​​官方文档​​。我们将重点关注几个最重要的包及其设计和用法来演示地道Go语言的基本原则。一些包(​​errors​​、​​sync​​、​​context​​、​​testing​​、​​reflect​​和​​unsafe​​)在各自的章节中进行过介绍。在本章中,我们将学习Go对I/O、时间、JSON和HTTP的内置支持。

I/O和它的小伙伴们

要使程序有价值,它需要能读取和写出数据。Go的输入/输出理念的核心在​​io​​包中有体现。特别是,在该包中定义的两个接口可能是Go中第二和第三最常用的接口:​​io.Reader​​和​​io.Writer​​。

注:第一名是谁呢?自然是​​error​​,我们已经在​​错误处理​​一章中学习过了。

​io.Reader​​和​​io.Writer​​各自定义了一个方法:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

​io.Writer​​接口中的​​Write​​方法接收一个字节切片参数,位于接口的实现中。它返回写入的字节数,如果出现错误则返回错误信息。​​io.Reader​​中的​​Read​​方法更有趣。它不是通过返回参数来返回数据,而是将一个切片作为入参传入实现,并进行修改。最多会将​​len(p)​​个字节写入到该切片中。该方法返回写入的字节数。这可能看起来有点奇怪。读者期望的可能是:

type NotHowReaderIsDefined interface {
    Read() (p []byte, err error)
}

标准库中定义​​io.Reader​​的方式是有原因的。我们来编写一个函数说明如何使用​​io.Reader​​方便大家理解:

func countLetters(r io.Reader) (map[string]int, error) {
    buf := make([]byte, 2048)
    out := map[string]int{}
    for {
        n, err := r.Read(buf)
        for _, b := range buf[:n] {
            if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') {
                out[string(b)]++
            }
        }
        if err == io.EOF {
            return out, nil
        }
        if err != nil {
            return nil, err
        }
    }
}

有三点需要注意。首先,我们只需创建一次缓冲区,在每次调用​​r.Read​​.时复用它即可。这样我们能够使用单次内存分配读取可能很大的数据源。如果​​Read​​方法返回​​[]byte​​,那么每次调用都需要重新分配内存。每次分配最终都会出现在堆上,这会给垃圾回收器带来很大的工作量。

如果我们想进一步减少分配,可以在程序启动时创建一个缓冲池。然后在函数开始处从池中获取一个缓冲区,结束时归还。通过将切片传递给​​io.Reader​​,内存分配就由开发人员所控制。

其次,我们使用​​r.Read​​返回的​​n​​值来了解有多少字节被写入缓冲区,并遍历​​buf​​切片的子切片,处理所读取的数据。

最后,在​​r.Read​​返回的错误是​​io.EOF​​时,对​​r​​的读取就结束了。这个错误有点奇怪,因为它实际上并不是一个错误。它表示​​io.Reader​​中没有剩余可读取的内容。在返回​​io.EOF​​时,我们结束处理并返回结果。

​io.Reader​​的​​Read​​方法有一个特别之处。在大多数情况下,在函数或方法具有错误返回值时,我们在尝试处理非错误返回值之前先检查错误。但在​​Read​​的情况中情况相反,因为在数据流结束或意外情况触发错误之前可能已经返回了一些字节,所以操作相反。

注:如果意外到达了​​io.Reader​​的末尾,会返回一个另一个哨兵错误(​​io.ErrUnexpectedEOF​​)。注意它以字符串​​Err​​开头,表示这是一种意料外的状态。

因为​​io.Reader​​和​​io.Writer​​接口非常简单,可以用多种方式进行实现。我们可以使用​​strings.NewReade​​函数通过字符串创建一个​​io.Reader​​:

s := "The quick brown fox jumped over the lazy dog"
sr := strings.NewReader(s)
counts, err := countLetters(sr)
if err != nil {
    return err
}
fmt.Println(counts)

我们在​​接口是类型安全的鸭子类型​​中讨论过,​​io.Reader​​ 和​​io.Writer​​的实现通常以装饰器模式链接。由于​​countLetters​​依赖于​​io.Reader​​,我们可以使用完全相同的​​countLetters​​函数来计算gzip压缩文件中的英文字母。首先编写一个函数,给定文件名时,返回​​*gzip.Reader​​:

func buildGZipReader(fileName string) (*gzip.Reader, func(), error) {
    r, err := os.Open(fileName)
    if err != nil {
        return nil, nil, err
    }
    gr, err := gzip.NewReader(r)
    if err != nil {
        return nil, nil, err
    }
    return gr, func() {
        gr.Close()
        r.Close()
    }, nil
}

这个函数演示了实现​​io.Reader​​合适的封装类型。我们创建了一个​​*os.File​符合​​io.Reader​​接口),在确保其为有效之后,将它传递给​​gzip.NewReader​​函数,该函数返回一个​​*gzip.Reader​​实例。如果有效,我们返回​​*gzip.Reader​​和一个关闭器闭包,当调用它时可以恰如其分地清理我们的资源。

因​​*gzip.Reader​​实现了​​io.Reader​​,我们可以像之前使用的​​*strings.Reader​​一样使其与​​countLetters​​一起使用:

r, closer, err := buildGZipReader("my_data.txt.gz")
if err != nil {
    return err
}
defer closer()
counts, err := countLetters(r)
if err != nil {
    return err
}
fmt.Println(counts)

因为我们有用于读取和写入的标准接口,在​​io​​包中有一个标准函数用于从​​io.Reader​​拷贝至​​io.Writer​​,即​​io.Copy​​。还有其他标准函数可为已有的​​io.Reader​​和​​io.Writer​​实例添加新功能。其中包括:

​io.MultiReader​​返回一个从多个​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值