Go标准库简述

Go标准库中包含了大量的包,这些包提供了非常广泛的功能。在这里我们只选择性的介绍一些包。这是因为在本书发布之后,库还在增加。所以最好的方式是查看在线API(golang.org/pkg/)或者本地godoc, 这两种方法都可以查看到最近的信息和详细的信息。

exp中的包("experimental实验性")有可能会添加到标准库当中,所以在这里的包不应使用,除非我们想要参与它们的开发(e.g.,测试,评论,提交补订). 当从Google的Go源代码树中获取的exp包也是正常可用的,但是它们不会被包含到预编译的包中。所有的其它包都是可用的,虽然在写本书的时候,有些还不是很完整。

存档与压缩包

Go可以读写tarball和.zip文件。相关的包为archive/tar和archive/zip, 对于压缩的tarballs,可以使用compress/gzip和compress/bzip2. 这本书的pack和unpack的例子在8.2, 第397页。

其它的压缩格式也是支持的,比如,Lempel-Ziw-Welch(compress/lzw)可以用于.tiff的图片和.pdf的文件。

字节和字符串相关的包

bytes和strings包有很多共同的功能。前者用于[]byte值,可用者用于字符串。对于字符串,strings包提供了最有用的工具,用于查找,替换子串,分割字符串,去掉字符串中的字符,和改变大小写。 strconv包提供了数字和布尔值到字符串的转换功能。fmt包提供了各种各样的打印和scan函数,print函数在第三章中有很详细的讲解。scan函数在表8.2(page 383), 而它的例子在(8.1.3.2 380).

unicode包提供的函数用于确定字符属性,比如一个字符是否为打印字符,或者是数字。unicode/utf8和unicode/utf16包用于编码和解码runes(i.e.,Unicode字符码). 对于 unicode/utf8,可以看3.6.3, 117. 而unicode/utf16在第八章的utf16-to-utf8的练习中。

text/template和html/template包可以用来创建模板.以下是个简单的使用text/template的例子

type GiniIndex struct {
    Country string
    Index float64
}
gini := []GiniIndex{{"Japan", 54.7}, {"China", 55.0}, {"U.S.A.", 80.1}}
giniTable := template.New("giniTable")
giniTable.Parse(
`<TABLE>` +
    `{{range .}}` +
    `{{printf "<TR><TD>%s</TD><TD>%.1f%%</TD></TR>" .Country .Index}}`+
    `{{end}}` +
    `</TABLE>`)
err := giniTable.Execute(os.Stdout, gini)

Output

<TABLE>
<TR><TD>Japan</TD><TD>54.7%</TD></TR>
<TR><TD>China</TD><TD>55.0%</TD></TR>
<TR><TD>U.S.A.</TD><TD>80.1%</TD></TR>
</TABLE>

template.New()函数用给定的名字,创建一个*template.Template。模板的名字在识别模板时非常有用(实际用于模板嵌套于其它模板)。template.Template.Parse()函数用于解析一个模板(典型的为一个.html文件), 这样就可以用于后面的填充。template.Template.Execute()函数将产生的结果发送到io.Writer, 第二个参数是用于填充到模块的数据。在这个例子中,我们将文本输出到os.Stdout,同时传递了gini slice给它。

在一个模板中,行为(action)都封装在双花括号这中(). function (start, stop, step) { if(!stop) { stop = start; start = 0; step = 1; } else if(!step) { step = 1; }

        var arr = [];
        var i;
        if (step > 0) {
            for (i=start; i<stop; i+=step) {
                arr.push(i);
            }
        } else {
            for (i=start; i>stop; i+=step) {
                arr.push(i);
            }
        }
        return arr;
    } ... 用于遍历一个slice中的元素。在这里,我们设置在slice中的每一个GiniIndex类型的元素为点(.), 即为当前元素。我们可以通过名字访问struct中的导出字段,当然前面需要加点号,表示当前元素。行为跟fmt.Printf()函数一样,但使用空格替换了圆括号和参数分隔符的逗号。

text/template和html/template都有自已复杂的模板语言。比如包含遍历和条件分支的行为,支持变量和方法的调用。除此之后,html/template包是安全的,不会有代码注入。

Collection包

Slice是Go提供的最有效率的集合类型,但有时,需要使用到更加专业的集合类型。很多情况下,内置的map类型可以满足。但是Go标准库中也提供了container包,包含了各种各样的集合类型。

container/heap包提供了操作堆的函数,这里的heap必须是一个实现了heap.Interface接口的自定义类型的值. 一个堆(严格来说,一个最小堆min-heap)按顺序维护它的值。这样它的第一个元素总是最小的(对于max-heap为最大的值), 这被称为堆的属性。heap.Interface嵌入了sort.Interface,和添加了Push()和Pop()方法。我们在第4.2.4和5.7, 244中讲过sort.Interface.

以下是自定义的heap类型

type IntHeap []int
func (ints *IntHeap) Less(i, j int) bool {
    return (*ints)[i] < (*ints)[j]
}
func (ints *IntHeap) Swap(i, j int) {
    (*ints)[i], (*ints)[j] = (*ints)[j], (*ints)[i]
}
func (ints *IntHeap) Len() int {
    return len(*ints)
}
func (ints *IntHeap) Pop() interface{} {
    x := (*ints)[ints.Len()-1]
    *ints = (*ints)[:ints.Len()-1]
    return x
}
func (ints *IntHeap) Push(x interface{}) {
    *ints = append(*ints, x.(int))
}


ints := &IntHeap{5, 1, 6, 7, 9, 8, 2, 4}
heap.Init(ints) // Heapify
ints.Push(9) // IntHeap.Push() doesn't preserve the heap property
ints.Push(7)
ints.Push(3)
heap.Init(ints) // Must reheapify after heap-breaking changes
for ints.Len() > 0 {
    fmt.Printf("%v ", heap.Pop(ints))
}
fmt.Println() // prints: 1 2 3 4 5 6 7 7 8 9 9

这个自定义类型的实现对于大部分情况足够使用。我们可以稍微也进一下代码,将类型定义为type IntHeap struct {ints []int},这样在方法中可以使用ints.ints访问,而不需要使用*ints,具有更好的可读性。

container/list包,提供了双向链表。添加到链表的元素可以为interface{}类型的值。从链表中检索回的元素类型为list.Element, 可以通过list.Element.Value获得原始的值。

items := list.New()
for _, x := range strings.Split("ABCDEFGH", "") {
    items.PushFront(x)
}
items.PushBack(9)
for element := items.Front(); element != nil; element = element.Next() {
    switch value := element.Value.(type) {
    case string:
        fmt.Printf("%s ", value)
    case int:
        fmt.Printf("%d ", value)
    }
}
fmt.Println() // prints: H G F E D B A 9

在这个例子中,我们将单字的字符串添加到新的链接的最前部(从前面添加元素), 然后在最后添加一个数字。 添加完元素后,我们遍历链表中的元素并且打印每一个元素的值。我们实际上可以不用使用到type switch, 可以直接使用fmt.Printf("%v ", element.Value), 但如果我们不仅仅是为了打印,我们就需要使用到type switch. 当然,如果所有的元素都是相同的类型,我们可以使用断言(type assertion)——比如, element.Value.(string). 断言和type switch在第5.2.2.2, 197和5.1.2 191

除了上面使用到的方法,list.List类型还有很多其它的方法,包括Back(), Init()(用于清空list), InsertAfter(), InsertBefore(), Len(), MoveToBack(), MoveToFront(), PushBackList()(将一个list添加到另一个链接的后面), Remove().

标准库中的container/ring包提供了循环表。

虽然所有的集合类型保存的数据都是在内存中,但Go也提供了database/sql包,它为SQL数据库提供了泛型接口。对于实际的数据库,必须安装特定数据库驱动的包。驱动包和其它集合类型的包,都可以从Go Dashboard(godashboard.appspot.com/project)中获得。以我们之前的omap.Map类型,它就是基于一个 LLRBT左倾红黑树。

文件,操作系统相关包

标准库中提供了许多用于文件和目录处理和与操作系统交互的包。在很多情况下,这些包提供了与操作系统元关的抽像,使得容易创建跨平台的应用程序。

os package提供了与操作系统交换的函数,比如改变当前的工作目录,改变文件的模式和拥有者,获得和设置环境变量,创建和删除文件和目录。此外,这个包中的os.Create() 和os.Open()函数用于创建和打开一个文件。而获得一个文件的属性(e.g.,通过os.FileInfo类型). 这些我们都在之前的章节中已讲到过(比如7.2.5, 349和第8章).

一旦一个文件被打开,尤其是文本文件,通常是通过一个缓冲区来访问(e.g.,读取一行字符串,而不是一个byte slice). 但缓冲区需要使用bufio包,这在我们之前很多例子中都讲解过。除了使用bufio.Reader和bufio.Writer读取写符串,我们也可以read(and unread) runes, read(and unread) 单个字节,多个字节,以及写runes, 单个字节和多个字节。

io包提供了大量的函数用于io.Reader和io.Writer(*os.File满足这两个接口),比如,我们可以使用io.Copy()函数将reader中的数据复制到一个writer. 这个包中有的函数也可以用于创建同步的内存管道。

io/ioutil包,提供了更方便的高级别函数。其中,这个包的ioutil.ReadAll()函数用于读取io.Reader中的所有数据,并且一个[]byte返回;ioutil.ReadFile()函数也是类型,但接收的是一个字符串参数(文件名)而不是一个io.Reader;ioutil.TempFile()函数返回一个临时文件(*os.File); ioutil.WriteFile()函数将一个[]byte 写入到给定文件名的文名中。

path包中的函数用于操作类Unix的路径,URL路径, git "引用", FTP文件等等。path/filepath包提供了跟path包相同的函数和其它更多的函数——它用于提供平台无关的路径处理。这个包中的filepath.Walk()函数用于遍历一个给定路径下的所有文件和目录,如第7.2.5所示的。

runtime package包含了很多函数和类型,用于访问Go程序运行的系统。很多都是高级的功能,通常我们不需要在创建标准的可维护程序时使用到。但是在这个包中有几个常量非常有用,比如runtime.GOOS,它保存一个字符串(e.g., "darwin", "freebsd", "linux", or "windows"), runtime.GOARCH它保存的也是一个字符串(e.g., "386", "amd64" or "arm"). runtime.GOROOT()函数返回GOROOT环境变量的值(如果没有设置GOROOT环境变量,则返回Go创建时的根目录). runtime.Version()函数返回Go的版本(字符串). 我们还在第7章中使用了runtime.GOMAXPROCS()和runtime.NumCPU()函数来确保Go所使用了机器的所有处理器.

文件格式相关的包

Go支持文本和二进制文件的处理。Go对JSON和XML文件提代了专门的包,以及Go自已的二进制文件格式,这种格式具有非常快,简洁,方便使用的特性。(第8章中有讲解过)

此外,csv包可以用于读写.csv文件。这个包将这些文件中的每一行(逗号分隔)作为一条记录来访问。这个包非常通用——比如,可以改变分隔符(e.g., 将逗号改为tab或者其它字符), 以及如何读写其它方面的记录和字段。

encoding package包含了几个包,一个是encoding/binary, 我们已经用它来读取二进制数据(第8.1.5, 387). 其它的包用来编码和解码不同的其它格式——比如encoding/base64,用于编码和解码URL。

图型相关的包

Go image包提供了一些高级别的函数和类型,用于创建和处理图像数据。包包含了用于不同标准文件格式的编码和解码器,比如image/jpeg和impage/png. 我们在这章的9.1.2, 416)以及第7章的练习中都讨论过。

image/draw包提供了一些基础的绘制函数,比如我们在第6章中看到的. 第三方包freetype,添加了更多的绘制函数。freetype包可以使用指定的TrueType类型的字体绘制文本, freetype/raster包,可以画直接,立方体和二次曲线。

Mathematics包

math/big包提供了无限大小(限制于内存大小)的整型(big.Int)和实数(big.Rat); 这些都在第2.3, 57页中讨论过。 pi_by_digits例子中展示了如何使用big.Ints.

math package提供了所有的标准数学函数(基于float64) 和多个标准常量。如表2.8, 2.9, 2.10所示。

math/complx包提供了操作复数的标准函数(基于complex128). 如表2.11

其它

除也可以分组的包,标准裤中还包含了许多单个的包。

crypto包提供了MD5,SHA-1,SHA-224,SHA-256,SHA-384和SHA-512的hash算法。除此以外,crypto包还提供了AES,DES等多种不同的加密和解密算法, 每个包对应于它相应的算法名字(crypto/aes,crypto/des)。

exec包用于运行外部程序。虽然这可以通过os.StartProcess()完成,但exec.Cmd类型会更方便。

flag包提供了一个命令行分析器。它接受X11样式的可选项(e.g., -width, 而不是GNU样式的-w 和 --width). 这个包会生成一个非常简单的使用帮助,并且只对指定的值的类型进行验证。(所以,这个包可以用于指定int类型的可选项,而不是什么值可能可接受), 在Go Dashboard中还有其它几个类型的包。

log包提供了记录信息的函数(默认输出到os.Stdout), 并且终止程序或者抛出一个异常。log包的可以使用log.SetOutput()函数,将输出目的端修改变任何io.Writer。日志消息由时间戳和消息组成; 如果不想输出时间戳,可以在第一条log函数调用前,使用log.SetFlags(0)。也可以使用log.New()函数创建自定义的logger。

math/rand包提供了很多非常有用的伪随机数字产生函数,包括rand.Int(),它返回一个int,rand.Intn(n)它返回从[0,n)之间的一个随机整数。crypto/rand包中有一个函数用于生成加密的强伪随机器。

regexp包提供了非常快和强大的正则表达式引擎,它支持RE2引擎的语法。我们在之前的例子中多次使用了这个包。

sort包提供了用于对int, float64和string类型的slice进行排序的函数,和在已排序slice中进行快序搜索(对半查找). 它也提供了泛型的sort.Sort()和sort.Search()函数,用于自定义数据(例子在4.2.4, 160;表4.2,161和5.6.7,238)

time包用于测量时间和解析和格式化日期,date/time和time的值。time.After()函数可以用来在channel中发送当前的时间,它在传递的指定纳秒后返回(第332页中的例子).time.Tick()和time.NewTicker()函数可以用于提供一个channel,用来在指定间隔内发送"tick". time.Time struct有用来提供当前时间的方法和格式化date/time作为字符串和解析date/times的方法(time.Time的例子在第8章375).

网络包

Go标准库中有许多的支持网络有关的程序包。net包提供的函数和类型用于Unix域的通信和网络socket,TCP/IP,and UDP. 这个包也提供了域名的解析。

net/http包使用了net包,用于解析http的请求,响应。同时提供了一个基础HTTP客户端。net/http包也包含了一个易于扩展的HTTP服务器,比如,我们之前在第2章中使用到的(2.4,72)和第3章中的练习。net/url包提供了RUL解析和参数转义。

其它高级的网络编程包也在标准库中,其中一个是net/rpc("Remote Procedure Call")允许一个服务器提供对像,它的方法可以被客户端调用。另一个是net/smtp用于发送email.

The Reflect Package

reflect用于提供运行时reflect(也称为内省),即,可以在运行时,访问任意类型的值。

这个包也提供了一些有用的工具函数,比如reflect.DeepEqual(),它可以比效任意的两个值——比如,slice, 它们不能直接使用==和!=操作符。

Go中的每一个值都有两个属性:它的实际值和它的类型。reflect.TypeOf()函数可以告诉我们任意值的类型。

x := 8.6
y := float32(2.5)
fmt.Printf("var x %v = %v\n", reflect.TypeOf(x), x)
fmt.Printf("var y %v = %v\n", reflect.TypeOf(y), y)

Output

var x float64 = 8.6
var y float32 = 2.5

在这里,我们输出了两个浮点变量和它们的类型。

当在一个值上调用了reflect.ValueOf()函数, 它返回一个reflect.Value, 它持有这个值,但又不是值本身。如果我们想要访问持有的这个值,我们必须使用reflect.Value类型中的方法。

word := "Chameleon"
value := reflect.ValueOf(word)
text := value.String()
fmt.Println(text)
//Chameleon

reflect.Value类型有很多的方法,用来提取底层的类型。包括reflect.Value.Bool(), reflect.Value.Complex(), reflect.Value.Float(), reflect.Value.Int(), 和reflect.Value.String().

reflect包也可以用于集合类型,比如slice和map. 也可以用于struct。甚至可以访问struct的tag文本.(tag用于json和xml的编码和解码器,第8章中讲过)

type Contact struct {
    Name string "check:len(3,40)"
    Id int "check:range(1,999999)"
}
person := Contact{"Bjork", 0xDEEDED}
personType := reflect.TypeOf(person)
if nameField, ok := personType.FieldByName("Name"); ok {
    fmt.Printf("%q %q %q\n", nameField.Type, nameField.Name, nameField.Tag)
}
//"string" "Name" "check:len(3,40)"

如果reflect.Value持有的底层值是可以被设置的,那么这个值可以被改变。可设置性,可以通过调用reflect.Value.CanSet()检查确认,它返回一个bool.

presidents := []string{"Obama", "Bushy", "Clinton"}
sliceValue := reflect.ValueOf(presidents)
value = sliceValue.Index(1)
value.SetString("Bush")
fmt.Println(presidents)
//[Obama Bush Clinton]

虽然Go字符串是不能改变的,但[]string中的元素是可以被替换为其它的字符串,如我们例子中所示的那样(正常情况下,可以直接使用presidents[1]="Bush", 而不需要使用到内省)

我们不能必变不可变的值,但如果我们有原始值的地址,我们可以替换一个不可变的值为另外的值。

count := 1
if value = reflect.ValueOf(count); value.CanSet() {
    value.SetInt(2) // Would panic! Can't set an int.
}
fmt.Print(count, " ")
value = reflect.ValueOf(&count)
// Can't call SetInt() on value since value is a *int not an int
pointee := value.Elem()
pointee.SetInt(3) // OK. Can replace a pointed-to value.
fmt.Println(count)
//1 3

这段代码中的if条件会被求值为false. 所以if内的语句不会被执行。虽然我们不可以设置不可变的值,比如int, float64, string, 但我们可以使用reflect.Value.Elem()方法来检索到一个reflect.Value类型的值,在通过这个值,我们可以设置一个指针的值。

还可以使用反射调用任意的函数和方法。以下是一个例子,它两次调用自定义的TitleCase()函数,一次是常规调用,一次是使用反射。

caption := "greg egan's dark integers"
title := TitleCase(caption)
fmt.Println(title)
titleFuncValue := reflect.ValueOf(TitleCase)
values := titleFuncValue.Call([]reflect.Value{reflect.ValueOf(caption)})
title = values[0].String()
fmt.Println(title)
//Greg Egan's Dark Integers
//Greg Egan's Dark Integers

reflect.Value.Call()方法接收和返回一个[]reflect.Value类型的slice.

调用方法也是类似的——在实际中,我们甚至可以查询一个方法是否存在,如果存在,我们才调用。

a := list.New() // a.Len() == 0
b := list.New()
b.PushFront(1) // b.Len() == 1
c := stack.Stack{}
c.Push(0.5)
c.Push(1.5) // c.Len() == 2
d := map[string]int{"A": 1, "B": 2, "C": 3} // len(d) == 3
e := "Four" // len(e) == 4
f := []int{5, 0, 4, 1, 3} // len(f) == 5
fmt.Println(Len(a), Len(b), Len(c), Len(d), Len(e), Len(f))
//0 1 2 3 4 5

在这里我们创建了两个链表(使用container/list), 其中一个,我们向它添加了一个元素,我们同样创建了一个stack(使用了我们在第1章中,定义的stacker/stack包), 并且向它添加了两个元素。我们还创建了一个map,一个字符串,和一个int类型的slice. 然后我们使用了一个泛型的自定义函数Len(),来获得它们的长度。

func Len(x interface{}) int {
    value := reflect.ValueOf(x)
    switch reflect.TypeOf(x).Kind() {
    case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice,
    reflect.String:
        return value.Len()
    default:
        if method := value.MethodByName("Len"); method.IsValid() {
            values := method.Call(nil)
            return int(values[0].Int())
        }
    }
    panic(fmt.Sprintf("'%v' does not have a length", x))
}

这个函数返回一个值的长度,或者抛出panics如果这个值的类型不支持获取长度。

我们首先获得这个值的reflect.Value类型的值,由于我们之后需要使用到它。然后我们在switch中依赖于值的reflect.Kind. 如果这个值的kind是支持len()函数的内置的类型,我们可以直接调用reflect.Value.Len(). 否则类型不支持长度的概念,或者有它自己的Len()方法。我们使用reflect.Value.MethodByName()方法来获取一个方法——或者获取到一个无效的reflect.Value. 如果这个方法是有效的,我们就调用它。在这个例子中,我们没有传递任何参数,这是因为常规的Len()方法都不会接收任何参数。

当我们使用reflect.Value.MethodByName()检索一个方法,它返回一个reflect.Value类型的值,它持有这个方法和这个值。所以当我们调用reflect.Value.Call()时,这个值会被传为receiver传递。

reflect.Value.Int()方法返回一个int64; 我们可以将这个值修改为纯int. 以适应跟泛型的Len()函数返回的类型一样。

如果一个传递的值,它不支持内置的len()函数,也没有Len()方法,这个泛型Len()函数抛出panics. 我们也可以用其它的方式处理错误——比如, 返回 -1 表示"no length available", 或者返回一个int和一个error.

Go的reflect包是非常灵活的,允许我们在运行时依赖于程序的动态状态来处理事情。可是,引用Rob Pike的话, reflection is “a powerful tool that should be used with care and avoided unless strictly necessary”.★