常用命令
- go get -u .
- go mod tidy
go mod tidy:清理和验证依赖项,确保 go.mod 和 go.sum 的准确性。
go get -u .:更新所有依赖项到最新版本。
操作目标:
go mod tidy:移除未使用的依赖项,添加缺失的依赖项,并更新校验和信息。
go get -u .:将现有依赖项更新到最新的次要版本或修正版本。
文件更新:
go mod tidy:主要更新 go.mod 和 go.sum,确保它们的准确性。
go get -u .:除了更新 go.mod 和 go.sum,还会下载和编译更新后的依赖项。
使用建议
在确保项目依赖的正确性和清理未使用的依赖项时,使用 go mod tidy。
在希望获得依赖项的最新功能和修复时,使用 go get -u . 来更新依赖。
常用技巧
【建议1】对于少量数据,无需传递指针;对于大量数据的 struct 可以考虑使用指针;传入的参数是 map,slice,chan时不传递指针,因为 map,slice,chan 是引用类型,不需要传递指针的指针。
type S struct {
data string
}
func (s S) Read() string {
return s.data
}
func (s *S) Write(str string) {
s.data = str
}
sVals := map[int]S{1: {"A"}}
// 你只能通过值调用 Read
sVals[1].Read()
// 这不能编译通过:
// sVals[1].Write("test")
sPtrs := map[int]*S{1: {"A"}}
// 通过指针既可以调用 Read,也可以调用 Write 方法
sPtrs[1].Read()
sPtrs[1].Write("test")
【建议2】true/false求值 ,当明确变量expr为bool类型时,禁止使用==或!=与true/false比较,应该使用expr或!expr;判断某个整数表达式expr是否为零时,禁止使用!expr,应该使用expr == 0
【建议3】nil操作( fmt.Println(nil == nil) 返回true or false ?)
1).nil的数据类型
零值(zero value):Go语言中每个类型都有一个零值,这是该类型的默认值,根据类型的不同而不同。例如,对于基本数据类型,其零值是0(数字类型)、‘’(字符串)、false(布尔类型)。对于数组和结构体,其零值是每个元素或字段的零值。对于接口,其零值是nil。
空值(nil):在Go语言中,nil是一个预定义的标识符,用于表示指针、通道(channel)、映射(map)、切片(slice)、函数以及接口类型的“零值”。它相当于这些类型的“无”或“不存在”。例如,一个nil指针不指向任何内存地址,而一个nil通道不连接任何发送者或接收者。
● 单独的一个nil值本身没有类型,只有通过上下文,判断其赋值对象,才能判断其类型
● 不同类型的nil值无法进行比较,不同类型的nil值,内存大小也不一样
● nil其实包含两个值,分别是type和data,nil会被间接转换成其动态类型和值。即便两个接口都被设为 nil,这两个接口的类型信息却可能不相同,从而导致比较出错。
func main() {
var p *struct{} = nil
fmt.Println(unsafe.Sizeof(p)) // 8
var s []int = nil
fmt.Println(unsafe.Sizeof(s)) // 24
var m map[int]bool = nil
fmt.Println(unsafe.Sizeof(m)) // 8
var c chan string = nil
fmt.Println(unsafe.Sizeof(c)) // 8
var f func() = nil
fmt.Println(unsafe.Sizeof(f)) // 8
var i interface{} = nil
fmt.Println(unsafe.Sizeof(i)) // 16
fmt.Println(p == s) //报错,mismatched types *struct{} and []int
}
2). 在 Golang 中,你不能将 nil 直接赋值给非接口类型。
3). 切片判空慎用nil
func F() {
// 定义变量
var s []string
fmt.Printf("1:nil=%t\n", s == nil) // true
// 组合字面量方式
s = []string{}
fmt.Printf("1:nil=%t\n", s == nil) // false
// make方式
s = make([]string, 0)
fmt.Printf("1:nil=%t\n", s == nil) // false
}
---------------------
func A()[]string{
...
return nil
}
func B(){
c := A()
// 判断c是否是空数组
if c == nil {} // 不推荐
if len(c) == 0 {} // 推荐
}
4).any、interface{}和nil判断
func F(){
var x *string
var y *string
BothNil := func(a any, b any) bool {
return a == nil && b == nil
}
BothNilInterface := func(a interface{}, b interface{}) bool {
return a == nil && b == nil
}
fmt.Println(x == nil && y == nil)
fmt.Println(BothNil(nil, nil))
fmt.Println(BothNil(x, y))
fmt.Println(BothNilInterface(nil, nil))
fmt.Println(BothNilInterface(x, y))
}
因为any/interface{}数据,其定义中不仅包含其所代表的值,同样还有其代表值的类型。
直接使用any/interface{}做nil判断,不仅需要判断data是否为nil,
还需要判断其类型是否为空(类型只要被赋值interface{},一般就不会为空)
环境配置
goland import依赖标红 Project不显示项目目录
尝试了n种方法失败,最后发现可能真的是goland编辑器的问题。退出goland,切换到项目目录将.idea整个目录删除,重启goland重建.idea。此时项目目录完整了,依赖文件也正常了。
1.下载载MAC版,安装路径:/usr/local
修改配置:vi ~/.bash_profile, source ~/.bash_profile
整体的环境变量
# 1、配置go运行环境
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
# 2、配置go工作目录,并加bin到path
export GOPATH=/home/work
export PATH=$PATH:$GOPATH/bi
GOROOT
其实就是golang 的安装路径,当你安装好golang之后其实这个就已经有了
GOPATH,设置工作目录路径GOPATH(根据自己喜好新建文件夹即可),新建src 、pkg 、bin 三个目录
作用:存放sdk以外的第三方类库,自己收藏的可复用的代码
- src存放源代码(比如:.go .c .h .s等) 按照golang默认约定,go run,go install等命令的当前工作路径(即在此路径下执行上述命令)。
- pkg编译时生成的中间文件(比如:.a) golang编译包时
- bin编译后生成的可执行文件(为了方便,可以把此目录加入到 P A T H 变量中,如果有多个 g o p a t h ,那么使用 PATH 变量中,如果有多个gopath,那么使用 PATH变量中,如果有多个gopath,那么使用{GOPATH//😕/bin:}/bin添加所有的bin目录)
在go 1.12版本后,增加了go mod 来进行依赖包管理
在 Linux 或 macOS 上面,需要运行下面命令(或者,可以把以下命令写到 .bashrc 或 .bash_profile 文件中):
启用 Go Modules 功能
go env -w GO111MODULE=on
配置 GOPROXY 环境变量,以下三选一
#1. 七牛 CDN
go env -w GOPROXY=https://goproxy.cn,direct
#2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
#3. 官方
go env -w GOPROXY=https://goproxy.io,direct
确认下:
(base) aaron@B-832YLVDL-2026 go % go env|grep goproxy
GOPROXY=“https://goproxy.cn,direct”
goland 使用gomod
goland建议使用2019以上版本
file -> new project
选择vgo, 设置工程地址
配置proxy地址: https://goproxy.cn
(可选) 可以设置gopath,设置全局路径和项目专属的路径
设置方法
setting -> go -> go path
go整体目录
go_project // go_project为GOPATH目录
– bin
– myApp1 // 编译生成
– myApp2 // 编译生成
– myApp3 // 编译生成
– pkg
– src
– myApp1 // project1
– models
– controllers
– others
– main.go
– myApp2 // project2
– models
– controllers
– others
– main.go
– myApp3 // project3
– models
– controllers
– others
– main.go
参考:
https://blog.youkuaiyun.com/zxy_666/article/details/80182688
https://blog.youkuaiyun.com/comprel/article/details/108720699
自动垃圾回收
C和C++从程序性能的角度考虑,允许程序员自己管理内存,包括内存的申请和释放等等,虽然C和C++运行起来很快,但没有注意垃圾回收机制,会导致潜在的内存泄露、野指针等问题。
java和C#引入了GC机制,即程序要不需要再考虑内存回收问题,由语言特性提供垃圾回收器来回收内存,但带来的可能是程序运行效率的降低
Go没有C++中强大的指针计算功能,无需担心所指向的对象失效的问题
高级数据类型
array, slice(切片或者变长数组),map
//array 实例演示
var arr [10]int //定义一个长度为10的 int 类型的数组
arr[0] = 42 //array 数组下标是从0开始的,给数组第一个元素赋值42
arr[1] = 13//给数组第二个元素赋值13
//打印数组第一个元素
fmt.Printf("数组 arr 第一个元素是 %d\n", arr[0])
//打印数组最后一个元素,arr[9]因为没有初始化,输出 int 类型的零值 0
fmt.Printf("数组 arr 最后一个元素是 %d\n", arr[9])
//slice 实例演示
var arr = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
var aSlice, bSlie []byte
aSlice = arr[:3] // a b c
aSlice = arr[5:] //f g h i j
aSlice = arr[:] // a b c d e f g h i j
aSlice = arr[3:7] // d e f g, 长度 len=4, 容量 cap=10-3
bSlice = aSlice[1:3] // e f
//使用 string 类型的 key,int类型的 value,可以使用 make 初始化
var numbers map[string]int
//定义并且使用 make 初始化
numbers := make(map[string]int)
//初始化后可以赋值
numbers["one"] = 1
numbers["two"] = 2
numbers["three"] = 3
//获取 map 某个 key 的 value
fmt.Println(numbers["three"])
make 和 new 的区别
make 是 go 内置的一个方法,用于为 map,slice,channel 等内置类型分配内存,new可以为所有类型分配
new(T)给 T 分配零值内存,返回零值的内存地址,其实就是 *T,更详细的说,就是返回一个指针,指针指向的是 T 的零值
new 返回指针
make 主要用于 slice map channel,返回初始化后的 T。原因在于 map slice channel底层的数据必须在初始化后才能指向他们。举个例子,slice 包涵了一个指针,这个指针实际指向的是另外一个结构(包涵指针 长度 容量),在这些数据初始化之前 slice 是 nil,因此 slice,map,channel使用 make为他们底层的数据结构赋值合适的初始值
make 返回 非零值
go 参数传递
值传递就是函数传递的是传进来参数的一个副本。换个说法就是函数内部修改传入参数的值是函数外部传入值得一个拷贝,所以你在函数内部对这个值进行修改也不会影响外部该参数的值,的确很难表达,看个案例:
package main
import "fmt"
func modify(a int){
a = 10
}
func main(){
a := 20
fmt.Println(a)
modify(a)
fmt.Println(a)
}
输出:
20
20
很直观,参数a在进入函数后并没有被改变,我们还可以看看两个内存地址是否相同
package main
import "fmt"
func modify(a int){
fmt.Println(&a)
a = 10
}
func main(){
a := 20
fmt.Println(&a)
modify(a)
}
输出为:
0xc000052080
0xc000052088
可以看到两个内存地址并不一样,这就对了,可以说明modify中的参数a,并不是主函数中的变量a,而是a的一个拷贝而已,所以在函数中修改的也是拷贝a的值,对函数外的这个a并没有改动。
这个时候,我们就会使用指针来修改值,指针案例:
func main() {
i:=10
ip:=&i
fmt.Printf("原始指针的内存地址是:%p\n",&ip)
modify(ip)
fmt.Println("int值被修改了,新值为:",i)
}
func modify(ip *int){
fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)
*ip=1
}
输出:
原始指针的内存地址是:0xc000006028
函数里接收到的指针的内存地址是:0xc000006038
int值被修改了,新值为: 1
可以看到两次内存地址也是不一样的,所以其实指针也是值传递,但是指针保存的是内存地址,而在函数中修改语句"*ip=1",其实是在修改变量i内存地址对应的值,那自然就是能够修改的。
Go语言其实并没有引用传递,上面的例子可以证明如果内存地址不同自然就是值传递,那内存地址相同就是引用传递。
summary
确认Go语言中所有的传参都是值传递。但是传递的类型如果是int、string、struct等这些,那在函数中无法修改原参数内容数据;
如果是指针、map、slice、chan等这些,在函数中可以修改原参数内容数据。
反射
反射是使用TypeOf()和ValueOf()获取对象类型的相关信息,并动态操作对象,提高程序灵活性,反射最常见的使用场景是做对象的序列化
异常捕获
defer
类似于C++中的析构函数,但析构的不是对象,而是执行函数,defer用来添加函数结束时执行的语句
panic
接收任何值
当函数执行的时候panic了,函数停止了,运行时并不是立刻向上传递panic,而是到defer,等defer执行完毕,panic再向上传递,此时的defer有点类似finally
recover
继续上面的情况,可以在defer中调用recover捕获到当前的panic,同时恢复程序
type DataType uint16
func (ty DataType) String() string {
switch ty {
case Rtp:
return “RTP”
case Rtcp:
return “RTCP”
case Rtsp:
return “RTSP”
default:
var msg = “unknown data type, maybe to be implemented in future”
panic(msg)
}
}
//Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。
//在Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为0了)。才使用Go中引入的Exception处理:defer, panic, recover。
//Go没有异常机制,但有panic/recover模式来处理错误
//Panic可以在任何地方引发,但recover只有在defer调用的函数中有效
func test() {
// 必须要先声明defer,否则不能捕获到panic异常,也就是说要先注册函数,后面有异常了,才可以调用
defer func() {
if err := recover(); err != nil {
fmt.Println("终于捕获到了panic产生的异常:", err) // 这里的err其实就是panic传入的内容
fmt.Println("我是defer里的匿名函数,我捕获到panic的异常了,我要recover,恢复过来了。")
}
}() //注意这个()就是调用该匿名函数的,不写会报expression in defer must be function call
// panic一般会导致程序挂掉(除非recover) 然后Go运行时会打印出调用栈
panic("我要抛出一个异常了")
fmt.Println("end")
}
Struct关键字,无复杂的OOP概念,没有继承和重载,类似C
注意:小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。
同时这个规则也适用于变量的可见性,即首字母大写的变量才是全局的。
package core
import (
"errors"
"fmt"
"git***.com/citybrain-traffic/ninja/fs"
"gopkg.in/yaml.v2"
"io/ioutil"
)
type ConfigInfo struct {
Http struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"http"`
Rtsp struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
RtpPortMin int `yaml:"rtp_port_min"`
RtpPortMax int `yaml:"rtp_port_max"`
} `yaml:"rtsp"`
Log struct {
Level string `yaml:"level"`
} `yaml:"log"`
Authorization struct {
Enable bool `yaml:"enable"`
} `yaml:"authorization"`
Release bool
}
func (ci *ConfigInfo) String() string {
return fmt.Sprintf("[http: %v:%v][rtsp:%v:%v][log: %v][release:%v]", ci.Http.Host,
ci.Http.Port,
ci.Rtsp.Host,
ci.Rtsp.Port,
ci.Log.Level,
ci.Release)
}
func FromConfigFile(file string) (err error, config *ConfigInfo) {
if 0 == len(file) || !fs.IsPathExist(file) {
err = errors.New(fmt.Sprintf("配置文件不存在: [%s]", file))
config = nil
return err, nil
}
var content []byte
content, err = ioutil.ReadFile(file)
if nil != err {
return
}
config = new(ConfigInfo)
if err = yaml.Unmarshal(content, &config); err != nil {
return
}
return
}
var Configuration *ConfigInfo = nil
引用的时候:先导入 config *core.ConfigInfo
高并发
https://segmentfault.com/a/1190000018150987
一、Go并发模型
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。
CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。
请记住下面这句话:
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。
Go的CSP并发模型,是通过goroutine和channel来实现的。
goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
生成一个goroutine的方式非常的简单:Go一下,就生成了。
go f();
通信机制channel也很方便,传数据用channel <- data,取数据用<-channel。
在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。
而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。
示例如下:
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}
注意 main()本身也是运行了一个goroutine。
messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道
chan 是关键字 代表我要创建一个通道
当一个函数创建为goroutine时,会被调度到可用的逻辑处理器上执行
Go的并发同步基于通信顺序进程(CSP),通过在goroutine之间传递数据来传递消息,而不是通过枷锁实现同步访问
用于goroutine之间同步和传递数据的关键数据是通道(channel)
下图是 线程、goroutine、逻辑处理器之间的工作关系
web框架
Golang Web框架
使用gin接受post的json数据
第一种
func Login(c *gin.Context) {
json := make(map[string]interface{}) //注意该结构接受的内容
c.BindJSON(&json)
log.Printf("%v",&json)
c.JSON(http.StatusOK, gin.H{
"name": json["name"],
"password": json["password"],
})
}
第二种
type User struct {
Name string `json:"name"`
Password int64 `json:"password"`
}
func Login(c *gin.Context) {
json := User{}
c.BindJSON(&json)
log.Printf("%v",&json)
c.JSON(http.StatusOK, gin.H{
"name": json.Name,
"password": json.Password,
})
}
interface{}类型其实是个空接口,即没有方法的接口。go的每一种类型都实现了该接口。因此,任何其他类型的数据都可以赋值给interface{}类型。
Marshal—将数据编码成json字符串
Unmarshal—将json字符串解码到相应的数据结构
go 语言中的 数组是类型相同的元素的集合,
列表是双链表的容器, 可以添加不同类型的数据
切片是对现有数组的引用, 比数组更方便灵活, 还可以追加数据