目录
1 异常处理
1.1 error接口
error本质上是一个接口类型,其中包含一个Error()方法,错误值可以存储在变量中,通过函数返回。注意它必须是函数返回的最后一个值。
type error interface
{
Error() string
}
在Go语言中处理错误的方式通常是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。
import (
"errors"
"fmt"
)
func main(){
res , err := Sqrt(-100)
if err != nil {
fmt.Println(err)
//fmt.Println(err.Error())
} else {
fmt.Println(res)
}
}
errors包下的New()函数返回error对象,errors.New()函数创建新的错误。
//1、创建error对象的方式1
err1 := errors.New("自己创建的错误!")
//2、创建error对象的方式2
err2 := fmt.Errorf("错误的类型%d", 10)
//具体使用
res , err3 := checkAge(-12)
if err3 != nil {
fmt.Println(err3.Error())
fmt.Println(err3)
} else {
fmt.Println(res)
}
//设计一个函数:验证年龄。如果是负数,则返回error
func checkAge(age int) (string, error) {
if age < 0 {
err := fmt.Errorf("您的年龄输入是:%d , 该数值为负数,有错误!", age)
return "", err
} else {
return fmt.Sprintf("您的年龄输入是:%d ", age), nil
}
}
//1、定义结构体,表示自定义错误的类型
type MyError struct {
When time.Time
What string
}
//2、实现Error()方法
func (e MyError) Error() string {
return fmt.Sprintf("%v : %v", e.When, e.What)
}
//3、定义函数,返回error对象。该函数求矩形面积
func getArea(width, length float64) (float64, error) {
errorInfo := ""
if width < 0 || length < 0 {
errorInfo = fmt.Sprintf("长度:%v, 宽度:%v , 均为负数", length, width)
}
if errorInfo != "" {
return 0, MyError{time.Now(), errorInfo}
} else {
return width * length, nil
}
}
1.2 defer
关键字defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。defer语句只能出现在函数或方法的内部。
1)延迟函数
在函数中可以添加多个defer语句。当函数执行到最后时,这些defer语句才会按照逆序执行(报错的时候也会执行),最后该函数返回。
func main() {
defer funcA()
funcB()
defer funcC()
fmt.Println("main over...")
}
func funcA() {
fmt.Println("这是funcA")
}
func funcB() {
fmt.Println("这是funcB")
}
func funcC() {
fmt.Println("这是funcC")
}
执行的结果是:
2)延迟方法
type person struct {
firstName, lastName string
}
func (p person) fullName() {
fmt.Printf("%s %s", p.firstName, p.lastName)
}
func main() {
p := person{"Steven" , "Wang"}
defer p.fullName()
fmt.Println("Welcome ")
}
3)参数延迟
func main() {
a := 5
b := 6
defer printAdd(a, b, true)
a = 10
b = 7
printAdd(a, b, false)
}
func printAdd(a, b int, flag bool) {
if flag {
fmt.Printf("执行延迟函数,参数a,b分别为%d, %d , 两数之和为:%d\n", a, b, a+b)
} else {
fmt.Printf("未执行延迟函数,参数a,b分别为%d, %d , 两数之和为:%d\n", a, b, a+b)
}
}
4)字符串反转
func main() {
str := "hello word"
fmt.Printf("原始字符串:\n %s\n", str)
fmt.Println("翻转后字符串: ")
ReverseString(str)
}
//利用defer堆栈执行字符串倒序排列
func ReverseString(str string) {
for _, v := range []rune(str) {
defer fmt.Printf("%c" , v)
}
}
1.3 panic和recover
1)panic
panic()是一个内建函数,可以中断原有的控制流程。
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB()//TestB()发生异常,中断程序
TestC()
}
通常情况下,普通错误是返回一个额外的error类型值。当遇到不可恢复的错误状态时,如数组访问越界、空指针引用等,这些运行时错误会引起panic异常。
2)recover
panic异常一旦被引发就会导致程序崩溃,Go语言为此提供了recover()。recover()可以让进入恐慌流程的Goroutine恢复过来并重新获得流程控制权。注意recover()仅在延迟函数中有效。
在正常的程序运行过程中,调用 recover()会返回 nil,并且没有其他任何效果。如果当前的Goroutine陷入恐慌,调用recover()可以捕获panic()的输入值,使程序恢复正常运行。
func main() {
funcB()
fmt.Println("main over")
}
func funcB() {
defer func() {
if msg := recover(); msg != nil {
fmt.Println("恢复,获取recover的返回值:", msg)
}
}()
fmt.Println("这是funcB")
for i := 0; i < 10; i++ {
fmt.Println("i:", i)
if i == 5 {
panic("funcB恐慌啦")
}
}
}
2 I/O操作
1)fileinfo
//绝对路径
fileInfo , err := os.Stat("/Users/xxx/Documents/1.png")
//相对路径
fileInfo , err = os.Stat("./files/1.docx")
if err !=nil {
fmt.Println("err:" , err.Error())
} else {
//文件名
fmt.Println(fileInfo.Name())
//是否是目录
fmt.Println(fileInfo.IsDir())
//文件尺寸大小
fmt.Println(fileInfo.Size())
//mode 权限
fmt.Println(fileInfo.Mode())
//文件最后修改时间
fmt.Println(fileInfo.ModTime())
}
2)filepath
2.1 创建
os.MKdir()仅创建一层目录。
os.MKdirAll()创建多层目录。
os.Create(name string) (file *File, err Error
根据提供的文件名创建新的文件,返回一个文件对象,如果文件存在,会将其覆盖。
os.NewFile(fd uintptr, name string) *File
根据文件描述符创建相应的文件,返回一个文件对象
os.Open(name string) (file *File, err Error)
只读方式打开一个名称为name的文件,函数本质上是在调用os.OpenFile()函数。
os.OpenFile(name string, flag int, perm uint32) (file *File, err Error)
第一个参数:filename,文件名称。
第二个参数:mode,文件的打开方式。可同时使用多个方式,用“|”分开。
第三个参数:perm,文件的权限。文件不存在时创建文件,需要指定权限。
os.Close()关闭文件
os.Remove(name string) Error
删除文件名为name的文件
2.2 读写
file.Read(b []byte) (n int, err Error)从文件中开始读取数据,返回值n是实际读取的字节数。如果读取到文件末尾,n为0或err为io.EOF。
file.Write(b []byte) (n int, err Error)
写入byte类型的信息到文件
file.WriteAt(b []byte, off int64) (n int, err Error)
在指定位置开始写入byte类型的信息
file.WriteString(s string) (ret int, err Error)
写入string信息到文件
2.3 其他包
1)ioutil包
2)bufio包
3 net/http网络编程
3.1 服务端
1)http.FileServer()
http.FileServer()搭建的服务器只提供静态文件的访问。
http.ListenAndServer()函数用来启动Web服务,绑定并监听http端口。其中第一个参数为监听地址,第二个参数表示提供文件访问服务的HTTP处理器Handler。
Handler是一个接口,其中只有ServeHTTP(http.ResponseWriter, *http.Request)这一个方法。
func main() {
testFileServer()
}
func testFileServer() {
//如果该文件里面有index.html文件,他会优先显示html文件,否则会看到文件目录
http.ListenAndServe(":8008", http.FileServer(http.Dir("./")))
}
2)http. HandleFunc()
http. HandleFunc()的作用是注册网络访问的路由。HandleFunc()的第一个参数是请求路径的匹配模式,第二个参数是一个函数类型,表示这个请求需要处理的事情。
func main() {
//绑定路径,去触发方法
http.HandleFunc("/index", indexHandler)
//绑定端口 ,第一个参数为监听地址,第二个参数表示服务端处理程序,通常为nil,这意味着服务端调用http.DefalutServeMux进行处理。
err := http.ListenAndServe("localhost:8008", nil)
fmt.Println(err)
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("这是默认首页"))
}
3)http. NewServeMux ()
http. NewServeMux ()的作用是注册网络访问的多路路由。
3.2 客户端
1)使用http.NewRequest ()方法
//1.创建一个客户端
client := http.Client{}
//2.创建一个请求。请求方式既可以是Get,也可以是Post。
request, err := http.NewRequest("GET", "https://www.toutiao.com/search/suggest/initial_page/", nil)
CheckErr(err)
//3.客户端发送请求
cookName := &http.Cookie{Name: "username", Value: "Steven"}
//添加cookie
request.AddCookie(cookName)
response, err := client.Do(request)
CheckErr(err)
//设置请求头
request.Header.Set("Accept-Lanauage", "zh-cn")
defer response.Body.Close()
//查看请求头的数据
fmt.Printf("Header:%+v\n", request.Header)
fmt.Printf("响应状态码: %v\n", response.StatusCode)
//4操作数据
if response.StatusCode == 200 {
//data, err := ioutil.ReadAll(response.Body)
fmt.Println("网络请求成功")
CheckErr(err)
//fmt.Println(string(data))
} else {
fmt.Println("网络请求失败", response.Status)
}
2)使用client. Get()方法
3)使用client. Post()或client.PostForm()方法
4)使用http. Get()方法
5)使用http. Post()或http.PostForm()方法
3.3 模板
模板就是在写动态页面时不变的部分,服务端程序渲染可变部分生成动态网页,Go语言提供了html/template包来支持模板渲染。
模板中的变量通过{{.}} 来访问。模板中使用{{/* comment */}} 来进行注释。
Golang渲染template的时候,可以在模板文件中读取变量内的值并渲染到模板里。有两个常用的传入类型。一是struct,在模板内可以读取该struct的内容。二是map[string]interface{},在模板内可以使用key来进行渲染。
内置模板函数
3.4 JSON
1)map转json
m := map[string][]string{
"level": {"debug"},
"message": {"File not found", "Stack overflow"},
}
if data, err := json.Marshal(m); err == nil {
fmt.Printf("%s\n", data)
}
if data, err := json.MarshalIndent(m, "", " "); err == nil {
fmt.Printf("%s\n", data)
}
2)结构体转json
type DebugInfo struct {
Level string
Msg string
author string // 未导出字段不会被json解析(首字母小写)
}
func main() {
dbgInfs := []DebugInfo{
DebugInfo{"debug", `File: "test.txt" Not Found`, "Cynhard"},
DebugInfo{"", "Logic error", "Gopher"},
}
if data, err := json.Marshal(dbgInfs); err == nil {
fmt.Printf("%s\n", data)
}
3)结构体标签
//可通过标签改变json键名
type DebugInfo struct {
Level string `json:"level"` // level 解码为 Level
Msg string `json:"message"` // message 解码为 Msg
Author string `json:"-"` // 忽略Author
}
func (dbgInfo DebugInfo) String() string {
return fmt.Sprintf("{Level: %s, Msg: %s}", dbgInfo.Level, dbgInfo.Msg)
}
func main() {
data := `[{"level":"debug","message":"File Not Found","author":"Cynhard"},` +
`{"level":"","message":"Logic error","author":"Gopher"}]`
var dbgInfos []DebugInfo
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
4)json转map切片
data := `[{"Level":"debug","Msg":"File: \"test.txt\" Not Found"},` +
`{"Level":"","Msg":"Logic error"}]`
var dbgInfos []map[string]string
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
5)json转结构体
type DebugInfo struct {
Level string
Msg string
author string // 未导出字段不会被json解析
}
func (dbgInfo DebugInfo) String() string {
return fmt.Sprintf("{Level: %s, Msg: %s}", dbgInfo.Level, dbgInfo.Msg)
}
func main() {
data := `[{"level":"debug","msg":"File Not Found","author":"Cynhard"},` +
`{"level":"","msg":"Logic error","author":"Gopher"}]`
var dbgInfos []DebugInfo
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
6)结构体字段标签
type DebugInfo struct {
Level string `json:"level"` // level 解码为 Level
Msg string `json:"message"` // message 解码为 Msg
Author string `json:"-"` // 忽略Author
}
func (dbgInfo DebugInfo) String() string {
return fmt.Sprintf("{Level: %s, Msg: %s}", dbgInfo.Level, dbgInfo.Msg)
}
func main() {
data := `[{"level":"debug","message":"File Not Found","author":"Cynhard"},` +
`{"level":"","message":"Logic error","author":"Gopher"}]`
var dbgInfos []DebugInfo
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
4 并发编程
4.1 基本概念
并发(Concurrency)是同时处理许多个任务,实际上是把任务在不同的时间点交给处理器进行处理,在微观层面,任务不会同时运行。
并行(Parallelism)是把每一个任务分配给每一个处理器独立完成,多个任务一定是同时运行。
协程(Coroutine),又称为微线程,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程,一个线程也可以拥有多个协程。
协程是编译器级的,进程和线程是操作系统级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。
Go语言中的协程叫作Goroutine。
4.2 Goroutine使用
在函数或方法前面加上关键字go,将会同时运行一个新的Goroutine。
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
所有Goroutine在main()函数结束时会一同结束。如果main()的 Goroutine比子Goroutine先终止,运行的结果就不会打印“Hello world goroutine”。可以在go hello()之后加上time.Sleep(50 * time.Microsecond)延缓主线程的结束。
func main() {
go printNum()
go printLetter()
time.Sleep(3 * time.Second)
fmt.Println("\n main over...")
}
func printNum() {
for i:=1; i<=5 ;i++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d " , i)
}
}
func printLetter() {
for i:='a'; i<='e' ;i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c " , i)
}
}
4.3 runtime包
runtime.Gosched(),让出CPU时间片,重新等待安排任务
func main() {
go Out("hello")
// 主协程
for i := 0; i < 2; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Println("%d ",i)
}
time.Sleep(3 * time.Second)
}
func Out(s string) {
for i := 0; i < 6; i++ {
time.Sleep(200 * time.Millisecond)
runtime.Gosched() //让出CPU时间片
fmt.Println(s)
}
}
runtime.Goexit()退出当前协程
runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
4.4 channel
channel即Go的通道,是协程之间的通信机制。传统的线程之间可以通过共享内存进行数据交互,不同的线程共享内存的同步问题需要使用锁来解决,这样会导致性能低下。Go语言中提倡使用channel的方式代替共享内存。
1)声明:
var 变量 chan 元素类型
channel是引用类型,需要使用make()进行创建:
make(chan 元素类型, [缓冲大小])
2)数据传输
通过channel发送和接收数据使用相同的特殊的操作符“<-”。channel发送的值的类型必须与channel的元素类型一致。
发送时如果没有接收,那么发送操作将持续阻塞。此时所有的Goroutine,包括main()的Goroutine都处于等待状态。即发生死锁(deadlock)。接收也是一样。
data,ok := <- ch //ok(布尔类型)表示是否接收到数据。通过ok值可以判断当前channel是否被关闭。
<- ch //忽略数据
//1、循环接收数据方式1
for {
data := <-ch1
//如果通道关闭,通道中传输的数据则为各数据类型的默认值。chan int 默认值为0,chan string默认值为"" 等。
if data == "" {
break
}
fmt.Println("从通道中读取数据方式1:", data)
}
//2、循环接收数据方式2
for {
data, ok := <-ch1
fmt.Println(ok)
//通过多个返回值的形式来判断通道是否关闭,如果通道关闭,则ok值为false。
if !ok {
break
}
fmt.Println("从通道中读取数据方式2:", data)
}
//3、循环接收数据方式3
for range循环会自动判断通道是否关闭,自动break循环。
for value := range ch1 {
fmt.Println("从通道中读取数据方式3:", value)
}
func main() {
ch1 := make(chan string)
go sendData(ch1)
//接收方式1 or 2 or 3
}
func sendData(ch1 chan string) {
defer close(ch1)
for i := 0; i < 3; i++ {
ch1 <- fmt.Sprintf("发送数据%d\n", i)
}
fmt.Println("发送数据完毕。。")
}
3)缓冲channel
默认创建的都是非缓冲channel,读写都是即时阻塞。缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。
其实就是在使用make函数初始化通道的时候为其指定通道的容量,容量就是缓冲区大小
4)单向channel
channel默认都是双向的,即可读可写。定向channel也叫单向channel,只读,或只写。
只读
make(<- chan type)
<- chan
只写
make(chan <- type)
chan <- type
直接创建单向channel没有任何意义。通常的做法是创建双向channel,然后以单向channel的方式进行函数传递。
func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func main() {
ch1 := make(chan int)
go counter(ch1)
}
4.5 sync包
1)同步
同步的sync是串行执行,异步的sync是同时执行。
Go语言中可以使用sync.WaitGroup来实现并发任务的同步。
需要确保某些操作在高并发的场景下只执行一次的解决方案–sync.Once
func (o *Once) Do(f func()) {}
其次Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。
2)互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
3)读写互斥锁
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,读写锁在Go语言中使用sync包中的RWMutex类型。
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
rwlock sync.RWMutex
)
func write() {
rwlock.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 解写锁
wg.Done()
}
func read() {
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 解读锁
wg.Done()
}
5 数据库操作
5.1 连接Mysql
使用第三方开源的mysql库: github.com/go-sql-driver/mysql (mysql驱动)
下载导入:go get github.com/Go-SQL-Driver/MySQL。
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
//database, err := sqlx.Open("数据库类型", "用户名:密码@tcp(地址:端口)/数据库名")
if err != nil {
fmt.Println("access mysql failed,", err)
return
}
Db = database
defer db.Close() // 注意这行代码要写在上面err判断的下面
5.2 增删改查
1) 通过db.Exec()可进行增删改操作:
r, err := Db.Exec("insert into person(username, sex, email)values(?, ?, ?)", "admin", "man", "123@qq.com")
es, err := Db.Exec("update person set username=? where user_id=?", "admin", 1)
res, err := Db.Exec("delete from person where user_id=?", 1)
通常使用DB对象的Prepare()方法获得预编译对象stmt,然后调用Exec()方法
smt, err := Db.prepare("insert person set username = ?,set sex = ?, set email= ? ")
res,err := stmt.exec("admin","man","123@qq.com")
获取影响的行数
count,err := res.RowsAffected()
2) 查询
(1)调用db.Query()方法执行SQL语句,此方法返回一个Rows作为查询结果。
(2)将rows.Next()方法的返回值作为for循环的条件,迭代查询数据。
(3)在循环中,通过 rows.Scan()方法读取每一行数据,语法如下所示。
(4)调用db.Close()关闭查询。
//1、查询当行数据
func (dbConn *DbConn) QueryRowData(sqlString string) (data UserTable) {
user := new(UserTable)
err := dbConn.Db.QueryRow(sqlString).Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
if err != nil {
panic(err)
return
}
return *user
}
//2、使用预编译语句进行查询多行数据
func (dbConn *DbConn) PreQueryData(sqlString string , args ...interface{}) (resultSet map[int]UserTable) {
stmt, err := dbConn.Db.Prepare(sqlString)
defer stmt.Close()
if err != nil {
panic(err)
return
}
rows, err := stmt.Query(args ...)
defer rows.Close()
if err != nil {
panic(err)
return
}
resultSet = make(map[int]UserTable)
user := new(UserTable)
for rows.Next() {
err := rows.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
if err != nil {
panic(err)
continue
}
resultSet[user.Uid] = *user
}
return resultSet
}
3)select
var person []Person
err := Db.Select(&person, "select user_id, username, sex, email from person where user_id=?", 1)
if err != nil {
fmt.Println("exec failed, ", err)
return
}
fmt.Println("select succ:", person)
参考:
1 Go语言开发实战(千峰教育)