go语言教程大全
目录:
-
Go语言基础教程 - 学习go语言基础语法
-
Web框架 - 学习流行的web开发框架
-
数据库
-
GORM教程 - 操作mysql必备的orm库
-
sqlx教程 - 简单的sql操作库,可以用来操作Mysql
-
go语言redis教程 - go语言中如何操作redis?
-
-
模板引擎
-
html模板引擎教程 - go语言中如何处理html模板
-
-
其他
-
Grpc教程 - 高性能的rpc通信框架
golang http客户端
golang 内置的net/http包,支持http客户端功能,我们可以用来处理http请求,请求api接口等等。
1.处理GET请求
使用http.Get函数处理get请求
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// 发起http GET请求
resp, err := http.Get("http://api.baidu.com/user/info?id=1")
if err != nil {
// 处理错误
}
// 请求处理完后,需要记得释放body资源
defer resp.Body.Close()
// 使用ReadAll函数,读取http请求内容,返回的是字节数组
content, _ := ioutil.ReadAll(resp.Body)
// 打印http请求结果
fmt.Println(string(content))
}
2.处理POST请求
发送post有两个函数常用函数:
-
PostForm - 发送表单,其实就是将content-type设置为application/x-www-form-urlencoded
-
Post - 发送post请求
使用PostForm的例子:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// 发起http POST请求, 提交表单
// 使用Values组织post请求参数
params := url.Values{}
params.Add("id", "1")
params.Add("username", "tizi")
params.Add("password", "123456")
// PostForm请求自动将content-type设置为application/x-www-form-urlencoded
resp, err := http.PostForm("http://api.baidu.com/user/creation", params)
if err != nil {
// 处理错误
}
// 请求处理完后,需要记得释放body资源
defer resp.Body.Close()
// 使用ReadAll函数,读取http请求内容,返回的是字节数组
content, _ := ioutil.ReadAll(resp.Body)
// 打印http请求结果
fmt.Println(string(content))
}
Post函数和PostForm用法类似,下面例子暂时差异部分
// 组织post参数,因为post函数接受的是一个实现io.Reader接口的对象
// 这里我使用string reader组织字符串参数
data := strings.NewReader("username=tizi&password=1234")
// 第二个参数是content-type, 需要自己设置
// 第三个参数是post请求的参数,接受实现io.Reader接口的对象
resp, err := http.Post("http://api.baidu.com/user/creation","application/x-www-form-urlencoded", data)
3.设置请求头
如果我们要给http 请求设置header就需要另外的方式发送http请求
下面例子介绍如何为http请求设置请求头、http请求超时时间、设置cookie
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func main() {
// 初始化一个默认的http客户端
client := http.DefaultClient
// 设置请求超时时间为5秒
client.Timeout = time.Second * 5
// 使用Values组织post请求参数
params := url.Values{}
params.Add("id", "1")
params.Add("username", "tizi")
params.Add("password", "123456")
// 创建http请求, 因为NewRequest的请求参数,接受的是io.Reader,所以先将Values编码成url参数字符串,
// 然后使用strings.NewReader转换成io.Reader对象
req, _ := http.NewRequest("POST", "http://api.baidu.com/user/info", strings.NewReader(params.Encode()))
// 设置请求头(header)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Site", "www.tizi365.com")
// 设置cookie
// 初始化cookie对象
cookie := http.Cookie{}
cookie.Name = "tizi-domain" // cookie名字
cookie.Value = "tizi365.com" // cookie值
cookie.Path = "/" // cookie 路径
//cookie有效期为3600秒
cookie.MaxAge = 3600
// 为请求添加cookie
req.AddCookie(&cookie)
// 使用http客户端发送请求
resp, err := client.Do(req)
if err != nil {
//请求错误处理
}
// 处理完请求后需要关闭响应对象的body
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
golang读取toml配置文件
项目通常是有很多配置信息需要存储,比如数据库的账号密码、Redis的连接地址、各种环境参数,这些配置信息通常都是写在配置文件中。
我们常用的配置文件存储格式有JSON文件、INI文件、YAML文件和TOML文件等等。
这里介绍golang读写toml配置文件, 本教程主要通过BurntSushi/toml包读写toml配置文件。
1.toml介绍
TOML 的全称是 Tom’s Obvious, Minimal Language,因为它的作者是 GitHub 联合创始人 Tom Preston-Werner。
TOML 的目标是成为一个极简的配置文件格式。
toml配置文件例子:
# toml注释
title = "TOML Example"
level = 2
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # 注释可以写在这里
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
toml配置文件格式非常灵活,可以是数字、字符串、布尔等简单类型,也可以是数组、map等等复杂的类型。
2.安装包
go get github.com/BurntSushi/toml
3.读取toml配置文件
toml配置文件:config.toml
Age = 25
Cats = [ "Cauchy", "Plato" ]
Pi = 3.14
Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z
golang读取配置文件,主要就是将toml配置映射到golang struct变量中。
读取toml配置文件的例子:
package main
import (
"fmt"
"github.com/BurntSushi/toml"
"time"
)
// 我们先根据toml配置文件定义struct
// 他们的关系都是一一对应,toml配置文件定义的字段是什么类型,struct字段就是什么类型
type Config struct {
Age int
Cats []string // 对应toml配置文件中的Cats数组
Pi float64
Perfection []int
// 因为toml配置文件定义的是时间格式,可以转换成golang的time类型
DOB time.Time
}
func main() {
// 首先初始化struct对象,用于保存配置文件信息
var conf Config
// 通过toml.DecodeFile将toml配置文件的内容,解析到struct对象
if _, err := toml.DecodeFile("./config.toml", &conf); err != nil {
// handle error
}
// 可以通过conf读取配置
fmt.Println(conf.Age)
}
golang Logrus日志处理框架
在项目中输出日志是比较常见的需求,golang默认的日志包功能比较弱,这里介绍Logrus日志处理包。
Logrus是一个结构化日志处理框架,并且api完全兼容golang标准库的logger日志api,意味着你可以直接使用Logrus替换logger。
通常我们输出的日志都是非结构化的,例如:
2019-09-10 10:10:00 [info] connect to redis error,ip=12.11.21.1
这样格式的日志,不方便对日志进行分析,尤其大数据处理,通常需要对日志进行格式化。
结构化的日志,就方便程序解析日志的内容,例如json格式:
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
1.安装包
go get github.com/sirupsen/logrus
2.Logrus简单例子
package main
import (
"os"
// 导入logrus日志包,别名为log
log "github.com/sirupsen/logrus"
)
func main() {
// 设置日志等级
log.SetLevel(log.DebugLevel)
// 设置日志输出到什么地方去
// 将日志输出到标准输出,就是直接在控制台打印出来。
log.SetOutput(os.Stdout)
// 设置为true则显示日志在代码什么位置打印的
//log.SetReportCaller(true)
// 设置日志以json格式输出, 如果不设置默认以text格式输出
log.SetFormatter(&log.JSONFormatter{})
// 打印日志
log.Debug("调试信息")
log.Info("提示信息")
log.Warn("警告信息")
log.Error("错误信息")
//log.Panic("致命错误")
//
// 为日志加上字段信息,log.Fields其实就是map[string]interface{}类型的别名
log.WithFields(log.Fields{
"user_id": 1001,
"ip" : "123.12.12.11",
"request_id" : "kana012uasdb8a918gad712",
}).Info("用户登陆失败.")
}
执行后日志输出:
{"level":"debug","msg":"调试信息","time":"2019-09-11T11:30:14+08:00"}
{"level":"info","msg":"提示信息","time":"2019-09-11T11:30:14+08:00"}
{"level":"warning","msg":"警告信息","time":"2019-09-11T11:30:14+08:00"}
{"level":"error","msg":"错误信息","time":"2019-09-11T11:30:14+08:00"}
{"ip":"123.12.12.11","level":"info","msg":"用户登陆失败.","request_id":"kana012uasdb8a918gad712","time":"2019-09-11T11:30:14+08:00","user_id":1001}
3.将日志保存到文件
我们如何将日志保存到本地文件。
前面的例子我们知道,可以通过SetOutput函数设置将日志保存到什么地方,下面演示如何将日志保存到文件中。
// 先打开一个日志文件
file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
// 设置将日志输出到文件
log.SetOutput(file)
} else {
log.Info("打开日志文件失败,默认输出到stderr")
}
4.自定义日志保存的位置
SetOutput函数定义:
func SetOutput(out io.Writer)
SetOutput函数接受的是一个实现了io.Writer接口的对象,只要实现了这个接口,你可以将日志保存到任何地方,例如,我们可以自定义个实现了io.Writer接口的日志存储对象,将日志保存到阿里云日志服务。
例子:
// 自定义日志存储,实现io.Writer接口
type Alilog struct {
}
// io.Writer接口只有Write一个函数
func (v *Alilog) Write(p []byte) (n int, err error) {
// 日志内容,传进来的是字节数组
// 将日志发送到阿里云日志服务
// 当然这里可以做下缓存,批量想阿里云发送日志,不用一条条的发
return
}
// 设置日志存储方式
log.SetOutput(&Alilog{})
golang flag命令行参数解析
在写命令行程序时,对命令参数进行解析是常见的需求, flag包是Go语言标准库提供用来解析命令行参数的包,使得开发命令行工具更为简单。
命令参数的例子:
例如运行:
nginx -h
输出类似信息:
nginx version: nginx/1.10.0
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : 显示帮助信息
-v : 显示版本信息
-V : 显示版本信息和配置信息
-t : 检测nginx配置文件
下面介绍如何通过flag包实现命令行参数解析。
1.flag基本用法
package main
import (
"fmt"
"flag"
)
func main() {
// 定义命令参数
// flag.xxx序列函数,xxx指的是数据类型,例如String、Int、Bool
// 定义个字符串参数,参数名是name, 参数默认值是tizi, 参数提示信息是:姓名
namePtr := flag.String("name", "tizi", "姓名")
// 定义个int参数,参数名:age, 默认值:18,提示信息是:年龄
agePtr := flag.Int("age", 18, "年龄")
vipPtr := flag.Bool("vip", true, "是否vip")
// 自定义命令行 -h 参数,输出什么内容。
flag.Usage = func() {
// -h 参数通常用于输出帮助信息,命令有什么参数,怎么用之类的信息
// 先打印下怎么使用命令
fmt.Println("Usage: flagdemo [-h] [-name string] [-vip] [-age int]")
// 调用PrintDefaults打印前面定义的参数列表。
flag.PrintDefaults()
}
// 解析命令行参数
flag.Parse()
// 因为前面flag.xxx函数返回的是指针,所以需要在变量前面加上*号读取参数内容
fmt.Println("name:", *namePtr)
fmt.Println("age:", *agePtr)
fmt.Println("vpi:", *vipPtr)
}
运行程序:
go run flagdemo.go -h
输出:
Usage: flagdemo [-h] [-name string] [-vip] [-age int]
-age int
年龄 (default 18)
-name string
姓名 (default "tizi")
-vip
是否vip (default true)
golang atomic原子操作
atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。
atomic原子操作主要用于并发环境下,无须加锁对整数进行安全的加减、比较、读取操作。
atomic原子操作支持的数据类型:
-
int32
-
int64
-
Uint32
-
Uint64
1.例子
// 访问量计数
var count int64 = 0
// 对count变量进行原子加 1
// 原子操作可以在并发环境安全的执行
atomic.AddInt64(&count, 1)
// 对count变量原子减去10
atomic.AddInt64(&count, -10)
// 原子读取count变量的内容
pv := atomic.LoadInt64(&count)
2.atomic常用函数
2.1. LoadInt32
函数定义:
func LoadInt32(addr *int32) (val int32)
LoadInt32原子性的获取*addr的值。
LoadInt64,LoadUint32,LoadUint64序列函数用法类似,区别就是数据类型不同。
例子:
var count int32 = 0
// 原子读取count变量的内容
pv := atomic.LoadInt32(&count)
2.2. StoreInt32
函数定义:
func StoreInt32(addr *int32, val int32)
StoreInt32原子性的将val的值保存到*addr。
StoreInt64、StoreUint32、StoreUint64序列函数用法类似,区别就是数据类型不同
例子:
var count int32 = 0
// 安全的将100保存到count变量中
atomic.StoreInt32(&count, 100)
2.3. AddInt32
函数定义:
func AddInt32(addr *int32, delta int32) (new int32)
AddInt32原子性的将delta的值添加到*addr并返回新值。
AddInt64、AddUint32、AddUint64序列函数用法类似,区别就是数据类型不同
AddXXX 系列函数实现加法操作,在原子性上等价于:
*addr += delta
return *addr
例子:
var count int32 = 0
// 对count变量进行原子加 1, 并且返回新值
newCount := atomic.AddInt32(&count, 1)
2.4. SwapInt32
函数定义:
func SwapInt32(addr *int32, new int32) (old int32)
SwapInt32原子性的将新值保存到*addr并返回旧值。
SwapInt64、SwapUint32、SwapUint64序列函数用法类似,区别就是数据类型不同。
SwapXXX序列函数操作在原子性上等价于:
old = *addr
*addr = new
return old
例子:
var count int32 = 0
// 将200保存到count中, 并且返回旧的值
oldCount := atomic.SwapInt32(&count, 200)
2.5. CompareAndSwapInt32
函数定义:
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
CompareAndSwapInt32原子性的比较addr和old,如果相同则将new赋值给addr并返回真。
CompareAndSwapInt64、CompareAndSwapUint32、CompareAndSwapUint64序列函数用法类似,区别就是数据类型不同。
CompareAndSwapXXX系列函数实现的比较-交换操作,在原子性上等价于:
if *addr == old {
*addr = new
return true
}
return false
例子:
var count int32 = 0
// 如果count变量的值为0,才将100保存到变量中,保存成功返回true,反之false
ok := atomic.CompareAndSwapInt32(&count, 0, 100)
golang Mutex和RWMutex
本章介绍go语言内置的加锁机制。
-
Mutex - 互斥锁
-
RWMutex - 读写互斥锁
1.Mutex
Mutex是一个互斥锁,可以作为为其他结构体的字段,零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。
基本用法:
// 初始化Mutex对象
lk := sync.Mutex{}
// 加锁
lk.Lock()
// 处理业务逻辑
// 解锁
lk.Unlock()
例子:
package main
import (
"fmt"
"sync"
)
var count int = 0
func main() {
// 初始化锁
lk := sync.Mutex{}
done := make(chan bool)
for i:=0; i < 100; i++ {
// 并发的累加count
go func() {
// 加锁
lk.Lock()
// 延迟解锁
defer lk.Unlock()
// 处理业务逻辑
count++
done <- true
}()
}
for i:=0; i < 100; i++ {
<-done
}
fmt.Println(count)
}
2.RWMutex
RWMutex是读写互斥锁。该锁可以被同时多个读取者持有或唯一个写入者持有。RWMutex可以创建为其他结构体的字段;零值为解锁状态。RWMutex类型的锁也和线程无关,可以由不同的线程加读取锁/写入和解读取锁/写入锁。
基本用法:
// 初始化RWMutex对象
lk := sync.RWMutex{}
// 加写锁
lk.Lock()
// 处理业务逻辑
// 解除写锁
lk.Unlock()
// 加读锁
lk.RLock()
// 处理业务逻辑
// 解除读锁
lk.RUnlock
跟Mutex的区别就是锁的粒度更小了,区分为读锁和写锁,读写锁的互斥关系如下:
协程1 | 协程2 | 阻塞状态 |
---|---|---|
读锁 | 读锁 | 不阻塞 |
读锁 | 写锁 | 阻塞 |
写锁 | 写锁 | 阻塞 |
大家都加读锁的时候是不会阻塞的,其他情况就会议阻塞协程。
golang WaitGroup和Once
本章介绍go语言sync包中的两个同步原语:
-
WaitGroup - 主要用于等待一组并发的任务
-
Once - 用于在并发环境中仅执行一次的任务
1. WaitGroup
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
基本用法说明:
// WaitGroup内部维护了一个计数器,用于统计需要等待的任务数量,当计数器的值等于0的时候,Wait函数就会直接返回,否则阻塞。
var wg sync.WaitGroup
// 内部计数加2
wg.Add(2)
// 内部计数减1
wg.Done()
// 如果内部计数器大于0,则阻塞,计数器等于0则返回。
wg.Wait()
例子:
// 初始化WaitGroup对象
var wg sync.WaitGroup
// url数组
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
// 访问所有的url
for _, url := range urls {
// WaitGroup的计数加1
wg.Add(1)
// 使用协程访问URL
go func(url string) {
// 调用WaitGroup的Done函数,实际上就是内部计数减1
defer wg.Done()
// 访问url
http.Get(url)
}(url)
}
// 调用WaitGroup的Wati函数等待所有url加载完成。
// 因为有3个url需要访问,Add函数调用3次,计数器等于3,
// url访问完成后,调用Done函数3次,计数器就会等于0
wg.Wait()
2. Once
Once是只执行一次动作的对象。
例子:
// 定义Once对象
var once sync.Once
onceBody := func() {
fmt.Println("我只跑一次。")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
// 并发执行10次
go func() {
// 调用Once对象的Do函数执行onceBody函数
// 注意:一个Once对象只能执行一次,无论调用多少次Do函数。
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
输出:
我只跑一次。
golang 信号(signal)处理
signal包实现了对输入信号的访问。
在开发服务端程序的时候,常常需要通过linux系统信号通知程序做一些任务,例如重启、重新加载配置、停止接收新的请求、退出程序前清理工作。
在go语言中,通过通道(channel)可以接收系统信号,通过Notify函数可以注册我们需要监听的系统信号。
例子:
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
// 创建一个信号通道,用于接收信号,通道容量为 1
c := make(chan os.Signal, 1)
// 调用Notify注册,通过通道c接收Interrupt和Kill两种信号
signal.Notify(c, os.Interrupt, os.Kill)
// 从通道中读取收到的信号,没有信号则阻塞协程
switch <-c {
case os.Interrupt:
fmt.Println("收到:Interrupt信号")
case os.Kill:
fmt.Println("收到:Kill信号")
default:
fmt.Println("收到其他信号")
}
}
golang 文件读写
本章介绍go语言中如何进行文件读写。
在go语言中文件读写相关的包有:
-
io/ioutil - 提供常用的io函数,例如:便捷的文件读写。
-
os - 系统相关的函数,我这里主要用到跟文件相关的函数,例如,打开文件。
-
path/filepath - 用处理文件路径
1.读文件
通过io/ioutil包的ReadFile函数,可以一次读取一个文件的内容。
函数定义:
func ReadFile(filename string) ([]byte, error)
ReadFile 从filename指定的文件中读取数据并返回文件的内容。成功的调用返回的err为nil而非EOF。因为本函数定义为读取整个文件,它不会将读取返回的EOF视为应报告的错误。
例子:
package main
import (
"fmt"
"io/ioutil"
)
func main() {
content, err := ioutil.ReadFile("./readme.txt")
if err != nil {
// 读取文件失败
panic(err)
}
fmt.Println(string(content))
}
2.写文件
通过io/ioutil包的WriteFile函数,可以将内容保存到文件中。
函数定义:
func WriteFile(filename string, data []byte, perm os.FileMode) error
函数向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件。
例子:
package main
import (
"io/ioutil"
)
func main() {
content := "这里是文件的内容。"
// 因为WriteFile函数接受的文件内容是字节数组,所以需要将content转换成字节数组
// 0666是指文件的权限是具有读写权限,具体可以参考linux文件权限相关内容。
err := ioutil.WriteFile("./demo.txt", []byte(content), 0666)
if err != nil {
panic(err)
}
}
3.检测文件是否存在
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Stat("./demo.txt")
if err != nil {
if os.IsNotExist(err) {
fmt.Println("文件不存在")
return
}
if os.IsPermission(err) {
fmt.Println("没有权限对文件进行操作。")
return
}
fmt.Println("其他错误。")
return
}
// err == nil 则表示文件存在
fmt.Println("文件存在")
}
4.文件路径操作
4.1.获取文件扩展名
package main
import (
"fmt"
"path/filepath"
)
func main() {
ext := filepath.Ext("/images/logo.jpg")
fmt.Println(ext)
}
输出:
.jpg
4.2.获取文件名
package main
import (
"fmt"
"path/filepath"
)
func main() {
// 获取文件名字
filename := filepath.Base("/images/logo.jpg")
fmt.Println(filename)
// 获取目录
dir := filepath.Dir("/images/logo.jpg")
fmt.Println(dir)
}
5.读写文件的高级用法
前面介绍的文件读写,是一次性完成文件的读写操作,比较方便,但是对于大文件的读写的性能优化和文件读写的细节控制就做不到了。
例子:
package main
import "os"
func main() {
// 使用OpenFile打开文件, 并且设置文件内容以追加的形式添加到文件尾部
f, err := os.OpenFile("./demo.txt", os.O_CREATE | os.O_APPEND |os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
// 延迟关闭文件
defer f.Close()
// 写入文件内容
f.WriteString("内容1")
f.WriteString("内容2")
f.WriteString("内容3")
}
5.1. OpenFile函数定义
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
参数说明:
-
name - 文件路径
-
flag - 文件打开模式,后面专门介绍
-
perm - 文件权限模式,例如: 0666 代表当前用户拥有读写权限
常用的文件打开模式:
-
O_CREATE - 如果文件不存在,则创建一个
-
O_APPEND - 写入文件的内容,自动追加到文件的尾部。
-
O_RDONLY - 打开一个只读的文件
-
O_WRONLY - 打开一个只写的文件
-
O_RDWR - 打开一个可以读写的文件
提示:上面这些文件打开模式标签是可以自由组合的。
例子:
// 打开一个只读的文件,并且写入内容自动追加到文件尾部,如果文件不存在则自动创建一个新的
f, err := os.OpenFile("./demo.txt", os.O_CREATE | os.O_APPEND |os.O_WRONLY, 0666)
5.2. File对象常用函数
OpenFile函数创建一个文件后返回的是File对象,下面是File对象常用的函数:
1.Read
func (f *File) Read(b []byte) (n int, err error)
Read方法从f中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。
2.ReadAt
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt从指定的位置(相对于文件开始位置)读取len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。当n<len(b)时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err会是io.EOF。
3.Write
func (f *File) Write(b []byte) (n int, err error)
Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
4.WriteString
func (f *File) WriteString(s string) (ret int, err error)
WriteString类似Write,但接受一个字符串参数。
5.WriteAt
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt在指定的位置(相对于文件开始位置)写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
6.Seek
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
6.Close
func (f *File) Close() error
Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。
golang 时间
go语言的time包,提供时间处理函数,方便我们获取时间戳、时间格式化、处理日期。
在go语言中内置的时间类型是:Time, 我们可以将字符串时间转换成Time类型,也能将Time类型转换成字符串时间。
提示: 使用时间函数,需要导入time包。
1.获取当前时间
// 获取当前时间,返回的是Time类型
t := time.Now()
fmt.Println(t)
2.获取时间戳
1970-01-01开始经过的秒数。
// 获取当前时间的时间戳
s := time.Now().Unix()
// 获取某个Time时间的时间戳。
// t是Time类型对象
s := t.Unix()
3.格式化时间
如何将Time类型转换成我们想要的字符串时间格式。
// 获取当前时间
t := time.Now()
// 按指定格式,格式化时间
s := t.Format("2006-01-02 15:04:05")
fmt.Println(s)
// 返回年月日
year, month, day := t.Date()
// 返回是时分秒
hour,minute,sec := t.Clock()
// 返回星期几,从0开始
weekday := t.Weekday()
// 可以转换成整数
fmt.Println(int(weekday))
输出:
2019-09-15 10:43:14
格式化时间主要通过Time对象的Format函数处理。
函数定义:
func (t Time) Format(layout string) string
只有一个参数layout,layout就是指定我们需要格式化时间的模版。
下面是Time包预定于的时间模版:
const (
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // 使用数字表示时区的RFC822
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // 使用数字表示时区的RFC1123
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
)
使用例子:
t := time.Now()
// 使用预定义的时间模版time.RFC3339
fmt.Println(t.Format(time.RFC3339))
输出:
2019-09-15T10:50:03+08:00
时间模版里面的参数不能随便书写,建议大家根据上面预定义的时间模版,进行修改。
例子:
// 获取当前时间
t := time.Now()
// 只需要格式化年月日
tpl1 := "2006-01-02"
fmt.Println(t.Format(tpl1))
// 只需要格式化输出时间
tpl2 := "15:04:05"
fmt.Println(t.Format(tpl2))
4.时间戳和日期互转
go的Time时间类型可以转换成时间戳,也能转换成字符串日期类型,因此我们只要将时间戳或者日期时间(字符串)转换成Time类型,即可完成自由转换。
4.1. 时间戳转日期
// 将时间戳转换成Time类型
// 第一个参数是时间戳(毫秒),第二个参数是纳秒,设置0即可
t := time.Unix(1568516266,0)
// 将Time转换成我们想要的日期格式。
s := t.Format("2006-01-02 15:04:05")
fmt.Println(s)
4.2. 日期转时间戳
// 将日期转换成Time类型
// 获取本地时区,重要
loc, _ := time.LoadLocation("Local")
// 指定我们要解析的时间模版
l := "2006-01-02 15:04:05"
// 调用ParseInLocation函数,将日期格式转换成Time类型
t, _ := time.ParseInLocation(l, "2019-09-15 10:57:46", loc)
// 将Time转换成时间戳
s := t2.Unix()
fmt.Println(s)
5.时间计算
项目中经常需要计算时间的前一天、后一天、前一周、上一月。
Time类型常用的时间计算函数:
-
Add - 返回增加指定时间的Time
-
AddDate - 返回增加指定的年份、月份和天数的时间点Time
-
Sub - 返回两个Time类型相减的时间间隔(Duration类型)
5.1. Add
函数定义:
func (t Time) Add(d Duration) Time
Add返回时间点t+d。
例子:
t := time.Now()
// 计算前一分钟,当前时间加上负一分钟
t1 := t.Add(-time.Minute)
5.2. AddDate
函数定义:
func (t Time) AddDate(years int, months int, days int) Time
AddDate返回增加了给出的年份、月份和天数的时间点Time。
例子:
t := time.Now()
// 计算前一天, 当前时间加上0年,0月,-1天
t1 := t.AddDate(0,0,-1)
// 计算上一个月时间,当前时间加上0年,-1月,0天
t1 := t.AddDate(0,-1,0)
5.3. Sub
函数定义:
func (t Time) Sub(u Time) Duration
返回一个时间段t-u。如果结果超出了Duration可以表示的最大值/最小值,将返回最大值/最小值。要获取时间点t-d(d为Duration),可以使用t.Add(-d)。
例子:
t := time.Now()
// 计算前一天
t1 := t.AddDate(0,0,-1)
// 两个时间相减
s := t.Sub(t1)
// 因为时间间隔使用的是Duration类型,我们可以将时间单位转换成小时
h := int(s/time.Hour)
// 也可以换成成其他单位,只要除于指定的时间常量即可:
// - Second 秒
// - Minute 分钟
// - Hour 小时
fmt.Println(h)
6. 时间比较
Time类型常用的时间比较函数如下:
-
Equal - 比较两个时间是否相等
-
Before - 相当于小于比较
-
After - 相当于大于比较
例子:
t := time.Now()
// 计算前一天
t1 := t.AddDate(0,0,-1)
if t.Equal(t1) {
fmt.Println("时间相等")
}
if t1.Before(t) {
fmt.Println("时间t1 < t")
}
if t.After(t1) {
fmt.Println("时间t > t1")
}
Go json解析
go语言中json包实现了json对象的编解码,通常在go语言中对于json的解析操作都是通过结构体(struct)和json字符串之间互相转换实现。
常用的json函数:
-
Marshal - 将struct结构体转换成json字符串
-
Unmarshal - 将json字符串转换成struct结构体类型
1. Marshal
函数定义:
func Marshal(v interface{}) ([]byte, error)
Marshal函数的作用是将结构体对象转换成json字符串。
例子:
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 定义结构体
type ColorGroup struct {
ID int
Name string
Colors []string
}
// 创建一个struct对象
group := ColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
// 将struct对象转换成json
b, err := json.Marshal(group)
if err != nil {
// 转换失败,错误处理
fmt.Println("error:", err)
}
// 因为Marshal返回的是字节数组,所以这里需要转换成字符串
fmt.Println(string(b))
}
输出:
{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
通过上面例子,我们知道如何将一个struct转换成json,默认json字段名和struct字段名是一样的,如果我们需要自定义json的字段名怎么处理?
通过struct标签定义json属性:
下面是标签定义的基本用法举例:
// 字段被忽略
Field int `json:"-"`
// 字段在json里的键为"myName"
Field int `json:"myName"`
// 字段在json里的键为"myName"且如果字段为空值将在对象中省略掉
Field int `json:"myName,omitempty"`
// 字段在json里的键为"Field"(默认值),但如果字段为空值会跳过;注意前导的逗号
Field int `json:",omitempty"`
上面介绍json标签的语法,下面看一个例子:
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 定义结构体
type User struct {
Id int
UserName string `json:"user_name"` // 定义json字段为user_name
Email string `json:"-"` // 忽略email字段
}
// 创建一个struct对象
u := User{
Id:100,
UserName:"tizi",
Email:"tizi@tizi365.com",
}
// 将struct对象转换成json
b, err := json.Marshal(u)
if err != nil {
// 转换失败,错误处理
fmt.Println("error:", err)
}
// 因为Marshal返回的是字节数组,所以这里需要转换成字符串
fmt.Println(string(b))
}
输出:
{"Id":100,"user_name":"tizi"}
UserName字段名改变为user_name, Email字段被忽略了。
2. Unmarshal
函数定义:
func Unmarshal(data []byte, v interface{}) error
Unmarshal函数解析json编码的数据并将结果存入v指向的值, Unmarshal和Marshal做相反的操作。
例子:
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 定义结构体
type User struct {
Id int
UserName string `json:"user_name"` // 定义json字段为user_name
Email string `json:"-"` // 忽略email字段
}
// json数据,因为Unmarshal函数接受的是字节数组,所以json数据以字节数组的形式保存
jsonData := []byte(`{"Id":100,"user_name":"tizi"}`)
//定义一个struct对象, 用来保存json解析后的数据
u := User{}
// 将struct对象转换成json
err := json.Unmarshal(jsonData, &u)
if err != nil {
// 转换失败,错误处理
fmt.Println("error:", err)
}
fmt.Println(u)
}
json和struct互相转换支持复杂的数据类型,例如:json嵌套、struct嵌套。
golang 数据类型转换
下面是go语言中各种数据类型之间互相转换的方法。
1. 基本数据类型转换
语法:
t := 新类型(变量)
例子:
var a int = 1
// 将int类型转换成int32类型
b := int32(a)
// 将int32转换成float64类型
c := float64(b)
数值类型之间可以互相转换。
2. 数字转字符串
a := 100
// 将int类型转换成字符串, 支持int, int32, int64
s := strconv.FormatInt(int64(a), 10)
var a1 uint = 100
// 将uint无符号类型转换成字符串,支持uint、uint32、uint64
s1 := strconv.FormatUint(uint64(a1), 10)
使用Sprintf将数字类型转换成字符串
// 将浮点数类型,转换成字符串,并且保留2位小数 (%.2f)
s2 := fmt.Sprintf("%.2f", 25.222)
// 将整数转换成字符串
s3 := fmt.Sprintf("%d", 1000)
3. 字符串转数字
a := "100"
d, _ := strconv.Atoi(a)
fmt.Println(d)
4. interface类型转换
语法:
t := v.(type)
v是interface{}类型,type是我们要转换的类型。
注意: interface类型转换,只有类型完全匹配的时候才能转换,例如: interface变量保存的是int类型,只能转换成int类型,不能转换成int32类型。
例子:
var d int = 1000
// 将d的值保存到interface变量中
var i interface{} = d
// 将interface类型转换成原来的类型
v, ok := i.(int)
// 通过ok判断转换是否成功
if ok {
fmt.Println("转换成功", v)
} else {
fmt.Println("转换失败")
}
5. string转byte数组
s := "我是字符串"
// 转换成功字节数组
bs := []byte(s)
6. byte数组转string
data := []byte("我是字节数组")
// 转换成字符串
s := string(data)
golang os包
go语言os包提供了操作系统函数的不依赖平台的接口。
下面是golang常用的系统函数.
1. Hostname
函数定义:
func Hostname() (name string, err error)
Hostname返回内核提供的主机名。
2. Environ
函数定义:
func Environ() []string
返回所有的环境变量,的格式为"key=value"的字符串的切片拷贝。
3. Getenv
函数定义:
func Getenv(key string) string
Getenv检索并返回名为key的环境变量的值。如果不存在该环境变量会返回空字符串。
4. Setenv
函数定义:
func Setenv(key, value string) error
Setenv设置名为key的环境变量。如果出错会返回该错误。
5. Exit
函数定义:
func Exit(code int)
Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
6. Getuid
函数定义:
func Getuid() int
Getuid返回调用者的用户ID。
7. Getgid
函数定义:
func Getgid() int
Getgid返回调用者的组ID。
8. Getpid
函数定义:
func Getpid() int
Getpid返回调用者所在进程的进程ID。
9. Getwd
函数定义:
func Getwd() (dir string, err error)
Getwd返回一个对应当前工作目录的根路径。如果当前目录可以经过多条路径抵达(因为硬链接),Getwd会返回其中一个。
10. Mkdir
函数定义:
func Mkdir(name string, perm FileMode) error
Mkdir使用指定的权限和名称创建一个目录。如果出错,会返回*PathError底层类型的错误。
例子:
os.Mkdir("/var/tizi", 0666)
11. MkdirAll
函数定义:
func MkdirAll(path string, perm FileMode) error
MkdirAll创建目录包括子目录,这是跟Mkdir最大的区别
12. Remove
函数定义:
func Remove(name string) error
Remove删除name指定的文件或目录。如果出错,会返回*PathError底层类型的错误。
RemoveAll函数,跟Remove用法一样,区别是会递归的删除所有子目录和文件
go 执行command/shell
go语言的exec包提供了执行外部命令的封装。通过exec包我们可以执行外部命令(command)或者shell脚本。
1.执行命令并获取执行结果
package main
import (
"fmt"
"os/exec"
)
func main() {
// 通过exec.Command函数执行命令或者shell
// 第一个参数是命令路径,当然如果PATH路径可以搜索到命令,可以不用输入完整的路径
// 第二到第N个参数是命令的参数
// 下面语句等价于执行命令: ls -l /var/
cmd := exec.Command("/bin/ls", "-l", "/var/")
// 执行命令,并返回结果
output,err := cmd.Output()
if err != nil {
panic(err)
}
// 因为结果是字节数组,需要转换成string
fmt.Println(string(output))
}
2.执行命令不返回结果
package main
import (
"os/exec"
)
func main() {
cmd := exec.Command("/bin/ls", "-l", "/var/")
// 执行命令,返回命令是否执行成功
err := cmd.Run()
if err != nil {
// 命令执行失败
panic(err)
}
}
golang url解析
golang语言net/url包,提供了url解析,url参数处理的函数。
1.url地址解析
package main
import (
"fmt"
"net/url"
)
func main() {
// 解析url地址
u, err := url.Parse("http://bing.com/search?q=dotnet")
if err != nil {
panic(err)
}
// 打印格式化的地址信息
fmt.Println(u.Scheme) // 返回协议
fmt.Println(u.Host) // 返回域名
fmt.Println(u.Path) // 返回路径部分
fmt.Println(u.RawQuery) // 返回url的参数部分
params := u.Query() // 以url.Values数据类型的形式返回url参数部分,可以根据参数名读写参数
fmt.Println(params.Get("q")) // 读取参数q的值
}
2.url参数编码(encode)
url包提供了url.Values类型,专门用来处理url参数。
// 定一个Values
v := url.Values{}
// 设置url参数
v.Set("name", "Ava")
// 添加参数,类似set,区别是add函数会将值追加到同一个参数的值后面,形成数组
v.Add("friend", "Jess")
v.Add("friend", "Sarah")
v.Add("friend", "Zoe")
// 调用Encode函数,对参数进行编码
fmt.Println(v.Encode())
// 输出:"name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
// 下面是读取url参数的方式
fmt.Println(v.Get("name"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])
3.url参数解码(decode)
package main
import (
"fmt"
"net/url"
)
func main() {
// url参数
q := "username=tizi&password=12345&type=100"
// 解析url参数
values, err := url.ParseQuery(q)
if err != nil {
panic(err)
}
// 根据参数名查询参数值
fmt.Println(values.Get("username"))
fmt.Println(values.Get("password"))
fmt.Println(values.Get("type"))
}
golang md5加密
go语言内置的crypto/md5包,提供了md5加密处理的函数库,下面介绍golang如何进行md5加密。
例子:
package main
import (
"crypto/md5"
"fmt"
)
func main() {
// 待加密字符串
s := "https://www.tizi365.com"
// 进行md5加密,因为Sum函数接受的是字节数组,因此需要注意类型转换
srcCode := md5.Sum([]byte(s))
// md5.Sum函数加密后返回的是字节数组,需要转换成16进制形式
code := fmt.Sprintf("%x", srcCode)
fmt.Println(string(code))
}
golang 字符串操作
go语言的strings包实现了处理字符串的简单函数,例如:字符串搜索、字符串分割、裁剪等等。
下面介绍常用的字符串操作函数
1.Contains
函数定义:
func Contains(s, substr string) bool
判断字符串s是否包含子串substr。
例子:
fmt.Println(strings.Contains("seafood", "foo"))
fmt.Println(strings.Contains("seafood", "bar"))
fmt.Println(strings.Contains("seafood", ""))
fmt.Println(strings.Contains("", ""))
输出:
true
false
true
true
2.Count
函数定义:
func Count(s, sep string) int
返回字符串s中有几个不重复的sep子串
例子:
// 输出:3, 字符e,在cheese中出现3次
fmt.Println(strings.Count("cheese", "e"))
3.Index
函数定义:
func Index(s, sep string) int
字符串查找函数,含义是子串sep在字符串s中第一次出现的位置,不存在则返回-1
例子:
fmt.Println(strings.Index("chicken", "ken"))
fmt.Println(strings.Index("chicken", "dmr"))
输出:
4
-1
LastIndex函数的作用和用法跟Index类似,区别是LastIndex返回最后一个匹配到的子字符串,不存在则返回-1。
4.字符串转换成大写字母
s := strings.ToUpper("Gopher")
5.字符串转换成小写字母
s := strings.ToLower("Gopher")
6.Trim
函数定义:
func Trim(s string, cutset string) string
将s字符串前后位置的所有cutset字符都去掉,例如,去掉字符串前后的空格, 第二个参数就是我们要去掉的字符
s := strings.Trim(" www.tizi365.com ", " ")
7.TrimSpace
将s前后端所有空白(unicode.IsSpace指定)都去掉的字符串。
fmt.Println(strings.TrimSpace(" \t\n a lone gopher \n\t\r\n"))
输出:
a lone gopher
8.Replace
函数定义:
func Replace(s, old, new string, n int) string
将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
例子:
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
输出:
oinky oinky oink
moo moo moo
9.Split
函数定义:
func Split(s, sep string) []string
用sep字符串去切割s字符串,最后返回一个切割后的字符串数组
例子:
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
输出:
["a" "b" "c"]
10.Join
函数定义:
func Join(a []string, sep string) string
将一系列字符串连接为一个字符串,字符串之间用sep来分隔,Join函数就是Split函数的反向操作。
例子:
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
输出:
foo, bar, baz
golang base64编码
go语言的encoding/base64包提供了base64编码函数。
// 导入包
import "encoding/base64"
1.base64编码
// 待编码字符串,因为base64编码函数接受的参数是字节数组,因此需要转换下类型
data := []byte("any + old & data")
// base64编码
str := base64.StdEncoding.EncodeToString(data)
fmt.Println(str)
2.base64解码
str := "c29tZSBkYXRhIHdpdGggACBhbmQg77u/"
// 对字符串进行base64解码, 返回的结果是字节数组,自己根据需要转换类型
data, err := base64.StdEncoding.DecodeString(str)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("%q\n", data)
golang math数据函数
go语言math包提供了基本的数学常数和数学函数。
下面是常用的数学函数:
1.Ceil
func Ceil(x float64) float64
返回不小于x的最小整数(的浮点值),特例如下:
Ceil(±0) = ±0
Ceil(±Inf) = ±Inf
Ceil(NaN) = NaN
例子:
fmt.Println(math.Ceil(5.1)) // 输出: 6
2.Floor
func Floor(x float64) float64
返回不大于x的最大整数(的浮点值)
例子:
fmt.Println(math.Floor(5.1)) // 输出: 5
3.Abs
func Abs(x float64) float64
返回x的绝对值
d := math.Abs(-5.1) // 返回: 5.1
4.Max
func Max(x, y float64) float64
返回x和y中最大值
例子:
fmt.Println(math.Max(1,10)) // 输出: 10
5.Min
func Min(x, y float64) float64
返回x和y中最小值
例子:
fmt.Println(math.Min(1,10)) // 输出: 1
6.Sqrt
func Sqrt(x float64) float64
返回x的二次方根
例子:
fmt.Println(math.Sqrt(4)) // 输出: 2
7.Pow
func Pow(x, y float64) float64
计算x的y次方
例子:
fmt.Println(math.Pow(2,10)) // 输出: 1024
golang 随机数生成
go语言rand包实现了伪随机数生成器。
导入包:
import "math/rand"
获取随机数的例子:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 使用当前的纳秒作为随机数种子
rand.Seed(time.Now().UnixNano())
// 返回大于等于0且小于100的随机数
fmt.Println(rand.Intn(100))
}
注意: 上面的例子,使用默认的随机数生成器,生成随机数;在并发高的时候会因为加锁问题,并发性能不高,为避免加锁问题,可以分别为协程新建单独的随机数生成器,避开加锁问题。
使用随机数生成器,生成随机数的例子:
package main import ( "fmt" "math/rand" "time" ) func main() { // 使用当前的纳秒作为随机数种子, 创建一个随机数生成器 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) // 返回大于等于0且小于100的随机数 fmt.Println(rnd.Intn(100)) }