资讯详情

【带你上手云原生体系】第二部分:Go语言从入门到精通

第一部分:文章简介 及 云原生思维概览 第二部分:Go从入门到精通语言 第三部分:Docker从入门到精通 第四部分:Kubernetes从入门到精通


文章目录

  • 2、Go语言
    • Go 语言的原则
    • 为什么需要 Go 语言
    • Go 不支持语言的特点
    • Go语言特征衍生源
    • Go语言安装
    • 配置Go国内代理模块
    • Go的目录介绍
    • Go 常用命令
      • 基本命令
      • Go build
      • Go test
      • Go vet
    • IDE
      • File Watcher
      • 快捷键
    • Go语言依赖管理
      • 依靠管理的概念
      • 三个阶段依赖管理 GOPATH、GOVENDOR、go mod
        • GOPATH方式
        • GOVENDOR方式
        • go mod方式
          • 旧项目迁移go mod
    • main 函数
      • 获取输入参数和标志值
    • Init 函数
    • 变量
      • 标准声明
      • 批量声明
      • 定义和初始化
        • 标准
        • 多个
        • 类型推导
        • 短变量声明
        • 匿名变量_
    • 内建变量类型
      • 字符串遍历
      • 常用的字符串方法
    • 强制类型转换
    • 常量与枚举
      • 常量
      • 枚举
      • iota
    • fmt.Printf格式化输出
    • 条件语句
      • if else
      • switch
    • 循环语句
      • for
      • for range
    • 函数 func
      • 可变参数列表
    • 内置函数
    • 指针(变量取地址&、 地址取变量值*)
      • 取变量的 指针地址
      • 根据指针地址 修改变量值
      • `b := &a`的剖析:
      • 实现引用传输
    • 数组Array
      • 声明
      • 遍历
      • 数组是值类型
      • 实现引用传输
    • 切片Slice
      • 定义 及 在数组中创建切片
      • 证明Slice是引用类型
      • Slice的扩展
      • Slice底层实现
      • 向Slice添加元素
      • Slice创建、复制、删除
      • Slice的遍历
      • 将字符串切片
    • 集合Map 映射关系
      • 创建
      • 遍历
      • 获取v、判断key是否存在
      • 删除元素
      • 获取元素的数量
    • 结构体struct
      • 定义
      • 实例化
      • 实现构造函数
      • 结构定义方法(接收者)
      • 定制内置类型
    • 封装
    • 三种扩展现有类型的方式
      • 定义别名
      • 使用组合
      • 使用内嵌(Embedding)来扩展 (类似继承、重载)
    • defer
      • defer执行时机
      • defer经典案例
    • 接口interface
      • 概念
      • 声明
      • duck typing概念
      • 接口的组合
      • 任何类型的空接口都可以用作
    • 函数与闭包
      • 斐波那契数列的闭包实现
      • 斐波那契数列为函数实现接口
    • 多态
      • 概念
      • 为什么要用多态?
      • 多态有什么好处?
      • Go语言多态
    • 反射
    • 回调函数(Callback)
    • Json编解码
      • Unmarshal:从string转换至struct
      • Marshal:从 struct 转换至 string
      • json 包使用 map[string]interface{} 和 []interface{} 类型保存任何对象
    • 错误处理
    • Panic 和 recover
    • 多线程
      • 并发和并行
      • 协程
      • Go语言多线程模型:通信顺序处理Communicating Sequential Process
      • 线程与协程的差异
      • 协程示例
      • channel - 多线程通信
      • 通道缓冲
      • 使用遍历通道缓冲区for rang方式
      • 单向通道
      • 关闭通道
      • select
      • 定时器 Timer
      • 上下文 Context
      • 如何停止一个子协程
      • 基于 Context 停止子协程
    • 线程加(Sync包)
      • 理解线程安全
        • Mutex示例
          • Kubernetes 中的 informer factory
        • RWMutex示例
        • WaitGroup 示例
          • Kubernetes 中的 端对端测试的源码
        • Once示例
        • Cond示例
          • Kubernetes 中的队列,标准的生产者消费者模式
    • 线程调度
      • Linux内核原理
      • Linux 进程的内存使用
      • CPU 对内存的访问
      • 进程切换开销
      • 线程切换开销
      • 用户线程
      • Goroutine
      • MPG的对应关系
      • GMP 模型细节
      • P的状态
      • G的状态
      • G的状态转换图
      • G 所处的位置
      • Goroutine 创建过程
      • 将 Goroutine 放到运行队列上
      • 调度器行为
    • 内存管理
      • 关于内存管理的争论
      • 堆内存管理
      • 堆内存管理的挑战
      • ThreadCacheMalloc 概览(TCMalloc)
      • Go 语言内存分配
      • 内存回收
      • mspan
      • GC 工作流程
      • 三色标记
      • 垃圾回收触发机制
    • 网络
      • 理解网络协议层
      • 理解Socket
      • 理解 net.http 包
      • 阻塞 IO 模型
      • 非阻塞 IO 模型
      • IO 多路复用
      • 异步IO
      • Linux epoll
      • Go 语言高性能 httpserver 的实现细节
    • 调试
      • debug
      • dlv 的配置
      • 更多 debug 方法
      • Glog 使用方法示例
      • 性能分析(Performance Profiling)
        • 分析 CPU 瓶颈
        • 其他可用 profiling 工具分析的问题
      • 针对 http 服务的 pprof
        • 分析 go profiling 结果
        • 结果分析示例

2、Go语言

Go 语言的原则

  • “Less is exponentially more“ – Rob Pike, Go Designer
  • “Do Less, Enable More” – Russ Cox, Go Tech Lead

为什么需要 Go 语言

  • 其他编程语言的弊端。

    • 硬件发展速度远远超过软件。

    • C 语言等原生语言缺乏好的依赖管理 (依赖头文件)。

    • Java 和 C++ 等语言过于笨重。

    • 系统语言对垃圾回收和并行计算等基础功能缺乏支持。

    • 对多核计算机缺乏支持。

  • Go 语言是一个可以编译高效,支持高并发的,面向垃圾回收的全新语言。

    • 秒级完成大型程序的单节点编译。

    • 依赖管理清晰。

    • 不支持继承,程序员无需花费精力定义不同类型之间的关系。

    • 支持垃圾回收,支持并发执行,支持多线程通讯。

    • 对多核计算机支持友好。

Go 语言不支持的特性

  • 不支持函数重载和操作符重载

  • 为了避免在 C/C++ 开发中的一些 Bug 和混乱,不支持隐式转换

  • 支持接口抽象,不支持继承

  • 不支持动态加载代码

  • 不支持动态链接库

  • 通过 recover 和 panic 来替代异常机制

  • 不支持断言

  • 不支持静态变量

Go语言特性衍生来源

在这里插入图片描述

Go语言安装

  • 官网:golang.org
  • 国内下载:studygolang.com
  • macOS:brew install go
    • 更新Go:brew upgrade go

配置Go模块国内代理

goproxy.cn

直接执行下面命令配置Go国内镜像:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

检查配置是否成功:

go env

Go的目录介绍

  • GOROOT

    • go的安装目录
  • GOPATH:

    默认目录在unix、linux:~/go;windows:%USERPROFILE%\go

    • src:存放源代码,建议不管用哪种依赖管理模式,都建议把源代码放在此。
    • pkg:存放依赖包
    • bin:存放可执行文件

Go 常用命令

# 设置全局go的环境变量
go env -w GO111MODULE=on

# 设置命令端临时go的环境变量
export GO111MODULE=on

# 查看go的环境变量
go env

# 查看go的版本
go version

# go mod模式初始化:创建go.mod文件
go mod init <mygomodname>

# 安装go的依赖库(Latest release版本)
go get -u go.uber.org/zap

# 安装go的旧版本依赖库
go get -u go.uber.org/zap@v1.21

# 进行清洁(去掉go.mod文件中项目不需要的依赖,清理go.sum文件中同名依赖库的不同版本为代码使用版本)
go mod tidy

# build当前目录所有的go文件,会把可执行文件放在当前目录下。
go build

# build当前目录及所有子目录下所有的go文件,不会产生可执行文件,只是检查编译是否正确。
go build ./...

# build当前目录及所有子目录下所有的go文件,产生可执行文件,在$GOPATH下的bin目录
go install ./...

# 指定输出目录
go build –o bin/mybinary .

# 编译成linux可执行的二进制文件
GOOS=linux go build

# 代码标准化entry.go文件
go fmt entry.go

基本命令

  • bug:start a bug report

  • build:compile packages and dependencies

  • clean:remove object files and cached files

  • doc:show documentation for package or symbol

  • env:print Go environment information

  • fix:update packages to use new APIs

  • fmt:gofmt (reformat) package sources

  • generate:generate Go files by processing source

  • get:add dependencies to current module and install them

  • install:compile and install packages and dependencies

  • list:list packages or modules

  • mod:module maintenance

  • run:compile and run Go program

  • test:test packages

  • tool:run specified go tool

  • version:print Go version

  • vet:report likely mistakes in packages

Go build

  • Go 语言不支持动态链接,因此编译时会将所有依赖编译进同一个二进制文件。

  • 指定输出目录。

    • go build –o bin/mybinary .
  • 常用环境变量设置编译操作系统和 CPU 架构。是用来交叉编译的。

    • 编译成linux可执行的二进制文件:GOOS=linux go build
    • GOOS=linux GOARCH=amd64 go build
  • 全支持列表。 GOOS表示操作系统,比如windows、linux等;

    GOARCH表示平台比如amd64、386等。

    关于支持的操作系统和平台,在$GOROOT/src/go/build/syslist.go文件中有详细定义。

Go test

Go 语言原生自带测试

import "testing" 
func TestIncrease(t *testing.T) { 
         
	t.Log("Start testing")
	increase(1, 2)
}

go test ./… -v 运行测试

go test 命令扫描所有*_test.go为结尾的文件,惯例是将测试代码与正式代码放在同目录, 如 foo.go 的测试代码一般写在 foo_test.go

Go vet

IDE

GoLand

File Watcher

可以安装goimports

快捷键

调用func时,自动生成返回值

option+command+v

Go语言的依赖管理

依赖管理的概念

​ 这里的依赖指的是我们引用别人写好的库,比如uber-go/zap,主要用来在 Go 中快速、结构化、分级日志记录。

​ 所以,我们需要把别人的库拉到本地,和我们自己的代码一起build,一起工作。

依赖管理的三个阶段 GOPATH、GOVENDOR、go mod

  • GOPATH:最早的管理方法,不好用。
  • GOVENDOR:比GOPATH稍好一点,但是还是不好用。
  • go mod:在2018年底,也就是Go 1.11版本开始,把对Go的包管理做到了Go的命令里去。使用go mod逐步淘汰GOPATH、GOVENDOR。

这里说明一下:jetbrains的GoLand中创建新项目选择"Go"就是GOPATH方式了。


GOPATH方式

  • 所有的依赖都需要到GOPATH目录下去找,会造成所有的项目的依赖都放在GOPATH目录下,GOPATH会越来越大、甚至整个github都镜像到GOPATH下了。

  • GOPATH方式对结构目录有要求,名字是固定的。一定要创建src,否则依赖的库将找不到。

  • GOPATH模式会去以下目录中找依赖:

    • $GOROOT

    • $GOPATH/src

  • GOPATH只能去git clone github的tag为Latest release版本的zap库,无法做到多个项目用不同版本的zap库。(为了解决此问题,诞生了GOVENDOR)

mkdir /tmp/gopathtest
cd /tmp/gopathtest
go env
# 设置全局go的环境变量(仅限测试使用,这样会把整个系统下的GOPATH都设置为此目录。)
go env -w GOPATH=/tmp/gopathtest
# 临时设置go的GOPATH(推荐测试使用)
export GOPATH=/tmp/gopathtest
mkdir src
# 打开jetbrains的GoLand创建新项目,选择“Go”而不是“Go Modules”。Project Location设置为/tmp/gopathtest/src/project1
# 因为我们是用export方式,所以需要再配置GoLand的GOPATH为/tmp/gopathtest
# 另外,我们还要设置GO111MODULE=off
go get -u go.uber.org/zap # 会clone github的tag为Latest release版本
cd /tmp/gopathtest/src
ls # go.uber.org project1
// 先配置一下jetbrains的GoLand的Run/Debug Configurations的Environment:GO111MODULE=off
package main
import "go.uber.org/zap"
func main(){ 
         
  log, _ := zap.NewProduction()
  log.Warn("warning test")
}

GOVENDOR方式

  • 和GOPATH差不多,只是多了一个在项目文件夹内新建一个文件夹,新建完后IDE会认识这个文件夹的。

  • 会先去项目内的vendor文件夹去找依赖库,如果找不到在到$GOPATH/src下去找。

  • 为了解决我们每次go get命令下载完依赖库后手工拷贝到vendor文件夹内,大量第三方依赖管理工具针对GOVENDOR而诞生:glide、dep、go dep、…

  • 一般开发的时候我们不会手动的去动vendor目录,动的都是第三方管理工具的配置文件。例如:glide.yml

mkdir /tmp/govendortest
cd /tmp/govendortest
go env
export GOPATH=/tmp/govendortest
mkdir src
mkdir src/project1/vendor
mkdir src/project2/vendor
# 然后我们只需要把zap的所有文件夹及文件全部放在各自项目文件夹下的vendor文件夹下即可。
# 别忘了设置GO111MODULE=off
// 测试代码与GOPATH相同

go mod方式

  • 我们的项目可以建在任何地方,用户只需要专注项目即可,不必关心目录结构。
  • 初始化:go mod init 会创建go.mod文件
  • 添加依赖:go get
  • 更新依赖:go get [@v...] , go mod tidy
  • 旧项目迁移到go mod:go mod init , go build ./...
  • 在jetbrains的GoLand中选择创建go modules会自动帮我们创建一个go.mod和go.sum文件
    • go.sum是记录库的版本和hash,确保我们拉下来的是正确的,没有被篡改的。
    • go.mod文件会被go自动编辑,当我们的代码没有用到某库时,后面会写// indirect ;如果我们使用了该库,则没有// indirect 标记。
  • 由go命令统一的管理,依赖库会自动保存在$GOPATH/pkg/mod下
  • 依赖的检索逻辑是:
    1. 先去go.mod文件去查用到的库及库的版本是什么
    2. 然后去$GOPATH/pkg/mod/go.uber.org/zap@1.21.0去找
  • 如果我们要变更使用库的版本,不要手动改go.mod文件,要使用命令的方式:go get -u go.uber.org/zap@v1.11
    • 我们查看go.sum文件发现1.21和1.11都同时存在,我们使用:go mod tidy进行清洁(去掉go.mod文件中项目不需要的依赖。),清洁后发现,1.21被删除了。
旧的项目迁移到go mod
# 把当前目录以及所有子目录下所有的go文件全部build一遍,这样在build的时候碰到代码中的import就会触发go mod去拉取库放在本地。
go mod init # 创建go.mod文件
go build ./...

main 函数

Go语言入口就是package main func main是固定搭配。

  • 每个 Go 语言程序都应该有个 main package

  • Main package 里的 main 函数是 Go 语言程序入口

所以,一个项目只允许一个入口,如果是测试用,可以修改IDE的Run kind为file。

package main

import "fmt"

func main() { 
         
	fmt.Println("hello world")
}

获取输入参数和标志值

package main

import (
	"flag"
	"fmt"
	"os"
)
func main() { 
         
	/* flag.String()定义了一个带有指定名称、默认值和使用字符串的字符串标志。 返回值是存储标志值的字符串变量的地址。 os.Args保存命令行参数,并以程序名称开头。 */
	name := flag.String("name", "jenrey", "specify the name you want to say hi")
	flag.Parse()
	fmt.Println("os args is:", os.Args)
	fmt.Println("input parameter is:", *name)
	fullString := fmt.Sprintf("Hello %s from Go\n", *name)
	fmt.Println(fullString)
}

Init 函数

  • Init 函数:会在包初始化时运行

  • 谨慎使用 init 函数

  • 当多个依赖项目引用统一项目,且被引用项目的初始化在 init 中完成,并且不可重复运行时,会导致启动错误

# k8s解析参数的逻辑
kubernetes -> glog -> init() -> flag parse parameter
# k8s引用了项目a,项目a在自己的vendor目录引用了自己的glog
kubernetes -> a -> vendor -> glog -> init() -> flag parse parameter

# 综上,整个项目里glog存在于多个地方,这个时候Go在编译的时候就不认为这两个glog是一个包,所以,就会在第4行中重新做init(),然后重新的flag parse parameter
# 所以,就会发现第4行的flag在之前第2行中被定义过了,重复定义,所以最终整个项目跑不起来了
# 现在的k8s废弃了glog,用的是自己开发的klog提供了自己的log机制
  • 一般都是做一些初始化的操作时需要用init函数

  • 如果main包引用了a包,而a包又引用了b包,则在main方法执行的时候会先执行b包的init方法,然后再执行a包的init方法,最后执行main包的init方法

    package main
    
    import (
    	"fmt"
    	_ "jenrey.com/learn-go/a"
    	_ "jenrey.com/learn-go/b"
    )
    
    func init() { 
               
    	fmt.Println("main init")
    }
    func main() { 
               
    	fmt.Println("main")
    }
    
    package a
    
    import (
    	"fmt"
    	_ "jenrey.com/learn-go/b"
    )
    
    func init() { 
               
    	fmt.Println("init from a")
    }
    
    package b
    
    import "fmt"
    
    func init() { 
               
    	fmt.Println("init from b")
    }
    
    # 结果展示
    init from b
    init from a
    main init
    main
    

说明:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包,当然也就无法通过包名来调用包中的其他函数。 b没有被打印两次是因为init方法只执行一次。

变量

​ 函数外的每个语句都必须以关键字开始(var、const、func等)

标准声明

var a int
var s string
var isOK bool
var d float32

批量声明

var (
	a string = "qwe"
	b int
	c bool = true
	d float32
)

定义及初始化

标准

var name string = "Q1mi"
var age int = 18

多个

// 一次初始化多个变量
var name, age = "Q1mi", 20

类型推导

var name = "Q1mi"
var age = 18

短变量声明

:=不能使用在函数外。

// 短变量声明并初始化只能在函数内部
package main
import fmt
// 包内部变量m(Go语言没有全局变量一说)
var m = 100

func main() { 
         
	n := 10
	m := 200 // 此处声明局部变量m
	fmt.Println(m, n)
}

匿名变量_

​ 匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。

// 匿名变量(作用:想要忽略某个值)
func foo() (int, string) { 
         
	return 10, "Q1mi"
}
func main() { 
         
	x, _ := foo()
	_, y := foo()
	fmt.Println("x=", x)
	fmt.Println("y=", y)
}

内建变量类型

  • bool,string
  • (u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr
  • byte,rune
  • float32,float64,complex64,complex128
  • 特殊类型:error
  • 指针类型:*int、*int64、*string、*[10]int等

uint8:无符号8位整型(0到255)或者叫 byte 型,代表了ASCII码的一个字符。

int8:有符号8位整型(-128到127)

int:32位操作系统上就是int32,64位操作系统上就是int64

ptr:就是指针,指针的长度和操作系统相关,64位操作系统就是64位,32位操作系统就是32位。

rune:类似于其他语言的char,就是字符型。当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。rune 类型来处理 Unicode。

complex64、complex128:复数,复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

package main

import (
	"fmt"
	"math/cmplx"
)

func main() { 
         
	var c1 complex128
	c1 = 3 + 4i
	var c2 complex128
	c2 = 2 + 3i
	fmt.Println(cmplx.Abs(c1)) // 5
	fmt.Println(cmplx.Abs(c2)) //3.6055512754639896
}

字符串遍历

var s string = "Yes你好啊!"
for i, ch := range []rune(s) { 
         
	fmt.Println(i,string(ch))
	fmt.Printf("%d %c\n", i, ch)
}

字符串常用方法

import (
	"fmt"
	"strings"
)
strings.Join()
strings.Split()
strings.Fields()
strings.Contains()
strings.Index()
strings.ToLower()
strings.ToUpper()
strings.Trim()
strings.TrimRight()
strings.TrimLeft()

强制类型转换

Go语言只有强制类型转换,没有隐式类型转换。

int() // 强制转int
float64() // 强制转float64

常量与枚举

常量

变量使用var定义,常量使用const定义,常量在定义的时候必须赋值且后期不能被修改。

const filename string = "abc.txt"
const a, b = 3, 4 // 不确定类型,既可以是int也可以是float

枚举

const (
	cpp = 0
  java = 1
  python = 2
  golang = 3
)

iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

使用iota能简化定义,在定义枚举时很有用。

举个例子:

const (
		n1 = iota //0
		n2        //1
		n3        //2
		n4        //3
	)

使用_跳过某些值

const (
		n1 = iota //0
		n2        //1
		_
		n4        //3
	)

iota声明中间插队

const (
		n1 = iota //0
		n2 = 100  //100
		n3 = iota //2
		n4        //3
	)
const n5 = iota //0
const (
	b = 1 << (10 * iota)
	kb
	mb
	gb
	tb
	pb
)
func main() { 
         
	// 1 1024 1048576 1073741824 1099511627776 1125899906842624
	fmt.Println(b, kb, mb, gb, tb, pb)
}

fmt.Printf格式化输出

占位符 说明
%s 直接输出字符串或[]byte
%q 该值用双引号括起来
%T 打印值的类型
%v 相应值的默认格式
%d 表示位十进制

条件语句

if else

func bounded(v int) int { 
         
	if v > 100 { 
         
		return 100
	} else if v < 100 { 
         
		return 0
	} else { 
         
		return v
	}
}

​ if条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断

package main

import (
	"fmt"
	"io/ioutil"
)

func main() { 
         
	const filename = "abc.txt"
	if contents, err := ioutil.ReadFile(filename); err != nil { 
         
		fmt.Println(err)
	} else { 
         
		fmt.Printf("%s\n", contents)
	}
	// 如果文件不存在则提示:open abc.txt: no such file or directory
	// 如果文件存在则打印文件内容
}

switch

func eval(a, b int, op string) int { 
         
	var result int
	switch op { 
         
	case "+":
		result = a + b
	case "-":
		result = a - b
	case "*":
		result = a * b
	case "/":
		result = a / b
	default:
		panic("unsupported operator:" + op)
	}
	return result
}
// switch后不带表达式
func grade(score int) string { 
         
	g := ""
	switch { 
         
	case score < 0 || score > 100:
		panic(fmt.Sprintf("Wrong score:%d", score))
	case score < 60:
		g = "D"
	case score < 80:
		g = "C"
	case score < 90:
		g = "B"
	case score <= 100:
		g = "A"
	}
	return g
}
func main() { 
         
	fmt.Println 

标签: 规格重载连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台