Go的研习笔记-day8(以Java的视角学习Go)

包(package)

标准库概述
像 fmt、os 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。其他包地址

  • unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
  • syscall-os-os/exec:
  • 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 解析器,有关 JSON 和 XML 的实例
  • text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板
  • net-net/http-html
  • net: 网络数据的基本操作。
  • http: 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
  • html: HTML5 解析器。
  • runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
  • reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。

regexp 包

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 包
当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争)
经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。比如Java中的synchronized关键字或者lock接口的实现类等来加锁保证先行发生原则。
在 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),这个方法确保被调用函数只能被调用一次。
是不是发现上面的描述和我们学过的Java挺像,lock接口的实现类读写锁ReadWriteLock。那么如果我们现在说Java中为了减少锁的冲突,采用了并发包之类的原子变量,并发数据结构提升性能,那么go语言有没有类似提升性能的方式呢?答案是可以使用goroutines 和 channels 来解决问题,这是在 Go 语言中所提倡用来实现并发的技术

精密计算和 big 包
java中对于精确数字或者金融计算金额的使用BigDecimal类,那么go语言中呢,也是类似的
对于整数的高精度计算 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)
	in := im
	io := big.NewInt(1956)
	ip := big.NewInt(1)
	ip.Mul(im, in).Add(ip, im).Div(ip, io)
	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
*/

自定义包和可见性
包分为两种:标准库中的包,自定义的包
当写自己包的时候,要使用短小的不含有 _(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。
import 的一般格式如下:
import “包的路径或 URL 地址”
导入外部安装包:
如果你要在你的应用中使用一个或多个外部包,首先你必须使用 go install(参见第 9.7 节)在你的本地机器上安装它们。
假设你想使用 http://codesite.ext/author/goExample/goex 这种托管在 Google Code、GitHub 和 Launchpad 等代码网站上的包。
你可以通过如下命令安装:
go install codesite.ext/author/goExample/goex
将一个名为 codesite.ext/author/goExample/goex 的 map 安装在 $GOROOT/src/ 目录下。
通过以下方式,一次性安装,并导入到你的代码中:
import goex “codesite.ext/author/goExample/goex”
因此该包的 URL 将用作导入路径。
http://golang.org/cmd/goinstall/ 的 go install 文档中列出了一些广泛被使用的托管在网络代码仓库的包的导入路径

  • 包的初始化:
    程序的执行开始于导入包,初始化 main 包然后调用 main 函数。
    一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 init 函数来初始化。一个包可能有多个 init 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。
    init 函数是不能被调用的。
    导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。
  • 使用 go install 安装自定义包
    go install 是 Go 中自动包安装工具:如需要将包安装到本地它会从远端仓库下载包:检出、编译和安装一气呵成。
    在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。
    go install 使用了 GOPATH 变量
    假设现在安装一个包, 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 的子目录下
  • import cgl “tideland-cgl.googlecode.com/hg
    不同版本变化比较大,可参考链接http://golang.org/cmd/go/

为自定义包使用 godoc
godoc工具在显示自定义包中的注释也有很好的效果:注释必须以 // 开始并无空行放在声明(包,类型,函数)前。godoc 会为每个文件生成一系列的网页。
命令行下进入目录下并输入命令:
godoc -http=:6060 -goroot="."
(. 是指当前目录,-goroot 参数可以是 /path/to/my/package1 这样的形式指出 package1 在你源码中的位置或接受用冒号形式分隔的路径,无根目录的路径为相对于当前目录的相对路径)
在浏览器打开地址:http://localhost:6060
然后你会看到本地的 godoc 页面从左到右一次显示出目录中的包:
在一个团队中工作,并且源代码树被存储在网络硬盘上,就可以使用 godoc 给所有团队成员连续文档的支持。通过设置 sync_minutes=n,可以让它每 n 分钟自动更新您的文档!

自定义包的目录结构、go install 和 go test
示范,我们创建了一个名为 uc 的简单包,它含有一个 UpperCase 函数将字符串的所有字母转换为大写。当然这并不值得创建一个自己包,同样的功能已被包含在 strings 包里,但是同样的技术也可以应用在更复杂的包中。

  • 自定义包的目录结构
    (uc 代表通用包名, 名字为粗体的代表目录,斜体代表可执行文件)
/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)
}




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 时测试通过。
备注:有可能你当前的用户不具有足够的资格使用 go install(没有权限)。这种情况下,选择 root 用户 su。确保 Go 环境变量和 Go 源码路径也设置给 su,同样也适用你的普通用户。

本地安装包
本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包:

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 程序中被引用。

依赖系统的代码
在不同的操作系统上运行的程序以不同的代码实现是非常少见的:绝大多数情况下语言和标准库解决了大部分的可移植性问题。
你有一个很好的理由去写平台特定的代码,例如汇编语言。这种情况下,按照下面的约定是合理的:

prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go

prog1.go 定义了不同操作系统通用的接口,并将系统特定的代码写到 prog1_os.go 中。 对于 Go 工具你可以指定 prog1_$ GOOS.go 或 prog1_$ GOARCH.go 或在平台 Makefile 中:prog1_$ (GOOS).go\ 或 prog1_$ (GOARCH).go\。

通过 Git 打包和安装

  • 安装到 GitHub
    在 Linux 和 OS X 的机器上 Git 是默认安装的,在 Windows 上你必须先自行安装。参见GitHub帮助页面
    其余与git托管其他项目一样
  • 从 GitHub 安装
    如果有人想安装您的远端项目到本地机器,打开终端并执行(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 的生态要比这大的多。当着手自己的 Go 项目时,最好先查找下是否有些存在的第三方的包或者项目能不能使用。大多数可以通过 go install 来进行安装。
目前已经有许多非常好的外部库,如:

  • 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)
发布了213 篇原创文章 · 获赞 258 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/wolf_love666/article/details/98949844