1.包#

在go中包是一个代码的组织方式,代码最后的分发都是以包的方式分发的。包也是go中最小的分发单位。包也是函数和数据的集合,将相关特性的函数和数据放在同一的文件/目录进行管理,每个包都可以作为独立的单元维护并提供给其他项目进行使用。通常,包中存放的就是变量,函数,产量,结构体等等。

go源文件都需要在开头使用package声明所在包,包名告知编译器那些是包的源代码用于编译库文件,其次包名用于限制包内成员对外的可见性,最后包用于在包外对公开成员的访问。

包名使用简短的小写字母,常与所在目录名保持一致,一个包中可以由多个Go源文件组成,通常,使用相同包名。

我们可以使用go env查看go环境中的gopath

[root@linuxea.com /opt/Golang]# go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/root/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build982029746=/tmp/go-build -gno-record-gcc-switches"

在windows上也可也使用go env

2.包导入-path#

在gopath目录中查找包。如:

import (
    "fmt"
    "gpkgname/pkg01"
)

在当前文件所在目录查找,如:

import "./gpkgname/pkg02"

在调用点导入包中的成员时可以直接使用成员名称进行调用(省略包名)

import . "fmt"

. 简导入,在导入前加入点,这种并不推荐使用

当导入不同路径的相同包名时,可以别名导入为包重命名,避免冲突

import ff "fmt"

fmt在调用的时候就成了ff

go不允许包导入后不使用,在某些情况下需要初始化包,使用空白符作为别名进行导入,从而使得包中的初始化函数可以执行。

_ 空白标识符通常和初始化函数init一起使用,在init函数中,不管是以什么方式导入,都会执行包的初始化函数。空白标识符仅仅用作调用初始化函数。

3.包的成员可见性#

go语言使用名称首字母大小写来判断对象(常量,变量,函数,类型,结构体,方法等)的访问权限,首字母大写标识包外可见(公开的),否则仅包内可访问(内部的)。比如变量:

var Name string // 包外可见
var NAME string // 包外可见
var NaMe string // 包外可见

var nAme string // 包外不可见
var naME string // 包外不可见
var nAME string // 包外不可见

其他常量,变量,函数,类型,结构体,方法也是需要首字母大写才能被包外可见。

4.main包和init函数#

main包用于声明告知编译器将包编译为二进制可执行文件。在main包中的main函数是程序的入口,无返回值,无参数。在一个main包中只有一个main函数

init函数是初始化包使用,无返回值,无参数,每个包中建议只定义一个,init函数在import包时自动被调用

如果有函数,通常init会先执行

[root@linuxea.com /opt/Golang/work4]# cat init.go
package main
import "fmt"
func main(){
    fmt.Println("main")
}
func init(){
    fmt.Println("init")
}

运行

[root@linuxea.com /opt/Golang/work4]# go run init.go
init
main

5.go包管理-vendor#

在1.5之前只有gopath方式,需要将代码放置gopath目录,而vendor机制是将项目依赖包拷贝到到vendor目录,在编译时使用项目下的vendor目录中的包进行编译

这种方式解决 了依赖外包包过多的问题,而且通过go get下载的库会放在第一个目录中,并且在多项目中,如果不向前兼容就容易造成复杂的库依赖等问题,同时系统的目录就会越来越大。

通常包搜索会在当前包下的vendor目录查找,向上级目录查找,直到GOPATH/src/vendor目录,而后在GOPATH目录查找,最后在GOROOT目录查找

第三方包,可以通过go get工具下载和安装第三方包以及依赖,需要安装与第三方包匹配的代码管理工具,比如git,svn等。如果二进制的包会放在bin目录,如果是库文件会放在pkg目录下。

go get会判断本地是否有,如果有就不进行下载,我们需要配合参数进行使用。

常用参数:

可以通过go help get查看帮助信息。

6.go包管理-modules机制#

相对于上面的gopath,modules不用设置GOPATH,代码可以放在任何位置,当你运行的时候会自动下载依赖管理。并且可以做版本控制,而在go get的时候默认是使用最新的版本,但是在go modules中,可以指定版本号。

在go module中也不允许相对导入。也可以使用replace来做一些错误修复。

go modules机制的包管理,同时保留GOPATH和Vendor机制,使用临时的环境变量GO111MODULE进行控制,GO111MODULE有三个可选值

a). 当off时,构建项目是在GOPATH和Vendor目录搜索目标程序依赖包

b). 当on时,构建项目则始终使用Go modules机制,在GOPATH/pkg/mod目录搜索目标程序依赖包

c). 当auto时,默认为auto,当构建代码不再GOPATH/src的子目录且包含go.mod文件,则使用Go.modules机制,否则使用GOPATH和vendor机制。

6.1go module初始化#

运行go mod init modname,比如初始化test

假如初始化的是一个github项目地址,就需要写完整,如:

go mod init github.com/marksugar/gotest

[root@linuxea.com /opt/Golang/work4]# mkdir test
[root@linuxea.com /opt/Golang/work4]# go mod init test
go: creating new go.mod: module test
[root@linuxea.com /opt/Golang/work4]# ll 
total 8
-rw------- 1 root root 21 Sep 29 21:46 go.mod
-rw-r--r-- 1 root root 98 Sep 29 19:02 init.go
drwxr-xr-x 2 root root  6 Sep 29 21:45 test

执行完成后,就会产生一个go.mod。在这个文件中会有go的版本信息和模块名称

[root@linuxea.com /opt/Golang/work4]# cat go.mod 
module test

go 1.12

6.2go buld#

当执行go buid的时候会自动下载。

比如写一段简单的代码,使用beego

代码要放在模块中,目录结构如下:

``` [root@linuxea.com /opt/Golang/work4]# tree ./ ./ ├── go.mod ├── go.sum ├── main.go └── test

0 directories, 4 files

```

[root@linuxea.com /opt/Golang/work4]# cat main.go 
package main
import (
    "github.com/astaxie/beego"
)
func main(){
    beego.Run()
}

而后使用go build就不会自动下载依赖

[root@linuxea.com /opt/Golang/work4]# go build
go: finding github.com/astaxie/beego v1.12.0
go: downloading github.com/astaxie/beego v1.12.0
go: extracting github.com/astaxie/beego v1.12.0
go: finding github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76
go: finding github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
go: finding github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c
go: finding github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712
go: finding github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb
go: finding github.com/pelletier/go-toml v1.2.0
go: finding github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
go: finding github.com/lib/pq v1.0.0
go: finding github.com/elazarl/go-bindata-assetfs v1.0.0
go: finding github.com/casbin/casbin v1.7.0
go: finding github.com/go-redis/redis v6.14.2+incompatible
go: finding github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
go: finding github.com/mattn/go-sqlite3 v1.10.0
go: finding github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a
go: finding github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
go: finding github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
go: finding github.com/gogo/protobuf v1.1.1
go: finding golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
go: finding github.com/Knetic/govaluate v3.0.0+incompatible
go: finding github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
go: finding github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c
go: finding github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d
go: finding gopkg.in/yaml.v2 v2.2.1
go: finding github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b
go: finding github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
go: finding golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: finding github.com/pkg/errors v0.8.0
go: finding github.com/go-sql-driver/mysql v1.4.1
go: finding github.com/OwnLocal/goes v1.0.0
go: finding github.com/gomodule/redigo v2.0.0+incompatible
go: finding github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
go: github.com/ssdb/gossdb@v0.0.0-20180723034631-88f6b59b84ec: git fetch -f origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /root/go/pkg/mod/cache/vcs/3914d3fd5d3f23eecdc3b9eac5ee362289232a8a30aa02518ada098000020213: exit status 128:
    fatal: unable to access 'https://github.com/ssdb/gossdb/': Could not resolve host: github.com; Name or service not known
go: error loading module requirements

重新build即可

go build

我们使用replace处理这个错误

-replace=golang.org/x/crypto=被替换github.com/golang/crypto@latest

go  mod edit -replace=golang.org/x/crypto=github.com/golang/crypto@latest

这样修改后,在go.mod中就会加入一条replace指令

[root@linuxea.com /opt/Golang/work4]# cat go.mod 
module test

go 1.12

require (
    github.com/astaxie/beego v1.12.0 // indirect
    github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

replace golang.org/x/crypto => github.com/golang/crypto latest

6.3第三方包#

在使用go mod tidy,go build,go test,go list命令会自动将第三方依赖包写入到go.mod文件中同时下载第三方依赖包到GOPATH/pkg/mod/cache目录,并在当前模块目录生成一个构建状态跟踪文件go.sum,文件中积记录当前module所有的项目和间接依赖,以及这些依赖的校验和

go.mod依赖包

require (
    github.com/astaxie/beego v1.12.0 // indirect
    github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

go.sum的签名

[root@linuxea.com /opt/Golang/work4]# cat go.sum 
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y=
github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

-require=package@version

-replace

=old_package@version=new_package@version

可以使用-replace功能将包替换为本地包,实现相对导入

6.4导入本地库#

我们创建一个测试的本地库,如:testmod

[root@linuxea.com /opt/Golang/inTest]# go mod init testmod
go: creating new go.mod: module testmod

创建完成会产生一个go.mod

[root@linuxea.com /opt/Golang/inTest]# cat go.mod 
module testmod

go 1.12

而后创建一个文件夹

[root@linuxea.com /opt/Golang/inTest]# mkdir info

在文件下面创建一个go文件,声明一个全局变量,假设是Name值守mark

[root@linuxea.com /opt/Golang/inTest]# cat info/desc.go 
package info
var Name = "mark"

而后和info的平级目录下创建main.go

这里调用desc.go中的Name全局变量

注意:info下的desc.go中的package 是上级目录名

package main
import (
    "testmod/info"
    "fmt"
)
func main(){
    fmt.Println(info.Name)
}

运行

[root@linuxea.com /opt/Golang/inTest]# go run main.go 
mark
[root@linuxea.com /opt/Golang/inTest]# tree ./
./
├── go.mod
├── info
│   └── desc.go
└── main.go

1 directory, 3 files

7.单元测试#

一般而言,我们通过包中的函数调用来做一些测试,比如正常的调用代码,如果错误然后追寻代码位置和行数来判断,如下:

假设现在创建一个目录codetest,而后放一段代码,如,add函数,做一个加法运算,如下

[root@linuxea.com /opt/Golang/inTest]# cat codetest/calc.go 
package codetest

func Add(a,b int) int {
    return a + b
}

而后在main函数中调用

[root@linuxea.com /opt/Golang/inTest]# cat main.go 
package main
import (
    "testmod/info"
    "testmod/codetest"
    "fmt"
)
func main(){
    fmt.Println(info.Name)

    a := codetest.Add(1,2)
    fmt.Println(a)

}

运行

[root@linuxea.com /opt/Golang/inTest]# go run main.go 
mark
3

但是这种方式并不可靠。我们可以使用go的单元测试。

当我们对代码进行重新修改后,我们有必要对一些模块进行单元测试以确保代码运行正常。我们做一个简单的示例。测试上面Add的函数

7.1单元测试#

还是如上所示,我们仍然测试ADD函数,加入go的测试风格。测试函数需要导入testing包,我们在calc的目录下创建一个go文件,文件中函数名称都是以Test开头,传入一个参数t,是*testing.T指针类型,在测试函数中调用函数进行返回值测试,当测试失败可通过testing.T结构体的Error*函数抛出错误

func TestAdd(t *testing.T){}

而后直接调用Add函数,如下:

func TestAdd(t *testing.T){
    if 4 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

如上所示,调用Add(1,2)的结果如果不等于4,就抛出错误

代码块

[root@linuxea.com /opt/Golang/inTest/codetest]# cat calc_test.go 
package codetest

import "testing"

func TestAdd(t *testing.T){
    if 4 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

而后我们运行go test testmod/codetest

go test testmod/codetest中的testmod是modules中init时候的名字,codetest是当前目录下的包名

[root@linuxea.com /opt/Golang/inTest/codetest]# go test testmod/codetest
--- FAIL: TestAdd (0.00s)
    calc_test.go:7: 1加2不等于3
FAIL
FAIL    testmod/codetest    0.003s

我们修改成正确的看看。

把条件判断改成3。很明显,1+2,是等于3的

package codetest

import "testing"

func TestAdd(t *testing.T){
    if 3 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

运行

[root@linuxea.com /opt/Golang/inTest/codetest]# go test testmod/codetest
ok      testmod/codetest    (cached)

7.2测试覆盖率#

我们在添加一个函数在calc.go

# cat calc.go 
package codetest

func Add(x,y int) int {
    if x >  0{
        return x + y
    }else {
        return x
    }
}
func Multi(x,y int) int {
    return x * y 
}

测试文件calc_test.go

package codetest

import "testing"

func TestAdd(t *testing.T){
    if 3 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

运行

[root@linuxea.com /opt/Golang/inTest/codetest]# go test testmod/codetest -coverprofile=calc.out
ok      testmod/codetest    0.002s  coverage: 50.0% of statements

这时候会产生一个calc.out

文件列表:

[root@linuxea.com /opt/Golang/inTest/codetest]# ll total 12 -rw-r--r-- 1 root root 137 Oct 2 10:04 calc.go -rw-r--r-- 1 root root 164 Oct 2 10:04 calc.out -rw-r--r-- 1 root root 119 Oct 2 10:04 calc_test.go

7.3查看cover文件#

使用go tool cover -html calc.out

[root@linuxea.com /opt/Golang/inTest/codetest]# go tool cover -html calc.out 
HTML output written to /tmp/cover001206950/coverage.html

这时候生成了一个html的文件存放在/tmp/cover001206950/coverage.html

20191002

红色是未覆盖的测试

7.4多条件测试覆盖率#

上述中,只覆盖率判断了x大于0的代码,在覆盖测试else这块,如下:

func TestAdd2(t *testing.T){
    if -1 != Add(-1,3){
        t.Errorf("-1")
    }
}

代码块

package codetest

import "testing"

func TestAdd(t *testing.T){
    if 3 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

func TestAdd2(t *testing.T){
    if -1 != Add(-1,3){
        t.Errorf("-1")
    }
}

运行

[root@linuxea.com /opt/Golang/inTest/codetest]#  go test testmod/codetest -coverprofile=calc.out
ok      testmod/codetest    0.002s  coverage: 75.0% of statements

转换html文件

[root@linuxea.com /opt/Golang/inTest/codetest]# go tool cover -html calc.out 
HTML output written to /tmp/cover693930635/coverage.html

如下:

20191002-1

两个if分支都被测试到,所以变成了绿色。

8.基准测试#

基准测试通常用来测试性能,函数使用Beanchmark开头,参数也是*testing.B

创建一个函数名称为Fact的阶层函数

func Fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * Fact(n-1)
}

代码块

package codetest

func Add(x,y int) int {
    if x >  0{
        return x + y
    }else {
        return x
    }
}
func Multi(x,y int) int {
    return x * y 
}

func Fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * Fact(n-1)
}

calc_test.go中基准测试写法如下:

func BeanchmarkFact(b *testing.B) {
    for i :=0;i <=20000;i++{
        Fact(i)
    }
}

代码块

package codetest

import "testing"

func TestAdd(t *testing.T){
    if 3 != Add(1,2){
        t.Errorf("1加2不等于3")
    }
}

func TestAdd2(t *testing.T){
    if -1 != Add(-1,3){
        t.Errorf("-1")
    }
}


func BeanchmarkFact(b *testing.B) {
    for i :=0;i <=20000 ;i++{
        Fact(i)
    }
}

在执行go test的时候 要加-bench参数

[root@linuxea.com /opt/Golang/inTest/codetest]#  go test testmod/codetest  -bench .  
goos: linux
goarch: amd64
pkg: testmod/codetest
BenchmarkFact-2            1    1376384478 ns/op
PASS
ok      testmod/codetest    1.381s

可以加benchmem查看内存使用

[root@linuxea.com /opt/Golang/inTest/codetest]#  go test testmod/codetest  -bench .  -benchmem
goos: linux
goarch: amd64
pkg: testmod/codetest
BenchmarkFact-2            1    1399430216 ns/op           0 B/op          0 allocs/op
PASS
ok      testmod/codetest    1.403s

9.os.args模块#

os.args是在os模块中,我们可以简单打印os.args,而后传递 一些参数

package main
import (
    "os"
    "fmt"
)

func main(){
    fmt.Println(os.Args)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run args.go www.linuxea.com
[/tmp/go-build909504842/b001/exe/args www.linuxea.com]

打印的结果是,文件的目录和传入的参数

也可以进行build

[root@linuxea.com /opt/Golang/work4/stdpkg]# go build args.go 
[root@linuxea.com /opt/Golang/work4/stdpkg]# chmod +x args

如下:

[root@linuxea.com /opt/Golang/work4/stdpkg]# ./args www.linuxea.com
[./args www.linuxea.com]

命令后传递参数可以看flag

10.flag模块绑定变量#

我们定义一个flag的参数传递

首先定义个变量

var port int

而后使用flag.IntVar()绑定命令行参数,这里需要传递指针,参数,默认值,备注。如下:

flag.IntVar(&port,"P",88,"nginx port")

解析命令行参数

flag.Parse()

而后打印

fmt.Println(port)

在如法炮制一个host的string和verbor的布尔类型参数,如下

package main

import (
    "flag"
    "fmt"
)
func main(){
    var port int
    var host string
    var verbor bool
    flag.IntVar(&port,"P",88,"nginx port")
    flag.StringVar(&host,"H","127.0.0.1","-host addr")
    flag.BoolVar(&verbor,"v",false,"detail log")

    flag.Parse()
    fmt.Println(host,port,verbor)
}

运行默认的参数就是127.0.0.1 88

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -P 66 -H 10.0.0.96
10.0.0.96 66 false

我们可以传递参数,-P-H-P是int类型,-H是string类型,-V是bool类型,如果输入错误就会报错,并且打出使用信息

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -P st -H 10.0.0.96 -v
invalid value "st" for flag -P: parse error
Usage of /tmp/go-build440964329/b001/exe/flag:
  -H string
        -host addr (default "127.0.0.1")
  -P int
        nginx port (default 88)
  -v    detail log
exit status 2

使用-P-H传递参数,如果不加-v默认就是false

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -P 66 -H 10.0.0.96 
10.0.0.96 66 false

加上-V就变成了true

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -P 66 -H 10.0.0.96 -v
10.0.0.96 66 true

10.1--help#

利用boolvar编写help帮助

声明一个Boolvar的help变量

var help bool
flag.BoolVar(&help,"help",false,"help")
func main(){
    var port int
    var host string
    var verbor bool
    var help bool
    flag.IntVar(&port,"P",88,"nginx port")
    flag.StringVar(&host,"H","127.0.0.1","-host addr")
    flag.BoolVar(&verbor,"v",false,"detail log")
    flag.BoolVar(&help,"help",false,"help")

    flag.Parse()

    fmt.Println(host,port,verbor)
}

而后判断help如果为true就打印帮助信息,这里调用flag.Usage()

    if help{
        flag.Usage()
    }else {
        fmt.Println(host,port,verbor)
    }

运行

加上-h

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -h
Usage of /tmp/go-build717929224/b001/exe/flag:
  -H string
        -host addr (default "127.0.0.1")
  -P int
        nginx port (default 88)
  -help
        help
  -v    detail log
exit status 2

而这个flag.Usage()可以改写

10.2flag.Usag#

flag.Usage()是一个无参的函数,我们进行重新赋值

    flag.Usage = func(){
        fmt.Println("Usage flagargs:\n  -H --host 127.0.0.1\n   -P --port 80\n  -V")
    }

而后在调用,就发现已经被改写了

package main

import (
    "flag"
    "fmt"
)
func main(){
    var port int
    var host string
    var verbor bool
    var help bool
    flag.IntVar(&port,"P",88,"nginx port")
    flag.StringVar(&host,"H","127.0.0.1","-host addr")
    flag.BoolVar(&verbor,"v",false,"detail log")
    flag.BoolVar(&help,"help",false,"help")


    flag.Usage = func(){
        fmt.Println("Usage flagargs:\n  -H --host 127.0.0.1\n   -P --port 80\n  -V")
    }

    flag.Parse()

    if help{
        flag.Usage()
    }else {
        fmt.Println(host,port,verbor)
    }
}

运行,使用-H

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -h
Usage flagargs:
    -H --host 127.0.0.1
    -P --port 80
    -V
exit status 2

或者我们在加一个参数,使用flag.PrintlnDefaults()

10.3flag.PrintDefault#

在原来的基础上,加上flag.PrintDefaults()

    flag.Usage = func(){
        fmt.Println("Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ]")
        flag.PrintDefaults()
    }

代码块

[root@linuxea.com /opt/Golang/work4/stdpkg]# cat flag.go 
package main

import (
    "flag"
    "fmt"
)
func main(){
    var port int
    var host string
    var verbor bool
    var help bool
    flag.IntVar(&port,"P",88,"nginx port")
    flag.StringVar(&host,"H","127.0.0.1","-host addr")
    flag.BoolVar(&verbor,"v",false,"detail log")
    flag.BoolVar(&help,"help",false,"help")


    flag.Usage = func(){
        fmt.Println("Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ]")
        flag.PrintDefaults()
    }

    flag.Parse()

    if help{
        flag.Usage()
    }else {
        fmt.Println(host,port,verbor)
    }
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -h
Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ]
  -H string
        -host addr (default "127.0.0.1")
  -P int
        nginx port (default 88)
  -help
        help
  -v    detail log
exit status 2

那么,现在看到的就是自己的定义的格式。

10.4flag.args#

现在,假如还要传递一些没有名称的flag,就是可以使用flag.args

在原有的代码上加上flag.args

    if help{
        flag.Usage()
    }else {
        fmt.Println(host,port,verbor)
        fmt.Println(flag.Args())
    }

我们添加一个Usage的信息[args01 args02]

并且在条件处理中也添加

    flag.Usage = func(){
        fmt.Println("Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ] [args01 args02]")
        flag.PrintDefaults()
    }

代码块

package main

import (
    "flag"
    "fmt"
    )

func main(){

    var port int
    var host string
    var verbor bool
    var help bool
    flag.IntVar(&port,"P",88,"nginx port")
    flag.StringVar(&host,"H","127.0.0.1","-host addr")
    flag.BoolVar(&verbor,"v",false,"detail log")
    flag.BoolVar(&help,"help",false,"help")

    flag.Usage = func(){
        fmt.Println("Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ] [args01 args02]")
        flag.PrintDefaults()
    }

    flag.Parse()

    if help{
        flag.Usage()
    }else {
        fmt.Println(host,port,verbor)
        fmt.Println(flag.Args())
    }

}

假如不传递任何参数,就是空的[]

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go
127.0.0.1 88 false
[]

传入参数

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go mark linuxea.com
127.0.0.1 88 false
[mark linuxea.com]

并且使用-H可以看到在Usage中添加的信息。

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flag.go -H
flag needs an argument: -H
Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ] [args01 args02]
  -H string
        -host addr (default "127.0.0.1")
  -P int
        nginx port (default 88)
  -help
        help
  -v    detail log
exit status 2

10.5#

我们换一种方式,赋值给每个flag,然后使用指针调用,如下:

package main

import (
    "flag"
    "fmt"
    )

func main(){

    port := flag.Int("P",88,"nginx port")
    host := flag.String("H","127.0.0.1","-host addr")
    verbor := flag.Bool("v",false,"detail log")
    help := flag.Bool("help",false,"help")

    flag.Usage = func(){
        fmt.Println("Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ] [args01 args02]")
        flag.PrintDefaults()
    }

    flag.Parse()

    fmt.Printf("%T %T %T %T\n",*port,*host,*verbor,*help)
    fmt.Printf("%v %v %v %v\n",*port,*host,*verbor,*help)

}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flags.go
int string bool bool
88 127.0.0.1 false false

-v

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flags.go -v
int string bool bool
88 127.0.0.1 true false

-H

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run flags.go -H
flag needs an argument: -H
Usage flagargs: [ -H --host 127.0.0.1  -P --port 80 -V ] [args01 args02]
  -H string
        -host addr (default "127.0.0.1")
  -P int
        nginx port (default 88)
  -help
        help
  -v    detail log
exit status 2

11.time模块#

导入time模块,time的类型也是time.Time。我们使用time.Now()获取当前时间

package main
import (
    "fmt"
    "time"
)
func main(){
    now := time.Now()
    fmt.Printf("%T\n",now)
    fmt.Printf("%v",now)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time.go
time.Time
2019-10-02 21:29:02.017017987 +0800 CST m=+0.000473262

时间类型

11.1时间转换#

最基础的方式是将时间转换成字符串

一般来讲有这样几种方式

2000/01/01 08:01:01

或者

2000-01-01 08:01:01

在go中,2006表示年01表示月02表示天小时24进制使用15分钟使用04表示秒使用05表示

那么现在使用now.Format()来转换时间,如下

func main(){
    now := time.Now()
    fmt.Printf("%T\n",now)
    fmt.Printf("%v\n",now)

    fmt.Println(now.Format("2006-01-02 15:04:05"))
    fmt.Println(now.Format("2006/01/02 15:04:05"))
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time.go
time.Time
2019-10-02 21:48:34.622411579 +0800 CST m=+0.000180147
2019-10-02 21:48:34
2019/10/02 21:48:34

假如此刻只需要年月日, now.Format("2006/01/02")即可,如果只是时分秒now.Format("15:04:05")即可

package main
import (
    "fmt"
    "time"
)
func main(){
    now := time.Now()
    fmt.Println(now.Format("2006/01/02"))
    fmt.Println(now.Format("15:04:05"))
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time.go
time.Time
2019/10/02
21:50:18

now.Unix()

package main
import (
    "fmt"
    "time"
)
func main(){
    now := time.Now()
    fmt.Println(now.Unix())
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time.go
1570065936

now.UnixNano()

package main
import (
    "fmt"
    "time"
)
func main(){
    now := time.Now()
    fmt.Println(now.UnixNano())
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time.go
1570066422096765619

11.2转换时间#

time.Parse又两个返回值,一个是err,一个是转换的值

转换的格式必须一样

我们定义一个时间格式,假如现在是2015/08/06 06:00:00,如下

    now := "2015/08/06 06:00:00"

而后使用time.Parse进行转换,在time.Parse("2006/01/02 15:04:05“)中的第一段时间是模板,第二段的时间是转换的时间。比如现在将now变量的2015/08/06 06:00:00按照2006/01/02 15:04:05格式转换成字符串。如下:

这里使用t和err进行接收返回值

    t,err := time.Parse("2006/01/02 15:04:05",now)

而后打印

fmt.Println(err,t)

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time2.go
<nil> 2015-08-06 06:00:00 +0000 UTC

示例:

    at := time.Unix(0,0)
    fmt.Println(at)

代码块

package main
import (
    "fmt"
    "time"
)
func main(){
    at := time.Unix(0,0)
    fmt.Println(at)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time2.go
1970-01-01 08:00:00 +0800 CST

11.3计算时间区间#

需要两个time类型的时间进行计算时间区间,于是我们就定义两个时间点,分别是now和date,date的时间需要通过字符串转换

    now := time.Now()
    date := "2019/10/02 00:00:00"
    t,err := time.Parse("2006/01/02 15:04:05",date)

然后通过now.Sum计算当前时间和date的时间差。并且打印出类型

d := now.Sub(t)
fmt.Println(d)
fmt.Printf("%T",d)

代码块

package main
import (
    "fmt"
    "time"
)
func main(){
    now := time.Now()

    date := "2019/10/02 00:00:00"
    t,err := time.Parse("2006/01/02 15:04:05",date)
    fmt.Println(t,err)
    d := now.Sub(t)
    fmt.Println(d)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time3.go
2019-10-02 00:00:00 +0000 UTC <nil>
26h23m59.68928344s
time.Duration

当前时间与date时间差了26h23m59.68928344s,时间区间的类型是time.Duration

### 11.4休眠时间

time.Sleep需要设置一个时间区间,比如,休眠三秒

    fmt.Println(time.Now())
    time.Sleep(time.Second * 3)
    fmt.Println(time.Now())

代码块

package main
import (
    "fmt"
    "time"
)
func main(){
    fmt.Println(time.Now())
    time.Sleep(time.Second * 3)
    fmt.Println(time.Now())
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time4.go
2019-10-03 10:35:32.848757428 +0800 CST m=+0.000131262
2019-10-03 10:35:35.849273679 +0800 CST m=+3.000647522

11.5添加时间区间#

我们给当前的时间加上几个小时后打印

package main
import (
    "fmt"
    "time"
)
func main(){
    fmt.Println(time.Now())
    now := time.Now()
    t := now.Add(3 * time.Hour)
    fmt.Println(t)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time4.go
2019-10-03 10:41:26.584561434 +0800 CST m=+0.000090932
2019-10-03 13:41:26.58466329 +0800 CST m=+10800.000192739

11.6解析时间区间#

可以将时间格式解析,time.ParseDuration又两个返回值

    d,err := time.ParseDuration("3h2m5s")
    fmt.Println(err,d)

代码块

package main
import (
    "fmt"
    "time"
)
func main(){
    d,err := time.ParseDuration("3h2m5s")
    fmt.Println(err,d)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time5.go
<nil> 3h2m5s

并且可以转换成小时,分钟,秒,只需使用Hours,Minutes,Seconds即可

fmt.Println(d.Hours())
package main
import (
    "fmt"
    "time"
)
func main(){
    d,err := time.ParseDuration("3h2m5s")
    fmt.Println(err,d)
    fmt.Println(d.Hours())
    fmt.Println(d.Minutes())
    fmt.Println(d.Seconds())
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run time5.go
<nil> 3h2m5s
3.0347222222222223
182.08333333333334
10925

12.md5#

crypto/md5的计算hash,在使用md5.sum的时候需要传入一个字节,如果传入的是字符串,需要转换下,而后计算hash结果,转换后是byte类型的数组,而后将数组转换成一个16进制

计算mark的md5值

func main(){
    md := md5.Sum([]byte("mark"))
    fmt.Println(md)
}

打印

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run md5.go
[234 130 65 12 122 153 145 129 107 94 238 235 225 149 226 10]

转换成16进制,使用fmt.Printf("%x",md)即可,但是这里是16进制的int类型,这里的%x百分号小x是小写

func main(){
    md := md5.Sum([]byte("mark"))
    fmt.Printf("%x",md)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run md5.go
ea82410c7a9991816b5eeeebe195e20a

如果要转换成字符串,就使用fmt.Sprintf或者hex.EncodeToString进行转换。要使用hex.EncodeToString就需要导入包

import "encoding/hex"

Sprintf转换字符串,这里的%X将会转换成大写

    md := md5.Sum([]byte("mark"))
    x := fmt.Sprintf("%X",md)
    fmt.Println(x)

hex.EncodeToString转换字符串

    fmt.Println(hex.EncodeToString(md[:]))

代码块

package main
import (
    "fmt"
    "crypto/md5"
    "encoding/hex"
)
func main(){
    md := md5.Sum([]byte("mark"))
    fmt.Printf("%x\n",md)
    fmt.Printf("%T\n",md)

    x := fmt.Sprintf("%X",md)
    fmt.Println(hex.EncodeToString(md[:]))
    fmt.Println(x)
    fmt.Printf("%T",x)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run md5.go
ea82410c7a9991816b5eeeebe195e20a
[16]uint8
ea82410c7a9991816b5eeeebe195e20a
EA82410C7A9991816B5EEEEBE195E20A
string

12.1md5.New批量计算#

通常值比较小直接就可以进行计算,如果值比较大,就可以使用md5.New一个对象进行批量计算,但是计算的结果和一次计算也是一样的。

定义一个m的md5.New

m := md5.New()

而后通过m.Write批量计算

m.Write([]byte("m"))
m.Write([]byte("a"))
m.Write([]byte("r"))
m.Write([]byte("k"))

而后通过sum计算,但是这里sum传递一个nil即可,因为这里没有值

fmt.Printf("%x\n",m.Sum(nil))

代码块

[root@linuxea.com /opt/Golang/work4/stdpkg]# cat md52.go
package main
import (
    "fmt"
    "crypto/md5"
)
func main(){
    m := md5.New()
    m.Write([]byte("m"))
    m.Write([]byte("a"))
    m.Write([]byte("r"))
    m.Write([]byte("k"))

    fmt.Printf("%x\n",m.Sum(nil))
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run md52.go 
ea82410c7a9991816b5eeeebe195e20a

13.base64#

使用base64,需要导入"encoding/base64",转换成string类型,加上EncodeToString

base64编码和解码都较为简单

package main
import (
    "encoding/base64"
    "fmt"
)
func main(){
    e :=  base64.StdEncoding.EncodeToString([]byte("mark"))
    fmt.Println(e)
}   

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run base64.go
bWFyaw==

解码使用base64.StdEncoding.DecodeString,会返回两个返回值,这里使用bytes和err接收

package main
import (
    "encoding/base64"
    "fmt"
)
func main(){
    e :=  base64.StdEncoding.EncodeToString([]byte("mark"))
    fmt.Println(e)
    bytes,err := base64.StdEncoding.DecodeString(e)
    fmt.Println(string(bytes),err)
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run base644.go
bWFyaw==
mark <nil>

除了这些还有URLEncoding,RawURLEncoding,RawEncoding

14.日志[log]#

log模块,首先要导入log模块

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go
2019/10/03 15:47:13 [info] susu

Panicf会答应日志并触发一个Panicf

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")

    log.Panicf("[Panicf] %s","error")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go
2019/10/03 15:55:23 [info] susu
2019/10/03 15:55:23 [Panicf] error
panic: [Panicf] error

goroutine 1 [running]:
log.Panicf(0x4c673f, 0xb, 0xc000043f78, 0x1, 0x1)
    /usr/local/go/src/log/log.go:340 +0xc0
main.main()
    /opt/Golang/work4/stdpkg/log.go:8 +0xcb
exit status 2

Fatalf在打印日志后也会退出

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")

    //log.Panicf("[Panicf] %s","error")

    log.Fatalf("[Fatalf] %s","Fatalf")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go
2019/10/03 16:00:27 [info] susu
2019/10/03 16:00:27 [Fatalf] Fatalf
exit status 1

日志加前缀[SetPrefix-],如下:

log.SetPrefix("SetPrefix-")

将这段放在哪里,后面的日志中就会有这个前缀,

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")
    log.SetPrefix("SetPrefix-")
    log.Fatalf("[Fatalf] %s","Fatalf")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go
2019/10/03 16:34:05 [info] susu
SetPrefix-2019/10/03 16:34:05 [Fatalf] Fatalf
exit status 1

log.SetFlags(log.Flags() | log.Lshortfile ),Lshortfile打印的是文件名i

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")
    log.SetPrefix("SetPrefix-")
    log.SetFlags(log.Flags() | log.Lshortfile )
    log.Fatalf("[Fatalf] %s","Fatalf")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go 
2019/10/03 17:13:19 [info] susu
SetPrefix-2019/10/03 17:13:19 log.go:14: [Fatalf] Fatalf
exit status 1

log.SetFlags(log.Flags() | log.Llongfile),Llongfile打印的是文件全路径

package main
import (
    "log"
)
func main(){
    log.Printf("[info] %s","susu")

    log.SetPrefix("SetPrefix-")

    log.SetFlags(log.Flags() | log.Llongfile)

    log.Fatalf("[Fatalf] %s","Fatalf")
}

运行

[root@linuxea.com /opt/Golang/work4/stdpkg]# go run log.go
2019/10/03 17:14:19 [info] susu
SetPrefix-2019/10/03 17:14:19 /opt/Golang/work4/stdpkg/log.go:14: [Fatalf] Fatalf
exit status 1