Go语言能够经过go/build包里定义的tags和命名约定来让Go的包能够运行不一样的代码。html
标签编译
在源代码里添加标注,一般称之为编译标签(build tag)。编译标签采用靠近源代码文件顶部用注释的方式添加。go build在构建一个包的时候会读取这个包里的每一个源文件而且分析编译便签,这些标签决定了这个源文件是否参与本次编译。linux
编译标签由空格分隔的编译选项(options)以”或”的逻辑关系组成(a build tag is evaluated as the OR of space-separated options)。
每一个编译选项由逗号分隔的条件项以逻辑”与”的关系组成( each option evaluates as the AND of its comma-separated terms)
每一个条件项的名字用字母+数字表示,在前面加!表示否认的意思(each term is an alphanumeric word or, preceded by !, its negation)
注:一个源文件能够有多个编译标签,多个编译标签之间是逻辑“与”的关系,一个编译标签能够包括由空格分割的多个标签,这些标签是逻辑“或”的关系。例子:git
// +build linux darwin
// +build 386
这个将限制此源文件只能在 linux/386或者darwin/386平台下编译
下边以json包的适配为例,介绍标签编译:github
背景
Golang提供的标准json解析库——encoding/json,在开发高性能、高并发的网络服务时会产生性能问题。替代的方案是使用高性能的json解析库,好比json-iterator和easyjson。在正式引用高性能的json解析库(以json-iterator为例)一般的作法是小范围的进行测试,此时就会出现两个库并存的时候,解决方案是使用标签编译选择运行的解析库web
统一的JSON库
如今咱们须要两个库并存,因此咱们先得统一这两个库的用法(参考适配器模式),这里咱们使用一个自定义的json包来适配encoding/json和json-iteratorjson
json/json.go网络
// +build !jsoniter
//必须留一行空格,不然会报重定义的错误
package json
import (
"encoding/json"
"fmt"
)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
fmt.Println("Use [encoding/json] package")
return json.MarshalIndent(v,prefix,indent)
}
json/jsoniter.go并发
// +build jsoniter
package json
import (
"fmt"
"github.com/json-iterator/go"
)
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary
)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
fmt.Println("Use [jsoniter] package")
return json.MarshalIndent(v,prefix,indent)
}
目录结构以下:svg
json
├── json.go
└── jsoniter.go
注: 上述例子中若是编译标签 和包的声明 之间没有空行隔开,编译标签会被当作包声明的注释而不是编译标签,切记空行必需要有。函数
main函数
此处引用的json包下的两个go文件中都有MarshalIndent函数定义,而且签名一致,但它们又是使用不一样的json解析库实现,此处至关于作了统一的适配包装,因此调用能够统一。
package main
import (
"fmt"
"pracitice/json_test/json"
)
type user struct {
Name string
Age int
}
func main() {
u := user{"xiyan",7}
b, err := json.MarshalIndent(u, "", "")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(b))
}
}
直接运行go run main.go结果:
Use [encoding/json] package
{
"Name": "xiyan",
"Age": 7
}
使用标签编译运行go run -tags=jsoniter main.go
Use [jsoniter] package
{"Name":"xiyan","Age":7}
总结
标签编译的关键在于 -tags=jsoniter , -tags 这个标志,是Go语言为咱们提供的条件编译方式之一。若是咱们不是运行,而是编译构建的话,改成go build -tags=jsoniter 便可生成调用了对应包的可执行文件。
// +build !jsoniter
表示,tags不是jsoniter的时候编译这个Go文件。
// +build jsoniter
表示,tags是jsoniter的时候编译这个Go文件。
这两行是Go语言条件编译的关键。+build能够理解标签编译tags的声明关键字,后面跟着tags的条件。
文件后缀
使用这种方案比编译标签要简单,go/build能够在不读取源文件的状况下就能够决定哪些文件不须要参加编译。文件命名约定能够在go/build包里找到详细的说明,简单来讲若是你的源文件包含后缀:_$GOOS.go,那么这个源文件只会在这个平台下编译, _$GOARCH.go也是如此。 这两个后缀能够结合在一块儿使用,顺序只能为:_$GOOS_$GOARCH.go
例子以下:
mypkg_freebsd_arm.go // only builds on freebsd/arm systems
mypkg_plan9.go // only builds on plan9
源文件不能只提供条件编译后缀,还必须有文件名,_linux.go、_freebsd_386.go 这两个源文件在全部的平台下都会被忽略掉,由于go/build将会忽略全部如下划线或者点开头的源文件 。
参考资料