目录
[一、GOPATH 时代 (Go 1.0 - Go 1.10)](#一、GOPATH 时代 (Go 1.0 - Go 1.10))
[1.1 工作区(Workspace)](#1.1 工作区(Workspace))
[1.2 GOROOT & GOBIN](#1.2 GOROOT & GOBIN)
[1.3 什么是GOPATH?](#1.3 什么是GOPATH?)
[1.4 GOPATH的使用](#1.4 GOPATH的使用)
[1.5 GOPATH的现状&缺陷](#1.5 GOPATH的现状&缺陷)
[二、Go Modules 引入至成熟 (Go 1.11~1.13及以后)](#二、Go Modules 引入至成熟 (Go 1.11~1.13及以后))
[2.1 Go Modules 的概念](#2.1 Go Modules 的概念)
[2.2 Go Modules > GOPATH](#2.2 Go Modules > GOPATH)
[2.3 go.mod & go.sum](#2.3 go.mod & go.sum)
[2.4 现代Go开发中的工作区使用](#2.4 现代Go开发中的工作区使用)
[2.4.1 工作区模式(Workspace mode)](#2.4.1 工作区模式(Workspace mode))
[2.4.2 go.work 文件](#2.4.2 go.work 文件)
[3.1 包的概念](#3.1 包的概念)
[3.2 包的声明 & 初始化](#3.2 包的声明 & 初始化)
[3.2.1 包的声明](#3.2.1 包的声明)
[3.2.2 包的初始化](#3.2.2 包的初始化)
[3.2.3 mian包](#3.2.3 mian包)
[3.2.4 init() & main()](#3.2.4 init() & main())
[3.3 包的加载顺序](#3.3 包的加载顺序)
[4.1 导入声明](#4.1 导入声明)
[4.2 GO标准库](#4.2 GO标准库)
[4.3 本地导入](#4.3 本地导入)
[4.4 远程导入](#4.4 远程导入)
[4.5 别名导入](#4.5 别名导入)
[4.6 匿名导入](#4.6 匿名导入)
前言
对于大部分编程语言来说,代码包都是最有效的代码管理方式,Go语言也是使用包来管理代码的。如同其他语言一样,Go语言包的主要作用是把功能相似或相关的代码组织在同一个包中, 以方便查找和使用。
本章会详细介绍Go语言工程结构和包的使用。熟练掌握包管理是Go语言编码的基础,由于这部分内容的版本变革比较大,所以了解包管理的发展史是很有必要的,同时也要清楚现在最新使用包的方式。
一、GOPATH 时代 (Go 1.0 - Go 1.10)
- 工作区由GOPATH环境变量定义
- 所有Go代码必须位于GOPATH/src下
- 包的导入路径直接对应src下的目录结构
1.1 工作区(Workspace)
Go工作区(Workspace)是Go语言中组织代码的一种方式,Go语言中没有工程文件的概念,而是通过目录结构来体现工程的结构关系。在传统的GOPATH模式下,Go代码必须放在工作区中。工作区是一个目录,其中包含了特定的目录结构来组织Go源代码、包和二进制文件。
传统的工作区其实就是一个对应于特定工程的目录,它应包含三个子目录:src目录、pkg目录和bin目 录。如下所示:
bash
GOPATH/
│
├── src/
│ ├── github.com/
│ │ └── user/
│ │ └── project/
│ │ └── *.go
│ └── myproject/
│ └── *.go
│
├── pkg/
│ └── ${GOOS}_${GOARCH}/
│ └── github.com/
│ └── user/
│ └── project.a
│
└── bin/
└── project
其中各目录的作用如下:
- src目录:用于以代码包的形式组织并保存Go源码文件(如.go、.c、.h、.s等),同时也是 Go编译时查找代码的地方。
- pkg目录:用于存放经由go get/install命令构建安装后的代码包的".a"归档文件,也是编译 生成的lib文件存储的地方。
- bin目录:与pkg目录类似,在通过go get/install命令完成安装后,保存由Go命令源码文件生 成的可执行文件。
目录src用于包含所有的源代码,是Go命令行工具一个强制的规则,而pkg和bin则无须手动创 建,必要时Go命令行工具在构建过程中会自动创建这些目录。
.a文件是Go语言中的静态库文件(archive file)。这些文件包含预编译的Go包代码,可以被链接到其他Go程序中。
1.2 GOROOT & GOBIN
GOROOT是Go语言的安装路径。它指向Go开发包的根目录,包含Go的标准库、工具和源代码。
- 定位Go标准库和工具的位置
- 通常不需要手动设置,Go安装程序会自动设置
Go
go env GOROOT
GOBIN指定了go install
命令安装可执行文件的目录。设置GOBIN可以让你更灵活地控制可执行文件的安装位置。
- 存放通过
go install
编译的可执行文件 - 如果未设置,默认为
GOPATH/bin
或GOROOT/bin
查看所有Go环境变量 打开命令提示符,输入:go env
1.3 什么是GOPATH?
GOPATH是Go语言中用来指定工作空间位置的环境变量。在Go 1.11版本之前,它是Go开发的核心概念,用于解析import语句,管理源代码、依赖包和编译后的文件。GOPATH是Go语言开发中一个非常重要的概念,尽管在Go Modules引入后其重要性有所降低,但理解GOPATH对于掌握Go的工作机制仍然很有帮助。------ GOPATH定义了Go查找、编译和安装包的方式。它为Go提供了一个统一的工作空间概念。
- 在Go Modules出现前,它是管理依赖的主要方式
- 存储项目源码、包文件和可执行文件
在windows用于查看GOPATH路劲:
bash
go env GOPATH
1.4 GOPATH的使用
1. 导入包: 在GOPATH模式下,import路径是相对于GOPATH/src
的。
2. 获取包: 在 GOPATH 模式下,这会将包下载到GOPATH/src
目录。
Go
go get github.com/user/project
3. 编译和安装: 这会编译myproject
并将二进制文件放在GOPATH/bin
目录。
Go
go install myproject
GOPATH的目的是为了告知Go需要代码的时候去哪里查找。需要注意的是,这里的代码包括本项目和引用外部项目的代码。GOPATH可以随着项目 的不同而重新设置。
在实际开发环境中,工作目录往往有多个。这些工作目录的目录路径都需要添加至GOPATH。 为了能够构建这个工程,需要先把所需工程的根目录加入到环境变量GOPATH中。否则,即使处于同一工作目录(工作区),代码之间也无法通过绝对代码包路径完成调用。
如果GOPATH设置了多个工作区,那么查找依赖包时是以怎样的顺序进行呢?例如包a依赖包 b,包b依赖包c,那么会先查找c包。那在工作区是如何查找这个依赖包c的呢?
首先,在查找依赖包的时候,总是会先查找GOROOT目录,也就是Go语言的安装目录,如果 没有找到依赖的包,才到工作区去找相应的包。在工作区中是按照设置的先后顺序来查找的,也 就是会从第一个开始依次查找,如果找到就不再继续,如果没有找到就报错。 如果多个工作区中存在导入路径相同的代码包会产生冲突吗?不冲突,因为按顺序找到所需 要的包就不往后继续找了。
而现代的Go Modules 提供了更灵活和精确的依赖管理方式,不依赖于 GOPATH。在现代 Go 开发中(尤其是使用 Go Modules 时),通常不需要关心多个 GOPATH 工作区。
1.5 GOPATH的现状&缺陷
现状
- 弱化的重要性:自Go 1.11引入Go Modules后,GOPATH的重要性大幅降低。不再是组织Go代码的唯一或主要方式。在使用Go Modules的项目中,不再需要设置GOPATH。
- 兼容性保留:仍然存在,主要用于向后兼容。在非模块模式下仍然发挥作用。
- **新的用途:**作为默认的模块缓存位置(GOPATH/pkg/mod)。用于存储全局安装的Go工具(GOPATH/bin)。
缺陷
- 版本管理困难:难以在同一个项目中使用同一包的不同版本,增加了项目间的依赖冲突风险。所有项目共享同一个GOPATH,容易造成版本冲突。
- 可移植性差:项目严重依赖于GOPATH的设置,难以在不同环境间移植。
- 包导入路径复杂 :导入路径必须是完整的,包括版本控制系统的信息。例如:
github.com/user/project
而不是简单的project
。 - 依赖更新麻烦:没有内置的版本控制机制,更新依赖时可能影响到其他项目。
- 全局状态:GOPATH是一个全局设置,影响所有Go项目。不利于在同一机器上同时处理使用不同Go版本的项目。
- 与现代包管理实践不符:不支持语义化版本控制,缺乏显式的依赖声明机制。
- 学习曲线:对新手来说,理解和正确使用GOPATH可能具有挑战性。新开发者入门困难,需要正确设置GOPATH。
语义化版本控制(Semantic Versioning)
语义化版本控制是一种版本号命名规范,通常格式为 X.Y.Z(主版本号.次版本号.修订号)。
- 主版本号:做了不兼容的 API 修改
- 次版本号:做了向下兼容的功能性新增
- 修订号:做了向下兼容的问题修正
显式的依赖声明机制
在项目中明确列出所有直接依赖及其版本,通常在一个专门的配置文件中
了解了传统的包管理方式,接下来我们就需要了解现代的包管理方式
二、Go Modules 引入至成熟 (Go 1.11~1.13及以后)
- Go Modules引入(Go 1.11)
- 引入模块概念,减少对GOPATH的依赖
- 项目可以位于GOPATH之外
- 每个项目可以有自己的依赖管理
- Go Modules成熟(Go 1.13及以后)
- 模块成为默认的代码组织方式
- GOPATH的重要性大大降低
2.1 Go Modules 的概念
- 定义: Go Modules 是 Go 语言的依赖管理系统,从 Go 1.11 版本引入,并在 Go 1.13 成为默认的依赖管理方式。它用于管理 Go 项目的依赖关系,确保项目的可重复构建和版本控制。
- 核心组件:
- go.mod 文件:定义项目的模块路径、Go 版本和依赖关系。
- go.sum 文件:包含依赖模块的加密校验和,用于验证下载的模块内容。
- 版本管理: 使用语义化版本(Semantic Versioning)来管理依赖版本,形如 v1.2.3。
- 依赖解析: Go Modules 可以自动下载、验证和缓存依赖,无需手动管理。
2.2 Go Modules > GOPATH
通过解决这些问题,Go Modules 极大地改善了 Go 语言的包管理和项目组织方式,使得大规模、复杂的 Go 项目开发变得更加可控和高效。
|-----------------|---------------------------------------------------|-----------------------------------------------------------------|
| | GOPATH | Go Modules |
| 版本管理 | 缺乏对包版本的明确控制 难以在同一项目中使用同一包的不同版本 | 引入 go.mod
文件,明确指定每个依赖的版本 支持语义化版本控制 允许在同一项目中使用同一包的不同版本 |
| 项目隔离 | 所有项目共享同一个 GOPATH,容易造成版本冲突 难以为不同项目维护不同的依赖版本 | 每个项目有自己的 go.mod
文件,实现了项目级的依赖隔离 不同项目可以使用同一包的不同版本而不冲突 |
| 可重现的构建 | 缺乏对依赖版本的精确记录,难以保证不同环境下的构建一致性 | 使用 go.sum
文件记录所有依赖的精确版本和校验和 确保在不同机器上可以重现完全相同的构建结果 |
| 依赖管理的显式性 | 依赖关系隐含在代码中,难以一目了然地了解项目依赖 | 在 go.mod
文件中明确列出所有直接依赖 提供 go mod why
等工具来解释依赖关系 |
| 包的位置灵活性 | 强制要求所有代码位于 GOPATH 中,限制了项目组织的灵活性 | 允许项目位于 GOPATH 之外的任何位置 简化了项目的共享和协作 |
| 中心化依赖问题 | 依赖 central public server(如 GitHub)的可用性 企业内部包的管理困难 | 引入了 proxy 和 checksum 数据库的概念 支持私有模块和企业内部模块仓库 |
| 依赖更新和回滚 | 更新或回滚依赖版本操作复杂,容易影响其他项目 | 提供简单的命令(如 go get -u
或 go mod tidy
)来更新依赖 可以轻松回滚到特定版本而不影响其他项目 |
| vendor 目录管理 | vendor 目录的使用和管理不够优雅和高效 | 改进了 vendor 的支持,使其成为可选功能 提供 go mod vendor
命令来创建和管理 vendor 目录 |
| 全局状态依赖 | 依赖全局 GOPATH 设置,不利于项目的可移植性 | 移除了对 GOPATH 的依赖(除了用作默认的模块缓存位置) 提高了项目的可移植性和共享便利性 |
| 工具链集成 | 与现代 IDE 和 CI/CD 工具的集成不够顺畅 | 提供了更好的工具链集成能力 简化了在 CI/CD 环境中的依赖管理 |
2.3 go.mod & go.sum
go.mod 和 go.sum 文件共同工作,为 Go 项目提供了强大、安全和可重现的依赖管理机制。go.mod 定义了项目的依赖关系,而 go.sum 则确保了这些依赖的完整性和一致性。
|----|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | go.mod | go.sum |
| 用途 | * 定义模块的身份和依赖关系 * 指定项目使用的 Go 版本 | * 记录项目所有直接和间接依赖的加密校验和 * 确保依赖的完整性和一致性 |
| 结构 | module github.com/yourusername/yourproject go 1.16 require ( github.com/pkg/errors v0.9.1 golang.org/x/text v0.3.6 ) | github.com/pkg/errors v0.9.1 h1:FEg... github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp... golang.org/x/text v0.3.6 h1:aRYxNxv6... golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
| 特点 | * 人类可读且易于编辑 * 自动由 Go 命令维护 * 可以手动编辑,但通常通过 go 命令管理 | * 自动生成和维护 * 包含每个依赖模块的两个哈希值:一个用于整个模块,一个用于 go.mod 文件 * 不应手动编辑 |
2.4 现代Go开发中的工作区使用
- 单项目开发
- 通常不需要关心传统的工作区概念
- 使用go.mod文件管理项目依赖
- 多模块项目
- 可以使用Go 1.18引入的工作区模式
- 创建go.work文件来管理多个相关模块
2.4.1 工作区模式(Workspace mode)
Go 1.18引入的工作区模式 ,工作区模式旨在简化多模块项目的开发。允许开发者在单个目录中同时处理多个相关的 Go 模块,而无需修改这些模块的 go.mod 文件。这对于开发涉及多个相互依赖的模块的大型项目特别有用。
主要特点:
- 简化了多模块项目的开发和测试
- 允许在本地修改依赖模块而不需要发布新版本
- 提供了更灵活的项目结构
2.4.2 go.work 文件
go.work 文件是工作区模式的核心。它定义了工作区的结构和包含的模块。
优点:
- 简化依赖管理:无需修改 go.mod 来使用本地版本的依赖
- 改善开发体验:更容易在相关模块间切换和测试
- 保持模块独立:每个模块仍然保持其独立的 go.mod 文件
注意事项:
- go.work 文件通常不应该提交到版本控制系统
- 它主要用于本地开发,不影响发布和构建过程
示例:
假设有一个项目包含三个模块:主应用、API 库和工具库。
Go
my-project/
├── app/
│ └── go.mod
├── api-lib/
│ └── go.mod
├── utils/
│ └── go.mod
└── go.work
go.work 内容:
Go
go 1.18
use (
./app
./api-lib
./utils
)
三、包的介绍
3.1 包的概念
包是结构化代码的一种方式:每个程序都由包(通常简称为pkg)的概念组成,可以使用自身 的包或者从其他包中导入内容。我们先来了解一下Go语言源码的组织方式:
- Go语言的源代码以代码包为基本的组织单位。
- 在文件系统中,代码包是与文件目录,每个Go文件都属于且仅属于一个包。一一对应的,文件目录的子目录也就是代码包的子包。
- 在工作区中,一个代码包的导入路径实际上就是从src子目录到该包的实际存储位置的相对路径。(这是对于GOPATH,Go Modules可忽略)
- 包名通常与目录名相同,但并非强制要求。另外要注意的是,所有的包名都应该使用小写字母
3.2 包的声明 & 初始化
3.2.1 包的声明
每一个Go源文件的第一行都需要声明包的名称,声明一个包使用关键字package。如声明一个 main包:
Go
package main
关键点:
- 同一目录下的所有 Go 文件必须属于同一个包。
- 包名通常与目录名相同,但这不是强制的(除了 main 包)。
- main 包是特殊的,它定义了一个可执行程序而不是一个库。
3.2.2 包的初始化
包的初始化过程比较复杂,按以下顺序进行:
a) 导入的包初始化 b) 包级变量初始化 c) init() 函数执行
3.2.3 mian包
在Go语言中,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编 译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个名叫main的包,main包有 且仅有一个。
当编译器发现某个包的名字为main时,它一定也会发现名为main()的函数,否则不会创建可执行文件。
main()函数是程序的入口:所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明main包的代码所在的目录名称作为可执行文件的文件名。
一个应用程序可以包含不同的包,而且即使你只使用main包也不必把所有的代码都写在一个 巨大的文件里,你可以用一些较小的文件,并且在每个文件非注释的第一行都使用package main来指明这些文件都属于main包。如果你打算编译包名不为main的源文件,如mypack,编译后产生的对象文件将会是mypack.a而不是可执行程序。
3.2.4 init() & main()
在Go语言里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。
概念:
|------------------------------------------------------------------|-----------------------------------------------------------------------|
| mian函数 | init函数 |
| * 只能在 package main 中定义 * 无参数,无返回值 * 一个程序只能有一个 main() 函数 * 程序的入口点 | * 可以在任何包中定义 * 无参数,无返回值 * 每个包可以有多个 init() 函数 * 自动调用,不能手动调用 * 用于包的初始化工作 |
注意事项:
- 虽然可以定义多个 init(),但无论是对于可读性还是以后的可维护性来说,但建议每个文件只写一个
- 包的初始化只执行一次:有时一个包会被多个包同时导入,但它只会被导入一次,即使被多个包导入(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)
- init() 函数都在 main() 函数之前执行,这是因为init()用于设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
- 每个package中的 init函数都是可选的,main 包必须包含一个 main() 函数
- 这些函数都会在程序执行开始的时候被调用。
3.3 包的加载顺序
- 包的导入顺序:
- 从 main 包开始,依次导入 pkg1、pkg2、pkg3。
- 导入是递归的,每个包都可能导入其他包。
- 初始化顺序:
- 初始化从最深层的包(这里是 pkg3)开始,然后逐层返回到 main 包。
- 每个包的初始化过程: a) 常量初始化(const...) b) 变量初始化(var...) c) init() 函数执行
- 包之间的依赖关系:
- 箭头表示依赖关系,例如 pkg1 依赖 pkg2,pkg2 依赖 pkg3。
- 每个包只会被初始化一次,即使被多个包引用。
- main 包的特殊性:
- main 包是最后初始化的。
- 在 main 包初始化完成后,main() 函数才会执行。
- 执行顺序: pkg3 -> pkg2 -> pkg1 -> main 对每个包:const -> var -> init() -> (next package)
- 程序结束:
- main() 函数执行完毕后,程序退出(Exit)。
代码示例,以下为示例的目录结构
bash
go_example/
├── main.go
├── pkg1/pkg1.go
├── pkg2/pkg2.go
├── pkg3/pkg3.go
└── go.mod # 无需手动创建
- 在
go_example
目录中初始化 Go module:
bash
go mod init go_example
go mod tidy
- 创建包文件
Go
// pkg3/pkg3.go
package pkg3
import "fmt"
const Pkg3Const = "pkg3 constant"
var Pkg3Var = "pkg3 variable"
func init() {
fmt.Println("pkg3 init")
fmt.Printf("pkg3: Const = %s, Var = %s\n", Pkg3Const, Pkg3Var)
}
func Pkg3Func() {
fmt.Println("pkg3 function called")
}
// pkg2/pkg2.go
package pkg2
import (
"fmt"
"go_example/pkg3"
)
const Pkg2Const = "pkg2 constant"
var Pkg2Var = "pkg2 variable"
func init() {
fmt.Println("pkg2 init")
fmt.Printf("pkg2: Const = %s, Var = %s\n", Pkg2Const, Pkg2Var)
fmt.Printf("pkg2: Using pkg3.Pkg3Var = %s\n", pkg3.Pkg3Var)
}
func Pkg2Func() {
fmt.Println("pkg2 function called")
pkg3.Pkg3Func()
}
// pkg1/pkg1.go
package pkg1
import (
"fmt"
"go_example/pkg2"
)
const Pkg1Const = "pkg1 constant"
var Pkg1Var = "pkg1 variable"
func init() {
fmt.Println("pkg1 init")
fmt.Printf("pkg1: Const = %s, Var = %s\n", Pkg1Const, Pkg1Var)
fmt.Printf("pkg1: Using pkg2.Pkg2Var = %s\n", pkg2.Pkg2Var)
}
func Pkg1Func() {
fmt.Println("pkg1 function called")
pkg2.Pkg2Func()
}
// main.go
package main
import (
"fmt"
"go_example/pkg1"
)
const MainConst = "main constant"
var MainVar = "main variable"
func init() {
fmt.Println("main init")
fmt.Printf("main: Const = %s, Var = %s\n", MainConst, MainVar)
fmt.Printf("main: Using pkg1.Pkg1Var = %s\n", pkg1.Pkg1Var)
}
func main() {
fmt.Println("main function started")
pkg1.Pkg1Func()
fmt.Println("main function ended")
}
导航到go_example目录下运行 main.go:
Go
go run main.go
运行这个程序,将看到类似以下的输出:
Go
pkg3 init
pkg3: Const = pkg3 constant, Var = pkg3 variable
pkg2 init
pkg2: Const = pkg2 constant, Var = pkg2 variable
pkg2: Using pkg3.Pkg3Var = pkg3 variable
pkg1 init
pkg1: Const = pkg1 constant, Var = pkg1 variable
pkg1: Using pkg2.Pkg2Var = pkg2 variable
main init
main: Const = main constant, Var = main variable
main: Using pkg1.Pkg1Var = pkg1 variable
main function started
pkg1 function called
pkg2 function called
pkg3 function called
main function ended
四、包的导入
在Go语言程序中,每个包都有一个全局唯一的导入路径。导入语句中类似""fmt""的字符串 对应包的导入路径。 Go语言的规范并没有定义这些字符串的具体含义或包来自哪里,它们是由构建工具来解释 的。一个导入路径代表一个目录中的一个或多个Go源文件。
4.1 导入声明
- 使用
import
关键字导入其他包。 - 可以使用相对路径或完整的包路径导入。
Go
// 声明方式一
import "fmt"
import "time"
// 声明方式二
import (
"fmt"
"time"
)
标准库源码位置:Go标准库(包括fmt等包)的源代码位于Go安装目录的src
文件夹中。
4.2 GO标准库
|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 基础包 | fmt:格式化I/O,主要用于格式化输出和输入。 os:操作系统功能接口,提供平台无关的操作系统功能,如文件操作、环境变量等。 io:基本的输入输出原语,提供基本的I/O接口。 bufio:带缓冲的I/O,包装了io包,提供带缓冲的I/O操作。 strconv:字符串和基本数据类型的转换。 time:时间和日期的功能,提供时间的表示和测量功能。 |
| 字符串处理 | strings:字符串操作的常用函数,如拼接、分割、替换等。 unicode:Unicode字符的支持,包含unicode字符集相关的函数。 regexp:正则表达式的实现,提供正则表达式搜索和替换功能。 |
| 数学运算 | math:基本的数学函数,如三角函数、对数、指数等。 math/rand:伪随机数生成器。 |
| 容器 | container/heap:堆操作。 container/list:双向链表。 container/ring:环形链表 |
| 并发 | sync:提供基本的同步原语,如互斥锁、读写锁等。 sync/atomic:提供原子操作。 |
| 文件操作 | path/filepath:文件路径操作。 io/ioutil:一些常用的I/O操作,如读取文件到内存等。 |
| 网络编程 | net:底层的网络接口,提供网络相关的底层操作,如TCP、UDP等。 net/http:HTTP客户端和服务端的实现,提供构建HTTP服务的功能。 net/rpc:RPC(远程过程调用)的实现。 |
| 加密 | crypto:通用的加密包,包含常见的加密算法接口。 crypto/md5 、crypto/sha256等:具体的加密算法实现。 |
| 数据编码 | encoding/json:JSON数据编码和解码。 encoding/xml:XML数据编码和解码。 encoding/base64:Base64编码和解码。 |
| 数据库 | database/sql:SQL数据库的接口。 database/sql/driver:SQL驱动的接口规范。 |
| 压缩 | compress/gzip:gzip格式的压缩和解压缩。 compress/zlib:zlib格式的压缩和解压缩。 |
| 日志 | log:简单的日志记录包,提供基本的日志记录功能。 |
| 测试 | testing:用于编写测试代码。 testing/quick:用于编写快速测试的包。 |
| 工具 | flag:命令行参数解析。 reflect:反射,允许在运行时检查类型和变量。 runtime:与Go运行时系统交互的操作,如垃圾回收和协程操作。 |
| 其他... | text/template:用于生成文本输出的模板。 html/template:用于生成安全的HTML输出的模板,防止XSS攻击。 pprof:性能分析工具,用于CPU、内存、goroutine的分析。 goget :Go内置的依赖管理工具,通过 go get
下载和安装包。 plugin:动态加载Go插件(*.so 文件),允许在运行时加载共享对象并使用其中的符号。 |
4.3 本地导入
导出标识符规则
在Go语言中,标识符(如变量名、函数名、结构体名等)的首字母大小写非常重要,它决定了该标识符的可见性:
- 首字母大写: 表示该标识符是公开的(public),可以被其他包访问和使用。
- 首字母小写: 表示该标识符是私有的(private),只能在定义它的包内部使用。
这个规则适用于包级别的变量、函数、结构体、接口等。
例如,下方的目录结构中,main文件夹下有main.go文件,mypackage文件夹下有mypackage.go文件
bash
go_example/
├── main/main.go
└── mypackage/mypackage.go
└── go.mod # 无需手动创建
- 创建包文件
在 "main" 文件夹中创建一个名为 "main.go" 的文件
Go
// main/main.go
package main
import (
"fmt"
"go_example/mypackage"
)
func main() {
mypackage.SayHello("Alice")
fmt.Println(mypackage.PublicVar)
// 下面的代码会导致编译错误,因为它们是私有的
// mypackage.sayPrivate()
// fmt.Println(mypackage.privateVar)
}
在 "mypackage" 文件夹中创建一个名为 "mypackage.go" 的文件
Go
// mypackage/mypackage.go
package mypackage
import "fmt"
// SayHello is an exported function
func SayHello(name string) {
fmt.Printf("Hello, %s! This is a function from mypackage.\n", name)
sayPrivate()
}
// sayPrivate is a private function
func sayPrivate() {
fmt.Println("This is a private function in mypackage.")
}
// PublicVar is an exported variable
var PublicVar = "I'm a public variable in mypackage"
// privateVar is a private variable
var privateVar = "I'm a private variable in mypackage"
- 初始化 Go Module:
- 打开命令提示符或终端
- 导航到 "go_example" 文件夹
- 运行以下命令初始化 Go Module:
bash
go mod init go_example
go mod tidy
构建并运行程序
- 在命令提示符或终端中,确保您仍在 "go_example" 文件夹中
- 运行以下命令来构建和运行程序:
Go
go run main/main.go
如果一切设置正确,应该看到类似以下的输出:
Go
Hello, Alice! This is a function from mypackage.
This is a private function in mypackage.
I'm a public variable in mypackage
4.4 远程导入
我们经常使用的包都是标准库中的包,也就是本地包。Go语言不仅支持我们调用本地包,还 支持调用远程服务器的包,例如托管在GitHub上的一个非常热门的Web框架gin,我们就可以通过 远程导入的方式将其导入使用。
前提条件
- 确保你的Go版本 >= 1.11(为了使用Go Modules)
- 项目位于GOPATH之外(推荐,但不是必须)
1. 初始化Go模块 如果你的项目还没有使用Go Modules,首先初始化:这会在你的项目根目录创建一个go.mod
文件。
Go
go mod init your_project_name
2. 在代码中导入远程包 在你的Go文件中,使用完整的包路径导入远程包:
bash
import "github.com/gin-gonic/gin"
- 在导入之前还需要使用go get命令下载远程包:
bash
go get github.com/gin-gonic/gin
以下是gin官方提供的一个框架入门的例子:
Go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
运行后,就启动了一个HTTP服务器,并且可以看到输出了如下提示信息:
bash
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
浏览器访问http://localhost:8080/ping后可以看到服务器返回如下消息(关于gin的这个Web框架,这里只作简要说明)
bash
{
"message": "pong"
}
此时控制台会输出如下信息,表示有人请求了我们的http服务:
4.5 别名导入
- 当包名很长或不够直观时,提供更简短或更有意义的名称
- 用于解决包名冲突:当两个包有相同的名称时,为导入的包指定一个别名
Go
import alias_name "package_name"
fmt包的别名print和time包的别名date。需要注意的是,定义了别名后,就不能再使用该包原本的名字来调用它,只能使用它的别名来调用,运行代码示例如下:
Go
package main
import (
print "fmt"
date "time"
)
func main() {
now := date.Now()
print.Printf("当前日期为: %d.%02d.%02d %02d:%02d:%02d\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second())
}
4.6 匿名导入
在Go语言中,如果导入了一个包而不使用,在编译时会产生"unused import"的编译错误。有 时我们可能需要导入一个包,但不会引用到这个包的标识符,在这种情况下可以使用包的匿名导 入的方式,就是使用下划线"_"来重命名该包。
- 导入包但不直接使用其功能
- 触发包的初始化函数(init())
- 注册包的副作用
Go
import _ "package_name"
不使用包里的函数,主要是为了调用该包中的init函数。 例如当我们想分析一个Web应用程序的性能时,导入net/http/pprof包即可,该包的init函数主要 做了路由匹配,具体如下:
Go
func init() {
// 设置 pprof 路由
http.HandleFunc("/debug/pprof/", Index)
http.HandleFunc("/debug/pprof/cmdline", Cmdline)
http.HandleFunc("/debug/pprof/profile", Profile)
http.HandleFunc("/debug/pprof/symbol", Symbol)
http.HandleFunc("/debug/pprof/trace", Trace)
匿名导入了性能分析包pprof后就可以对该Web 程序进行性能分析;之后浏览器访问**http://localhost:8080/debug/pprof/**就可以查看相关的性能参数
Go
package main
import (
"encoding/json"
"log"
"net/http"
_ "net/http/pprof" // 匿名导入 pprof
)
func main() {
// 处理 /ping 请求
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
response := map[string]string{"message": "hello world"}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
// 启动服务器
log.Println("Server starting on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
Go语言中的数据库操作也使用了相同的方法,让用户导入程序所必需的驱动,例如MySQL的 驱动包, 该包的init函数所做的事就是注册MySQL驱动。
安装MySQL驱动 在终端执行:
Go
go get -u github.com/go-sql-driver/mysql
注意事项:
- 确保替换dbname中的用户名、密码、主机地址和数据库名为你的实际配置。
- 如果连接成功,你会看到 "成功连接到MySQL数据库!" 的输出。
- 记得处理可能出现的错误。
Go
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" // 匿名导入MySQL驱动
)
func main() {
// 配置数据库连接信息
dbname := "root:password@tcp(127.0.0.1:3306)/dbname"
// 打开数据库连接
db, err := sql.Open("mysql", dbname)
if err != nil {
fmt.Println("数据库连接失败:", err)
return
}
defer db.Close() // 确保在函数结束时关闭数据库连接
// 测试连接
err = db.Ping()
if err != nil {
fmt.Println("数据库ping失败:", err)
return
}
fmt.Println("成功连接到MySQL数据库!")
// 这里可以添加其他数据库操作
// 例如: 查询、插入、更新等
}