共计 12428 个字符,预计需要花费 32 分钟才能阅读完成。
前言
本文应用代码片段的模式来解释在 go
语言开发中常常遇到的小性能点,因为自己次要应用 java
开发,因而会与其作比拟,心愿对大家有所帮忙。
1. hello world
新手村的第一课,毋庸置疑。
package main
import "fmt"
func main() {fmt.Printf("hello world")
}
2. 隐形初始化
package main
import "fmt"
func main() {load()
}
func load() {fmt.Printf("初始化.. 手动 %s 不错 \n", "1")
}
func init() {fmt.Printf("隐形初始化。。\n")
}
在 go 中定义 init
函数,程序在运行时会主动执行。相似使 junit
的 [@before](https://my.oschina.net/u/3870904)
注解。
3. 多模块的拜访
java
中 package
包的概念,go
是通过文件夹 + package
关键字来定义的。
一般而言,咱们会通过 go init
来创立我的项目,生成的 go.mod
文件位于根目录。
常见的实际是,创立文件夹并且放弃 package 名称与文件夹保持一致。这样 import
的永远是文件夹,遵循以上规定则意味着文件夹的名称即为模块名。
同一个 package
能够创立多个 .go
文件,尽管散布在不同的文件中。然而他们中的办法名称不能雷同。须要留神,这里与 java
中不同类中办法能够重名不同。
此外,也没有诸如 private、protected、public
等包拜访权限关键字。只有定义的函数首字母为大写。则能够被内部胜利调用。
来看一下示例:
go-tour
└── ch3
├── model
│ └── test
│ │ ├── testNest.go
│ └── helper.go
│ └── helper2.go
│
└── main.go
└── go.mod
此处,ch3、model、test
均为文件夹,也能够说是 package
。helper.go
位于 model
下,它的代码如下:
package model
import "fmt"
var AppName = "bot"
var appVersion = "1.0.0"
func Say() {fmt.Printf("%s", "hello")
}
func init() {fmt.Printf("%s,%s", AppName, appVersion)
}
再来看看 main.go
package main
import (
"ch3/model"
"ch3/model/test"
)
func main() {model.Say()
}
显然它的调用是通过 packageName.MethodName()
来应用的。须要留神的是,一个 go.mod
下只能有一个 main
包。
4. 援用内部库
和 java
的 maven
相似,go
几经挫折也提供了官网仓库。如下,通过 go get github.com/satori/go.uuid
命令即可装置 uuid
库,未指定版本,因而下载的为最新版本。
应用时是这样的:
package main
import (
"fmt"
uuid "github.com/satori/go.uuid"
)
func main() {uuid := uuid.NewV4()
fmt.Printf("%s", uuid)
}
5. 数组字典和循环
间接看代码就是了。
package main
import "fmt"
var item []int
var m = map[int]int{100: 1000,}
var m2 = make(map[int]int)
func main() {
for i := 0; i < 10; i++ {item = append(item, i)
m[i] = i
m2[i] = i
}
for i := range item {fmt.Printf("item vlaue=%d\n", i)
}
for key, value := range m {fmt.Printf("m:key=%d,value=%d\n", key, value)
}
for _, value := range m2 {fmt.Printf("m2:value=%d\n", value)
}
}
- := 的模式只能在办法内
- 全局的只能用 var x=..
- map 输入没有程序
6. 构造体和 JSON
go
中通过 struct
来定义构造体,你能够把它简略了解为对象。个别长这样。
type App struct {
AppName string
AppVersion string `json:"app_version"`
appAuthor string "pleuvoir"
DefaultD string "default"
}
咱们常常在 java
程序中应用 fastjson
来输入 JSON 字符串
。go
中自带了这样的类库。
package main
import (
app2 "app/app" // 能够定义别名
"encoding/json"
"fmt"
)
func main() {a := app2.App{}
fmt.Printf("%s\n", a)
app := app2.App{AppName: "bot", AppVersion: "1.0.1"}
json, _ := json.Marshal(app) // 转换为字符串
fmt.Printf("json is %s\n", json)
}
- 构造体中
JSON
序列化不会转变大小写,能够指定它输入的key
名称通过 json:xxx 的形容标签。 - 构造体中的默认值赋值了也不展现
7. 异样解决
作为一个有教训的程序员:),go
的异样解决波及的很简略,也往往为人所诟病。比方满屏幕的 err
应用。
package main
import (
"fmt"
"os"
)
func _readFile() (int, error) {file, err := os.ReadFile("test.txt")
if err != nil {fmt.Printf("error is = %s\n", err)
return 0, err
}
fmt.Printf("file = %s \n", file)
return len(file), err
}
func readFile() (int, error) {fileLength, err := _readFile()
if err != nil {fmt.Printf("异样,存在谬误 %s\n", err)
}
return fileLength, err
}
func main() {fileLength, _ := readFile()
fmt.Printf("%d\n", fileLength)
}
和 java
不同,它反对多返回值,为咱们的应用带来了很多便当。如果不须要解决这个异样,能够应用 _
疏忽。
8. 异步
千呼万唤始进去,令人兴奋的异步。
package main
import (
"bufio"
"fmt"
"os"
)
func worker() {
for i := 0; i < 10; i++ {fmt.Printf("i=%d\n", i)
}
}
func main() {go worker()
go worker()
// 阻塞 获取控制台的输入
reader := bufio.NewReader(os.Stdin)
read, err := reader.ReadBytes('\n') // 留神是单引号 回车后完结控制台输入
if err != nil {fmt.Printf("err is =%s\n", err)
return
}
fmt.Printf("read is %s \n", read)
}
如此的优雅,如此的简略。只须要一个关键字 go
便能够启动一个协程。咱们在 java
中常常应用的是线程池,而在 go
中也存在协程池。据我察看,局部协程池 benchmark
的性能的确比官方语言关键字高很多。
9. 异步期待
这里就相似 java
中应用 countdownLatch
等关键字空值并发编程中程序的期待问题。
package main
import (
"fmt"
"sync"
"time"
)
func upload(waitGroup *sync.WaitGroup) {
for i := 0; i < 5; i++ {fmt.Printf("正在上传 i=%d \n", i)
}
time.Sleep(5 * time.Second)
waitGroup.Done()}
func saveToDb() {fmt.Printf("保留到数据库中 \n")
time.Sleep(3 * time.Second)
}
func main() {begin := time.Now()
fmt.Printf("程序开始 %s \n", begin.Format(time.RFC850))
waitGroup := sync.WaitGroup{}
waitGroup.Add(1)
go upload(&waitGroup)
go saveToDb()
waitGroup.Wait()
fmt.Printf("程序完结 耗时 %d ms", time.Now().UnixMilli()-begin.UnixMilli())
}
sync
包相似于 J.U.C
包,外面能够找到很多并发编程的工具类。sync.WaitGroup
便能够简简单单认为是 countdownLatch
吧。也不能屡次调用变为正数,否则会报错。
留神,这里须要传入指针,因为它不是一个援用类型。肯定要通过指针传值,不然过程会进入死锁状态。
10. 管道
package main
import (
"fmt"
"sync"
)
var ch = make(chan int)
var sum = 0 // 是线程平安的
func consumer(wg *sync.WaitGroup) {
for {
select {
case num, ok := <-ch:
if !ok {wg.Done()
return
}
sum = sum + num
}
}
}
func producer() {
for i := 0; i < 10_0000; i++ {ch <- i}
close(ch) // 如果不敞开则会死锁
}
func main() {wg := sync.WaitGroup{}
wg.Add(1)
go producer()
go consumer(&wg)
wg.Wait()
fmt.Printf("sum = %d \n", sum)
}
这里演示的是什么呢?管道相似一个队列,进行线程间数据的传递。当敞开时生产端也退出,如果没敞开管道,运行时会报死锁。能够看出全局变量在线程间是平安的。
能够衍生出一种固定写法:
// 固定写法
func consumer(wg *sync.WaitGroup) {
for {
select {
case num, ok := <-ch:
if !ok {wg.Done()
return
}
sum = sum + num
}
}
}
11. 接口
package main
import "fmt"
type Person interface {Say()
SetName(name string)
}
type ZhangSan struct {Value string}
func (z *ZhangSan) Say() {fmt.Printf("name=%s", z.Value)
}
func (z *ZhangSan) SetName(name string) {z.Value = name + ":hehe"}
func main() {zhangSan := ZhangSan{}
zhangSan.SetName("pleuvoir")
zhangSan.Say()}
如上的程序演示了接口的应用。
- go 的接口没有强依赖
- 通过构造体 + 办法的模式实现,留神办法传入的能够是援用也能够是值
12. 锁
package main
import (
"fmt"
"sync"
)
type Number struct {
Value int
mutex sync.Mutex // 加锁
}
func (receiver *Number) Add() {receiver.mutex.Lock()
defer receiver.mutex.Unlock() // 退出时会执行
receiver.Value = receiver.Value + 1
//fmt.Printf("add\n")
}
func (receiver *Number) Get() int {receiver.mutex.Lock()
defer receiver.mutex.Unlock()
return receiver.Value
}
func main() {number := Number{Value: 0}
wg := sync.WaitGroup{}
n := 100_0000
wg.Add(n)
for i := 0; i < n; i++ {go func(wg *sync.WaitGroup) {number.Add()
wg.Done()}(&wg)
}
wg.Wait()
fmt.Printf("count=%d", number.Get())
}
这里是什么?显然就像是显示锁的 ReentrantLock
的应用,置信大家都能看懂。这里呈现了新关键字 defer
,我暂且是了解为 finally
。不晓得你怎么看?
13. 读写配置文件
这也是一个很惯例的性能,看看怎么实现。
package main
import (
"encoding/json"
"fmt"
"os"
)
type Preferences struct {
Name string `json:"name"`
Version float64 `json:"version"`
}
const configPath = "config.json"
func main() {preferences := Preferences{Name: "app", Version: 100.01}
marshal, err := json.Marshal(preferences)
err = os.WriteFile(configPath, marshal, 777)
if err != nil {fmt.Printf("写入配置文件谬误,%s\n", err)
return
}
// 读取配置文件
file, err := os.ReadFile(configPath)
if err != nil {fmt.Printf("读取文件谬误,%s\n", err)
return
}
fmt.Printf("%s\n", file) //{"name":"app","version":100.01}
// 构建一个对象用来序列化
readConfig := Preferences{}
// 反序列化
err = json.Unmarshal(file, &readConfig)
if err != nil {fmt.Printf("配置文件转换为 JSON 谬误,%s\n", err)
}
fmt.Printf("%v", readConfig) //{app 100.01}
这里挺没意思的,写入 JSON
字符串,而后读取回来在加载到内存中。不过,简略的示例也够阐明问题了。
14. 宕机解决
这是相似于一种最上层异样捕捉的机制,在程序的入口处捕捉所有的异样。
package main
import (
"fmt"
"time"
)
func worker() {//defer func() { // 不能写在主函数,最外层 catch 没啥用
// if err := recover(); err != nil {// fmt.Printf("%s", err)
// }
//}()
defer recovery()
panic("严重错误")
}
func recovery() {if err := recover(); err != nil {fmt.Printf("死机了。%s\n", err)
}
}
func main() {
for true {worker()
time.Sleep(1 * time.Second)
}
}
正文写的很分明,聪慧的你一看就懂。
15. 单元测试
与 java
不同,go
倡议单元测试文件尽可能的离源代码文件近一些。比方这样:
go-tour
└── main.go
└── main_test.go
并且它的命名也是这样简略粗犷:
package main
import ("testing")
func TestInit(t *testing.T) {t.Log("heh")
helper := PersonHelper{}
helper.init("pleuvoir")
t.Log(helper.Name)
}
以大写的 Test
结尾,文件名称以 _test
结尾,很清新的感觉。
16. 启动传参
这也是一个很罕用的知识点。这里有两种形式:
- 间接传
- 应用 flag
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
)
func main() {
// 第一种形式
args := os.Args
for i, arg := range args {println(i, arg)
}
// 第二种形式
config := struct {
Debug bool
Port int
}{}
flag.BoolVar(&config.Debug, "debug", true, "是否开启 debug 模式")
flag.IntVar(&config.Port, "port", 80, "端口")
flag.Parse()
json, _ := json.Marshal(config)
fmt.Printf("json is %s\n", json)
}
我倡议应用第二种,更便捷自带类型转换,还能够给默认值,十分好。
17. 优雅退出
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func quit() {println("执行一些清理工作。。")
}
// 失常的退出
// 终端 CTRL+ C 退出
// 异样退出
func main() {defer quit()
println("进来了")
// 读取信号,没有始终会阻塞住
exitChan := make(chan os.Signal)
// 监听信号
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGQUIT)
go func() {
// 有可能一次接管到多个
for s := range signals {
switch s {
case syscall.SIGINT, syscall.SIGQUIT:
println("\n 监听到操作系统信号。。")
quit() // 如果监听到这个信号没解决,那么程序就不会退出了
if i, ok := s.(syscall.Signal); ok {value := int(i)
fmt.Printf("是信号类型,筹备退出 %d", value)
} else {println("不晓得是啥,0 退出")
os.Exit(0)
}
// os.Exit(value)
exitChan <- s
}
}
}()
println("\n 程序在这里被阻塞了。")
<-exitChan
//panic("heh")
println("\n 阻塞被终止了。")
}
这其实是在监听操作系统的信号,java
中也有相似的回调的接口(我忘了名字)。
18. 反射
作为一门高级语言,反射必定是有的。还是应用 reflect
包。
package main
import (
"fmt"
"reflect"
)
type Person struct {Name string `json:"name"`}
func (p *Person) SetName(name string) {p.Name = name}
func (p *Person) GetName() (string, string) {return p.Name, "1.0.1"}
func worker1() {p := Person{}
p.SetName("pleuvoir")
name, _ := p.GetName()
fmt.Printf(name)
}
// 获取办法
func worker2() {p := Person{}
rv := reflect.ValueOf(&p)
value := []reflect.Value{reflect.ValueOf("peluvoir")}
rv.MethodByName("SetName").Call(value)
values := rv.MethodByName("GetName").Call(nil)
for i, v := range values {fmt.Printf("\ni=%d,value=%s\n", i, v)
}
}
func worker3() {s := Person{}
rt := reflect.TypeOf(s)
if field, ok := rt.FieldByName("Name"); ok {tag := field.Tag.Get("json")
fmt.Printf("tag is %s \n", tag)
}
}
func main() {
// 失常获取
worker1()
// 获取办法
worker2()
// 获取标签
worker3()}
没什么好说的,写代码全靠猜。
19. atomic
相似 java
中的 atomic
原子变量。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
workers := 1000
wg := sync.WaitGroup{}
wg.Add(workers)
for i := 0; i < workers; i++ {go worker2(&wg)
}
wg.Wait()
fmt.Printf("count = %d", count)
}
var count int64 = 0
func worker1(wg *sync.WaitGroup) {
count++
wg.Done()}
func worker2(wg *sync.WaitGroup) {atomic.AddInt64(&count, 1) // 特地简略
wg.Done()}
真的是特地简略。
20. 线程平安的 Map
相似于ConcurrentHashMap
,与一般的 api
有所不同。
var sessions = sync.Map{}
sessions.Store(uuid, uuid)
load, ok := sessions.Load(value.Token)
if ok {// 做你想做的事件}
21. return func
这里就是函数式变成的例子了。函数是一等公民能够作为参数随便传递。java
什么时候能反对呢?
package main
import "fmt"
func main() {engine := Engine{}
engine.Function = regular()
function := engine.Function
for i := 0; i < 3; i++ {s := function("pleuvoir")
fmt.Printf("s is %s\n", s)
}
}
type Engine struct {Function func(name string) string
}
func regular() (ret func(name string) string) {fmt.Printf("初始化一些货色。\n")
return func(name string) string {fmt.Printf("我是 worker。name is %s\n", name)
return "我是匿名函数的返回值"
}
}
比方这里,如果要初始化日志什么。最初须要让框架在哪里打印日志,就须要将这个初始化的日志实例传递过来。总而言之,言而总之。会须要让代码各种传递。
这种形式在于第一次调用的时候会执行下面的代码片段,前面只是保留了这个函数的句柄,而后能够始终调用这个匿名函数。
22. context
package main
import (
"context"
"fmt"
"time"
)
func main() {worker1()
}
func worker1() {
// 总共 2 秒超时
value := context.WithValue(context.Background(), "token", "pleuvoir")
timeout, cancelFunc := context.WithTimeout(value, 5*time.Second)
defer cancelFunc()
// 模仿工作
fmt.Println("开始工作")
deep := 10
go handler(timeout, deep)
fmt.Println("开始阻塞", time.Now())
// 期待主线程超时,阻塞操作
select {case <-timeout.Done():
fmt.Println("阻塞完结", timeout.Err(), time.Now())
}
}
// 模仿工作解决,循环下载图片等
func handler(timeout context.Context, deep int) {
if deep > 0 {fmt.Printf("[begin]token is %s %s deep=%d\n", timeout.Value("token"), time.Now(), deep)
time.Sleep(1 * time.Second)
go handler(timeout, deep-1)
}
// 上面的哪个先返回 先执行哪个
// 如果整体超时 或者 以后办法超过 2 秒 就完结
select {
// 期待超时会返回
case <-timeout.Done():
fmt.Println("超时了。", timeout.Err())
// 期待这么久 而后会返回 这个函数可不是比拟工夫,这里其实是在模仿解决工作,固定执行一秒 和劳动一秒成果一样
// 然而劳动一秒的话就不会实时返回了,所以这里理论利用能够是一个带超时的回调?case <-time.After(time.Second):
fmt.Printf("[ end]执行实现耗时一秒 %s %d\n", time.Now(), deep)
}
}
作用:在不同的协程中传递上下文。
- 传值 相似于 threadLocal
- 能够应用超时机制,无论往下传递了多少协程,只有最上层工夫到了 前面的都不执行
- 俄罗斯套娃一次一层包装
23. 字符串解决
这是最高频率的操作了,应用任何语言都无奈错过。
package main
import (
"fmt"
"strings"
)
func main() {
str := "pleuvoir"
trimSpace := strings.TrimSpace(str)
fmt.Printf("去除空格 %s\n", trimSpace)
subString := trimSpace[4:len(trimSpace)]
fmt.Printf("subString after is %s\n", subString)
prefix := strings.HasPrefix(subString, "vo")
fmt.Printf("是否有前缀 vo : %v\n", prefix)
suffix := strings.HasSuffix(subString, "ir")
fmt.Printf("是否有后缀 ir : %v\n", suffix)
builder := strings.Builder{}
builder.WriteString("hello")
builder.WriteString(" ")
builder.WriteString("world")
fmt.Printf("stringBuilder append is %s\n", builder.String())
eles := []string{"1", "2"}
join := strings.Join(eles, "@")
fmt.Printf("join after is %s\n", join)
// 拼接格式化字符串,并且能返回
sprintf := fmt.Sprintf("%s@%s", "1", "20")
fmt.Printf("Sprintf after is %s\n", sprintf)
// 打印一个对象 比拟清晰的形式
person := struct {
Name string
Age int
}{"pleuvoir", 18}
fmt.Printf("%v", person) // 输入 {Name:pleuvoir Age:18}
}
次要是应用 fmt
包。
24. 工作投递
如果说应用 go
最激动人心的是什么?是大量的协程。如果在下载工作中,咱们能够启动很多协程进行分片下载。如下,即展现应用多路复用高速下载。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
chunks := 10 // 文件分成 n 份
workers := 5 // 个线程解决
wg := sync.WaitGroup{}
wg.Add(chunks)
jobs := make(chan int, chunks) // 带缓冲的管道 等于工作数
for i := 0; i < workers; i++ {go handler1(i, jobs, &wg)
}
// 将工作全副投递给 worker
scheduler(jobs, chunks)
wg.Wait()
fmt.Println("download finished .")
}
// 分成 chunks 份工作 里散发
// 将 n 份下载工作都到管道中去,这里管道数量等于 工作数量 n 管道不会阻塞
func scheduler(jobs chan int, chunks int) {
for i := 0; i < chunks; i++ {//time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
jobs <- i
}
}
// 写法 2
// 留神这里的是间接承受管道,这也是一种固定写法,上面的 range jobs 能够认为是阻塞去抢这个工作,多个线程都在抢工作
func handler2(workerId int, jobs <-chan int, wg *sync.WaitGroup) {
for job := range jobs {// fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job)
time.Sleep(1 * time.Second)
fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job)
wg.Done() // 这里不要 break,这样执行完以后的线程就能持续抢了}
}
// 写法 1,select case 多路复用
func handler1(workerId int, jobs chan int, wg *sync.WaitGroup) {
for {
select {
case job, _ := <-jobs:
// fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job)
time.Sleep(3 * time.Second)
fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job)
wg.Done() // 这里不要 break,这样执行完以后的线程就能持续抢了}
}
}
后语
以上都是一个老手 Gopher
的经验总结,文中不免有谬误,恳请斧正。
作者:京东批发 付伟
起源:京东与开发者社区