Go学习总结

由于公司的业务需要,我跟着公司的大神学习了Go语言,Go高并发性能使其慢慢变成了服务端开发的主流语言,在做Go开发的过程中我也深深被其强大所吸引。下面主要介绍一下我学习的重点。

配置环境变量

MacOS

  • 官网直接下载对应操作系统的安装包, 点击安装即可(也可以下载源解压).

  • 安装包安装完成之后配置环境变量. 具体步骤如下

    1. 打开 .bash_profile 文件(目录路径: ~/)

    2. 复制如下配置到 .bash_profile 中

      1
      2
      export GOROOT=/usr/local/go
      export PATH=$PATH:$GOROOT/bin
      
  • 执行 source ~/.bash_profile 让配置生效

查看go的版本

  • go version 如果输出 go version gox.x.x darwin/amd64, 说明go的完成以上

查看go的环境配置

  • go env

如果go env有正确的输出, 那么go的安装和环境配置就完成了.

创建第一个 go 项目

终端

  • 选择一个目录, 新建一个hello.go文件, 然后在其中写入如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      //包名一定要命名为main, 并且要包含main函数, 否则无法运行
      package main
        
      import (
        "fmt"
      )
         
      func main() {
        fmt.Println("hello");
      }
    

    注意: ==包名(package)一定要命名为main, 并且要包含main函数, 否则无法运行==

GoLand

  • 使用GoLand创建一个新的项目

  • 然后作相应的配置, 配置好之后如下图:

    conf

  • 配置路径,具体配置方式请参考点击查看

配置完之后就可以点击运行了.

包的引用

包引用时的寻找路径为根目录下的 src 文件件, 所以在构建工程的时候最好的办法是构建一个 src 文件夹, 用来存放所有自己编写的代码.

第三方包可以在 gopath中配置多个目录, 然后在该目录新建src文件夹, 在此文件夹中存放第三方的包

细小知识点

  1. 每一个文件夹代表一个包, 这个文件夹下面的所有报名必须一致

godoc

log

设置log文件路径:

logfile, err := os.OpenFile(“test.log”, os.O_CREATE | os.O_WRONLY | os.O_APPEND, os.ModePerm) if err != nil { log.Fatalln(“file open failed”) }

1
2
3
	log.SetOutput(logfile)
	logger := log.New(logfile,"",log.Ldate | log.Llongfile,)
	logger.Println("hahahahahah")

文件读写

读写模式

代码 含义
os.O_RDONLY 以只读的方式打开
os.O_WRONLY 以只写的方式打开
os.O_RDWR 以读写的方式打开
os.O_NONBLOCK 打开时不阻塞
os.O_APPEND 以追加的方式打开
os.O_CREAT 创建并打开一个新文件
os.O_TRUNC 打开一个文件并截断它的长度为零(必须有写权限)
os.O_EXCL 如果指定的文件存在,返回错误
os.O_SHLOCK 自动获取共享锁
os.O_EXLOCK 自动获取独立锁
os.O_DIRECT 消除或减少缓存效果
os.O_FSYNC 同步写入
os.O_NOFOLLOW 不追踪软链接

读写权限

ModeDir FileMode = 1 « (32 - 1 - iota) // d: is a directory 文件夹模式 ModeAppend // a: append-only 追加模式 ModeExclusive // l: exclusive use 单独使用 ModeTemporary // T: temporary file (not backed up) 临时文件 ModeSymlink // L: symbolic link 象征性的关联 ModeDevice // D: device file 设备文件 ModeNamedPipe // p: named pipe (FIFO) 命名管道 ModeSocket // S: Unix domain socket Unix 主机 socket ModeSetuid // u: setuid 设置uid ModeSetgid // g: setgid 设置gid ModeCharDevice // c: Unix character device, when ModeDevice is set Unix 字符设备,当设备模式是设置Unix ModeSticky // t: sticky 粘性的 // Mask for the type bits. For regular files, none will be set. bit位遮盖.不变的文件设置为none ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 // Unix permission bits 权限位.

文件写入的模式

命令行参数

所属包: os 获取命令行参数(切片) os.Args. (注意: Args[0] 就是执行的命令本身。) 命令行传递参数: go run xxx.go "参数1" "参数2" ...

go 命令行

描述 指令 参数
标准库函数列表 go doc xxx xxx代表库名
当前工程的环境配置 go env  
执行go的可执行文件 go run xxx.go xxx.go必须包含main函数

快捷键

描述 快捷键
查看api提示 ctrl + J
跳转api源代码 cmd + B
代码格式化 opt + cmd + L
代码块向上移动一行 shift + cmd + ↑
代码块向下移动一行 shift + cmd + ↓

go输出占位符

普通占位符

占位符 说明 举例 输出
%v 相应值的默认格式。 Printf(“%v”, people) {zhangsan}
%+v 打印结构体时,会添加字段名 Printf(“%+v”, people) {Name:zhangsan}
%#v 相应值的Go语法表示 Printf(“#v”, people) main.Human{Name:”zhangsan”}
%T 相应值的类型的Go语法表示 Printf(“%T”, people) main.Human
%% 字面上的百分号,并非值的占位符 Printf(“%%”) %

布尔占位符

占位符 说明 举例 输出
%t true 或 false。 Printf(“%t”, true) true

整数占位符

占位符 说明 举例 输出
%b 二进制表示 Printf(“%b”, 5) 101
%c 相应Unicode码点所表示的字符 Printf(“%c”, 0x4E2D)
%d 十进制表示 Printf(“%d”, 0x12) 18
%o 八进制表示 Printf(“%d”, 10) 12
%q 单引号围绕的字符字面值,由Go语法安全地转义 Printf(“%q”, 0x4E2D) ‘中’
%x 十六进制表示,字母形式为小写 a-f Printf(“%x”, 13) d
%X 十六进制表示,字母形式为大写 A-F Printf(“%x”, 13) D
%U Unicode格式:U+1234,等同于 “U+%04X” Printf(“%U”, 0x4E2D) U+4E2D

浮点数和复数的组成部分(实部和虚部)

占位符 说明 举例 输出
%b 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat 的 ‘b’ 转换格式一致。例如 -123456p-78 - -
%e 科学计数法,例如 -1234.456e+78 Printf(“%e”, 10.2) 1.020000e+01
%E 科学计数法,例如 -1234.456E+78 Printf(“%e”, 10.2) 1.020000E+01
%f 有小数点而无指数,例如 123.456 Printf(“%f”, 10.2) 10.200000
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 Printf(“%g”, 10.20) 10.2
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 Printf(“%G”, 10.20+2i) (10.2+2i)

字符串与字节切片

占位符 说明 举例 输出
%s 输出字符串表示(string类型或[]byte) Printf(“%s”, []byte(“Go语言”)) Go语言
%q 双引号围绕的字符串,由Go语法安全地转义 Printf(“%q”, “Go语言”) “Go语言”
%x 十六进制,小写字母,每字节两个字符 Printf(“%x”, “golang”) 676f6c616e67
%X 十六进制,大写字母,每字节两个字符 Printf(“%X”, “golang”) 676F6C616E67

指针

占位符 说明 举例 输出
%p 十六进制表示,前缀 0x Printf(“%p”, &people) 0x4f57f0

引用类型和值类型

  • 值类型int、float、bool,string, array(数组)和(struct)结构体, 这些类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中。当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝。可以通过 &i 获取变量 i 的内存地址。  值拷贝

  • 引用类型:特指slice、map、channel这三种预定义类型。引用类型拥有更复杂的存储结构:

    • (1)分配内存
    • (2)初始化一系列属性等一个引用类型的变量r1存储的是r1的值所在的内存地址(数字),或内存地址中第一个字所在的位置,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中。  

go中单引号和双引号的区别

  • 单引号是 byte 类型 ‘\n’
  • 双引号是字符穿类型 “\n”

goland 文件头注释

打开GoLand的setting选项 依次选择Editor,CodeStyle ,File and Code Templates ,Go File

根据自己需要添加即可

1
2
3
4
5
6
7
/*
@Time : ${DATE} ${TIME} 
@Author : ${USER}
@File : ${NAME}
@Software: ${PRODUCT_NAME}
*/
package ${GO_PACKAGE_NAME}

go linux打包命令

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build xxx

go windows打包命令

CGO_ENABLED=1 GOOS=windows GOARCH= go build xxx

go调用JS

  1. 下面这个原生的Go库可以解析JavaScript
  2. 使用cgo调用V8引擎
  3. 使用goja

goja性能比otto快6-7倍

time包

time包里面主要包含有 timer 和 ticker,

  • 计时器(Timer)的原理和倒计时闹钟类似,都是给定多少时间后触发。
  • 打点器(Ticker)的原理和钟表类似,钟表每到整点就会触发。这两种方法创建后会返回

time.Ticker 对象和 time.Timer 对象,里面通过一个 C 成员,类型是只能接收的时间通道(<-chan Time),使用这个通道就可以获得时间触发的通知. 案例如下:

1
2
3
4
5
6
7
//多路通道
select{
    case <-timer.C:
        //计时器触发是调用
    case <-ticker.C
        //打点器触发是调用
}

mqtt使用方式

多个类型可以实现相同的接口

==一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的==

Service 接口定义了两个方法:一个是开启服务的方法(Start()),一个是输出日志的方法(Log())。使用 GameService 结构体来实现 Service,GameService 自己的结构只能实现 Start() 方法,而 Service 接口中的 Log() 方法已经被一个能输出日志的日志器(Logger)实现了,无须再进行 GameService 封装,或者重新实现一遍。所以,选择将 Logger 嵌入到 GameService 能最大程度地避免代码冗余,简化代码结构。详细实现过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一个服务需要满足能够开启和写日志的功能
type Service interface {
    Start()  // 开启服务
    Log(string)  // 日志输出
}
// 日志器
type Logger struct {
}
// 实现Service的Log()方法
func (g *Logger) Log(l string) {
}
// 游戏服务
type GameService struct {
    Logger  // 嵌入日志器
}
// 实现Service的Start()方法
func (g *GameService) Start() {
}

代码说明如下:

  • 第 2 行,定义服务接口,一个服务需要实现 Start() 方法和日志方法。
  • 第 8 行,定义能输出日志的日志器结构。
  • 第 12 行,为 Logger 添加 Log() 方法,同时实现 Service 的 Log() 方法。
  • 第 17 行,定义 GameService 结构。
  • 第 18 行,在 GameService 中嵌入 Logger 日志器,以实现日志功能。
  • 第 22 行,GameService 的 Start() 方法实现了 Service 的 Start() 方法。

此时,实例化 GameService,并将实例赋给 Service,代码如下:

1
2
3
var s Service = new(GameService)
s.Start()
s.Log(“hello”)

s 就可以使用 Start() 方法和 Log() 方法,其中,Start() 由 GameService 实现,Log() 方法由 Logger 实现。

结构体实例化的三种方式

一. 基本的实例化形式

结构体本身是一种类型,可以像整型、字符串等类型一样,以 var 的方式声明结构体即可完成实例化。

基本实例化格式如下:

1
var ins T

其中,T 为结构体类型,ins 为结构体的实例。

用结构体表示的点结构(Point)的实例化过程请参见下面的代码:

1
2
3
4
5
6
7
type Point struct {
    X int
    Y int
}
var p Point
p.X = 10
p.Y = 20

在例子中,使用.来访问结构体的成员变量,如p.X和p.Y等,结构体成员变量的赋值方法与普通变量一致。

二. 创建指针类型的结构体

Go语言中,还可以使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。

使用 new 的格式如下:

1
ins := new(T)

其中: T 为类型,可以是结构体、整型、字符串等。 ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。

Go语言让我们可以像访问普通结构体一样使用.来访问结构体指针的成员。

下面的例子定义了一个玩家(Player)的结构,玩家拥有名字、生命值和魔法值,实例化玩家(Player)结构体后,可对成员进行赋值,代码如下:

1
2
3
4
5
6
7
8
type Player struct{
    Name string
    HealthPoint int
    MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300

经过 new 实例化的结构体实例在成员赋值上与基本实例化的写法一致。 Go语言和 C/C++ 在 C/C++ 语言中,使用 new 实例化类型后,访问其成员变量时必须使用->操作符。

在Go语言中,访问结构体指针的成员变量时可以继续使用.,这是因为Go语言为了方便开发者访问结构体指针的成员变量,使用了语法糖(Syntactic sugar)技术,将 ins.Name 形式转换为 (*ins).Name。

三. 取结构体的地址实例化

在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:

1
ins := &T{}

其中: T 表示结构体类型。 ins 为结构体的实例,类型为 *T,是指针类型。

下面使用结构体定义一个命令行指令(Command),指令中包含名称、变量关联和注释等,对 Command 进行指针地址的实例化,并完成赋值过程,代码如下: 纯文本复制

1
2
3
4
5
6
7
8
9
10
type Command struct {
    Name    string    // 指令名称
    Var     *int      // 指令绑定的变量
    Comment string    // 指令的注释
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"

通道

一.非阻塞接收数据

使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:

1
2
3
4
    data, ok := <-ch

    data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
    ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行

select

Go语言没有提供直接的超时处理机制,所谓超时可以理解为当我们上网浏览一些网站时,如果一段时间之后不作操作,就需要重新登录。

那么我们应该如何实现这一功能呢,这时就可以使用 select 来设置超时。

虽然 select 机制不是专门为超时而设计的,却能很方便的解决超时问题,因为 select 的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。

超时机制本身虽然也会带来一些问题,比如在运行比较快的机器或者高速的网络上运行正常的程序,到了慢速的机器或者网络上运行就会出问题,从而出现结果不一致的现象,但从根本上来说,解决死锁问题的价值要远大于所带来的问题。

select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。

与 switch 语句相比***,select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作***,大致的结构如下:

1
2
3
4
5
6
7
8
select {
    case <-chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
}

在一个 select 语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。

如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。

如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有如下两种可能的情况: 如果给出了 default 语句,那么就会执行 default 语句,同时程序的执行会从 select 语句后的语句中恢复; 如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去。


附录一

goland中使用 go get 无法下载第三方库

解决办法: 可以使用 git clone xxx 将该库下载到本地, 然后复制到对应的gopath目录中即可

goland中配置多个gopath

路径一: 第三方库都放在thirdLibs文件夹下的src目录中, 路径二: 项目根目录

如果路径二在路径一的上面配置, 那么使用 go get xxx 下载新包时, 默认会在最上面的路径中去自动创建一路库目录, 所以不会下载到路径一种, 解决办法是将路径一设置在最上面

手机通过电脑连接查看检查元素

点击获取


go性能调优工具

Go pprof性能调优

go pprof详细教程

附录二

学习资源