Go语言学习笔记(七)------包(package)

一、标准库概述

1.像 fmt 、 os 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。完整列表可以在 Go Walker 查看。

  • unsafe : 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
  • os : 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
  • os/exec : 提供我们运行外部操作系统命令和程序的方式。
  • syscall : 底层的外部包,提供了操作系统底层调用的基本接口。
  • archive/tar 和 /zip-compress :压缩(解压缩)文件功能。
  • fmt - io - bufio - path/filepath - flag :
  • fmt : 提供了格式化输入输出功能。
  • io : 提供了基本输入输出功能,大多数是围绕系统功能的封装。
  • bufio : 缓冲输入输出功能的封装。
  • path/filepath : 用来操作在当前系统中的目标文件名路径。
  • flag : 对命令行参数的操作。
  • strings - strconv - unicode - regexp - bytes :
  • strings : 提供对字符串的操作。
  • strconv : 提供将字符串转换为基础类型的功能。
  • unicode : 为 unicode 型的字符串提供特殊的功能。
  • regexp : 正则表达式功能。
  • bytes : 提供对字符型分片的操作。
  • index/suffixarray : 子字符串快速查询。
  • math - math/cmath - math/big - math/rand - sort :
  • math : 基本的数学函数。
  • math/cmath : 对复数的操作。
  • math/rand : 伪随机数生成。
  • sort : 为数组排序和自定义集合。
  • math/big : 大数的实现和计算。
  • container - /list-ring-heap : 实现对集合的操作。
  • list : 双链表。
  • ring : 环形链表。

下面代码演示了如何遍历一个链表(当 l 是 *List ):

for e := l.Front(); e != nil; e = e.Next() {

//do something with e.Value

}
  • time - log :
  • time : 日期和时间的基本操作。
  • log : 记录程序运行时产生的日志,我们将在后面的章节使用它。
  • encoding/json - encoding/xml - text/template :
  • encoding/json : 读取并解码和写入并编码 JSON 数据。
  • encoding/xml :简单的 XML1.0 解析器。
  • text/template :生成像 HTML 一样的数据与文本混合的数据驱动模板。
  • net - net/http - html :
  • net : 网络数据的基本操作。
  • http : 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
  • html : HTML5 解析器。
  • runtime : Go 程序运行时的交互操作,例如垃圾回收和协程创建。
  • reflect : 实现通过程序运行时反射,让程序操作任意类型的变量。
  • exp 包中有许多将被编译为新包的实验性的包。它们将成为独立的包在下次稳定版本发布的时候。如果前一个版本已经存在了,它们将被作为过时的包被回收。然而 Go1.0 发布的时候并不包含过时或者实验性的包。

二、regexp 包

先将正则通过 Compile 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。在字符串中对正则表达式进行匹配。如果是简单模式,使用 Match 方法便可:ok, _ := regexp.Match(pat, []byte(searchIn)),变量 ok 将返回 true 或者 false,我们也可以使用 MatchString :ok, _ := regexp.MatchString(pat, searchIn),另外我们也可以使用 MustCompile 方法,它可以像 Compile 方法一样检验正则的有效性,但是当正则不合法时程序将 panic。

package main
import (
	"fmt"
	"regexp"
	"strconv"
)
func main() {
	//目标字符串
	searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18"
	pat := "[0-9]+.[0-9]+" //正则
	f := func(s string) string{
		v, _ := strconv.ParseFloat(s, 32)
		return strconv.FormatFloat(v * 2, 'f', 2, 32)
	}
	if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
		fmt.Println("Match Found!")
	}
	re, _ := regexp.Compile(pat)
	//将匹配到的部分替换为"##.#"
	str := re.ReplaceAllString(searchIn, "##.#")
	fmt.Println(str)
	//参数为函数时
	str2 := re.ReplaceAllStringFunc(searchIn, f)
	fmt.Println(str2)
}

三、锁和 sync 包

通常通过不同线程执行不同应用来实现程序的并发。当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争)。经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。map 类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 map 类型的数据,map 数据将会出错。Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。
sync.Mutex 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 Mutex 来实现的一个典型例子如下:

import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}

如果一个函数想要改变这个变量可以这样写:

func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}

有一个很有用的例子是通过 Mutex 来实现一个可以上锁的共享缓冲器:

type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}

在 sync 包中还有一个 RWMutex 锁:他能通过 RLock() 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 Lock() 将和普通的 Mutex 作用相同。包中还有一个方便的 Once 类型变量的方法once.Do(call) ,这个方法确保被调用函数只能被调用一次。相对简单的情况下,通过使用 sync 包可以解决同一时间只能一个线程访问变量或 map 类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题,这是在Go 语言中所提倡用来实现并发的技术。

四、精密计算和 big 包

对于整数的高精度计算 Go 语言中提供了 big 包。其中包含了 math 包:有用来表示大整数的 big.Int 和表示大有理数的 big.Rat 类型(可以表示为 2/5 或 3.1416 这样的分数,而不是无理数或 π)。这些类型可以实现任意位类型的数字,只要内存足够大。缺点是更大的内存和处理开销使它们使用起来要比内置的数字类型慢很多。大的整型数字是通过 big.NewInt(n) 来构造的,其中 n 为 int64 类型整数。而大有理数是用过 big.NewRat(N,D) 方法构造。N(分子)和 D(分母)都是 int64 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是Add() 和 Mul() 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver作为返回结果。因为没有必要创建 big.Int 类型的临时变量来存放中间结果,所以这样的运算可通过内存链式存储。

// big.go
package main
import (
"fmt"
"math"
"math/big"
)
func main() {
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64) //获得最大值9223372036854775807
in := im
io := big.NewInt(1956) 
ip := big.NewInt(1)
ip.Mul(im, in).Add(ip, im).Div(ip, io) //im*in==>ip==>ip+im==>ip==>ip/io==>ip
fmt.Printf("Big Int: %v\n", ip)
// Here are some calculations with bigInts:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)
}
/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/

六、自定义包和可见性

1.当前目录下有一个名为 package_test.go 的程序, 它使用了自定义包 pack1 中 pack1.go 的代码。这段程序(连同编译链接生成的 pack1.a)存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。

//pack1.go位于./pack1/pack1.go
//包含了一个整型变量 Pack1Int 和一个返回字符串的函数 ReturnStr 。这段程序在运行时不做任何的事情,因为它不包含有一个 main 函数。
package pack1
var Pack1Int int = 42
var PackFloat = 3.14
func ReturnStr() string {
return "Hello main!"
}

2.主程序利用包必须在主程序编写之前被编译。主程序中每个 pack1 项目都要通过包名来使用: pack1.Item 。在主程序 package_test.go 中这个包通过声明的方式被导入import "./pack1/pack1",import 的一般格式如下:import "包的路径或 URL 地址",例如:import "github.com/org1/pack1”,路径是指当前目录的相对路径。

package main
import (
"fmt"
"./pack1/pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf("Integer from package1: %d\n", pack1.Pack1Int)
// fmt.Printf("Float from package1: %f\n", pack1.pack1Float)
}

输出结果:

ReturnStr from package1: Hello main!
Integer from package1: 42

3.import . "./pack1",当使用 . 来做为包的别名时,你可以不通过包名来使用其中的项目。例如: test := ReturnStr() 。在当前的命名空间导入 pack1 包,一般是为了具有更好的测试效果。Import with _ :import _ "./pack1/pack1",pack1包只导入其副作用,也就是说,只执行它的init函数并初始化其中的全局变量。

4.如果你要在你的应用中使用一个或多个外部包,首先你必须使用 go install 在你的本地机器上安装它们。http://golang.org/cmd/goinstall/ 的 go install 文档中列出了一些广泛被使用的托管在网络代码仓库的包的导入路径。

5.包的初始化:程序的执行开始于导入包,初始化 main 包然后调用 main 函数。一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 init 函数来初始化。一个包可能有多个init 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。init 函数是不能被调用的。导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。

6.编译并安装一个包:在 Linux/OS X 下可以用类似 Makefile 脚本做到这一点:

include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
pack1.go\
pack1b.go\
include $(GOROOT)/src/Make.pkg

通过 chmod 777 ./Makefile 确保它的可执行性。上面脚本内的include语引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。然后终端执行 make 或 gomake 工具:他们都会生成一个包含静态库 pack1.a 的 _obj 目录。go install同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 import "pack1" 代替 import "path to pack1" ,这样只通过名字就可以将包在程序中导入。

六、为自定义包使用 godoc

godoc工具在显示自定义包中的注释也有很好的效果:注释必须以 // 开始并无空行放在声明(包,类型,函数)前。godoc 会为每个文件生成一系列的网页。例如:用来排序的 go 文件,文件中有一些注释(文件需要未编译)命令行下进入目录下并输入命令:
godoc -http=:6060 -goroot="."
( . 是指当前目录,-goroot 参数可以是 /path/to/my/package1 这样的形式指出 package1 在你源码中的位置或接受用
冒号形式分隔的路径,无根目录的路径为相对于当前目录的相对路径),在浏览器打开地址:http://localhost:6060,然后你会看到本地的 godoc 页面从左到右一次显示出目录中的包。

七、使用 go install 安装自定义包

1.go install 是 Go 中自动包安装工具:如需要将包安装到本地它会从远端仓库下载包:检出、编译和安装一气呵成。在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。go install 使用了 GOPATH 变量。
2.远端包:假设我们要安装一个有趣的包 tideland(它包含了许多帮助示例,参见 项目主页)。因为我们需要创建目录在 Go 安装目录下,所以我们需要使用 root 或者 su 的身份执行命令。确保 Go 环境变量已经设置在 root 用户下的 ./bashrc 文件中。使用命令安装: go install tideland-cgl.googlecode.com/hg 。可执行文件 hg.a 将被放到 $GOROOT/pkg/linux_amd64/tideland-cgl.googlecode.com 目录下,源码文件被放置在$GOROOT/src/tideland-cgl.googlecode.com/hg 目录下,同样有个 hg.a 放置在 _obj 的子目录下。现在就可以在 go 代码中使用这个包中的功能了,例如使用包名 cgl 导入。

import cgl "tideland-cgl.googlecode.com/hg"

3.如果你想更新,重编译、重安装所有的go安装包可以使用: go install -a 。

八、自定义包的目录结构、go install 和 go test

1.创建了一个名为 uc 的简单包,它含有一个 UpperCase 函数将字符串的所有字母转换为大写。下面的结构给了你一个好的示范:

/home/user/goprograms
   ucmain.go (uc包主程序)
   Makefile (ucmain的makefile)
   ucmain
   src/uc (包含uc包的go源码)
      uc.go
      uc_test.go
      Makefile (包的makefile)
      uc.a
      _obj
         uc.a
      _test
         uc.a
   bin (包含最终的执行文件)
      ucmain
   pkg/linux_amd64
      uc.a (包的目标文件)

将你的项目放在 goprograms 目录下(你可以创建一个环境变量 GOPATH:在 .profile 和 .bashrc 文件中添加 export GOPATH=/home/user/goprograms ),而你的项目将作为 src 的子目录。uc 包中的功能在 uc.go 中实现。

package uc
import "strings"
func UpperCase(str string) string {
return strings.ToUpper(str)
}

包通常附带一个或多个测试文件,在这我们创建了一个 uc_test.go 文件。

package uc
import "testing"
type ucTest struct {
in, out string
}
var ucTests = []ucTest {
ucTest{"abc", "ABC"},
ucTest{"cvo-az", "CVO-AZ"},
ucTest{"Antwerp", "ANTWERP"},
}
func TestUC(t *testing.T) {
for _, ut := range ucTests {
uc := UpperCase(ut.in)
if uc != ut.out {
t.Errorf("UpperCase(%s) = %s, must be %s", ut.in, uc,
ut.out)
}
}
}

通过指令编译并安装包到本地: go install uc , 这会将 uc.a 复制到 pkg/linux_amd64 下面。另外,使用 make ,通过以下内容创建一个包的 Makefile 在 src/uc 目录下:

include $(GOROOT)/src/Make.inc
TARG=uc
GOFILES=\
   uc.go\
include $(GOROOT)/src/Make.pkg

在该目录下的命令行调用: gomake,这将创建一个 _obj 目录并将包编译生成的存档 uc.a 放在该目录下。这个包可以通过 go test 测试,创建一个 uc.a 的测试文件在目录下,输出为 PASS 时测试通过。接下来我们创建主程序 ucmain.go:

package main
import (
"./uc/uc"
"fmt"
)
func main() {
str1 := "USING package uc!"
fmt.Println(uc.UpperCase(str1))
}

然后在这个目录下输入 go install 。另外复制 uc.a 到 /home/user/goprograms 目录并创建一个 Makefile 并写入文本:

include $(GOROOT)/src/Make.inc
TARG=ucmain
GOFILES=\
   ucmain.go\
include $(GOROOT)/src/Make.cmd

执行 gomake 编译 ucmain.go 生成可执行文件ucmain,运行 ./ucmain 显示: USING PACKAGE UC! 。
2.本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包:

go install /home/user/goprograms/src/uc # 编译安装uc
cd /home/user/goprograms/uc
go install ./uc # 编译安装uc(和之前的指令一样)
cd ..
go install . # 编译安装ucmain

安装到 $GOPATH 下:
如果我们想安装的包在系统上的其他 Go 程序中被使用,它一定要安装到 $GOPATH 下。 这样做,在 .profile 和 .bashrc中设置 export GOPATH=/home/user/goprograms 。然后执行 go install uc 将会复制包存档到 $GOPATH/pkg/LINUX_AMD64/uc 。现在,uc 包可以通过 import "uc" 在任何 Go 程序中被引用。

3.依赖系统的代码:在不同的操作系统上运行的程序以不同的代码实现是非常少见的,绝大多数情况下语言和标准库解决了大部分的可移植性问题。你有一个很好的理由去写平台特定的代码,例如汇编语言。这种情况下,按照下面的约定是合理的:
prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go
prog1.go 定义了不同操作系统通用的接口,并将系统特定的代码写到 prog1os.go 中。 对于 Go 工具你可以指定`prog1$GOOS.go 或 prog1$GOARCH.go 或在平台 Makefile 中: prog1$(GOOS).go` 或 prog1_$(GOARCH).go\ 。

九、通过 Git 打包和安装

1.上传包至git:uc 包创建一个 git 仓库作为演示,进入到 uc 包目录下并创建一个 Git 仓库在里面: git init ,信息提示: Initialized empty git repository in $PWD/uc 。每一个 Git 项目都需要一个对包进行描述的 README.md 文件,所以需要打开你的文本编辑器并添加一些说明进去。添加所有文件到仓库: git add README.md uc.go uc_test.go Makefile 。标记为第一个版本: git commit -m "initial rivision" 。在云端创建一个新的 uc 仓库;发布的指令为( NNNN 替代用户名):git remote add origin git@github.com:NNNN/uc.git、git push -u origin master,操作完成后检查 GitHub 上的包页面: http://github.com/NNNN/uc 。

2.本地安装:打开终端并执行(NNNN 是你在 GitHub 上的用户名): go get github.com/NNNN/uc 。这样现在这台机器上的其他 Go 应用程序也可以通过导入路径: "github.com/NNNN/uc" 代替 "./uc/uc" 来使用。也可以将其缩写为: import uc "github.com/NNNN/uc" 。然后修改 Makefile: 将 TARG=uc 替换为 TARG=github.com/NNNN/uc 。Gomake(和 go install)将通过 $GOPATH 下的本地版本进行工作。

十、Go 的外部包和项目

着手自己的 Go 项目时,最好先查找下是否有些存在的第三方的包或者项目能不能使用。大多数可以通过 go install 来进行安装。Go Walker 支持根据包名在海量数据中查询。目前已经有许多非常好的外部库,如:
MySQL(GoMySQL), PostgreSQL(go-pgsql), MongoDB (mgo, gomongo), CouchDB (couch-go), ODBC (godbcl), Redis
(redis.go) and SQLite3 (gosqlite) database drivers
SDL bindings
Google's Protocal Buffers(goprotobuf)
XML-RPC(go-xmlrpc)
Twitter(twitterstream)
OAuth libraries(GoAuth)

十一、在 Go 程序中使用外部库

1.应用程序中使用已经存在的库来节省开发时间。为了做到这一点,你必须理解库的 API(应用编程接口),那就是:库中有哪些方法可以调用,如何调用。你可能没有这个库的源代码,但作者肯定有记载的 API 以及详细介绍了如何使用它。谷歌 urlshortener 服务的文档可以在 "http://code.google.com/apis/urlshortener/"找到,谷歌将这项技术提供给其他开发者,作为 API 我们可以在我们自己的应用程序中调用(释放到指定的限制)。他们也生成了一个 Go 语言客户端库使其变得更容易。
2.下载并安装 Go 客户端库: 将通过 go install 实现。但是首先要验证环境变量中是否含有 GOPATH 变量,因为外部源码将被下载到 $GOPATH/src 目录下并被安装到 $GOPATH/PKG/"machine_arch"/ 目录下。我们将通过在终端调用以下命令来安装 API:
go install google.golang.org/api/urlshortener/v1
go install 将下载源码,编译并安装包,使用 urlshortener 服务的 web 程序: 现在我们可以通过导入并赋予别名来使用已安装的包:
import "google.golang.org/api/urlshortener/v1"
3.现在我们写一个 Web 应用,通过表单实现短地址和长地址的相互转换。我们将使用 template 包并写三个处理函数:root 函数通过执行表单模板来展示表单。short 函数将长地址转换为短地址,long 函数逆向转换。要调用 urlshortener 接口必须先通过 http 包中的默认客户端创建一个服务实例 urlshortenerSvc:
urlshortenerSvc, _ := urlshortener.New(http.DefaultClient)
我们通过调用服务中的 Url.Insert 中的 Do 方法传入包含长地址的 Url 数据结构从而获取短地址:
url, _ := urlshortenerSvc.Url.Insert(&urlshortener.Url{LongUrl: longUrl}).Do()
返回 url 的 Id 便是我们需要的短地址。
我们通过调用服务中的 Url.Get 中的 Do 方法传入包含短地址的Url数据结构从而获取长地址:
url, error := urlshortenerSvc.Url.Get(shwortUrl).Do()
返回的长地址便是转换前的原始地址。

package main
import (
"fmt"
"net/http"
"text/template"
"google.golang.org/api/urlshortener/v1"
)
func main() {
http.HandleFunc("/", root)
http.HandleFunc("/short", short)
http.HandleFunc("/long", long)
http.ListenAndServe("localhost:8080", nil)
}
// the template used to show the forms and the results web page to the user
var rootHtmlTmpl = template.Must(template.New("rootHtml").Parse(`
<html><body>
<h1>URL SHORTENER</h1>
{{if .}}{{.}}<br /><br />{{end}}
<form action="/short" type="POST">
Shorten this: <input type="text" name="longUrl" />
<input type="submit" value="Give me the short URL" />
</form>
<br />
<form action="/long" type="POST">
Expand this: http://goo.gl/<input type="text" name="shortUrl" />
<input type="submit" value="Give me the long URL" />
</form>
</body></html>

执行这段代码:
go run urlshortener.go
通过浏览 http://localhost:8080/ 的页面来测试。
为了代码的简洁我们并没有检测返回的错误状态,但是在真实的生产环境的应用中一定要做检测。将应用放入 Google App Engine,我们只需要在之前的代码中作出如下改变:
package main -> package urlshort
func main() -> func init()
创建一个和包同名的目录 urlshort ,并将以下两个安装目录复制到这个目录:
google.golang.org/api/urlshortener
google.golang.org/api/googleapi
此外还要配置下配置文件 app.yaml ,内容如下:
application: urlshort
version: 0-1-test
runtime: go
api_version: 3
handlers:
- url: /.*
script: _go_app
现在你可以去到你的项目目录并在终端运行: dev_appserver.py urlshort
在浏览器打开你的 Web应用:http://localhost:8080

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值