Go语言高级学习

目录

1 异常处理

1.1 error接口

1.2 defer

 1.3 panic和recover

 2 I/O操作

 2.1 创建

 2.2 读写

2.3 其他包

 3 net/http网络编程

3.1 服务端

3.2 客户端

3.3 模板

 3.4 JSON

4 并发编程

4.1 基本概念

4.2 Goroutine使用

4.3 runtime包

4.4 channel

4.5 sync包

5 数据库操作

5.1 连接Mysql

5.2 增删改查


 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语言开发实战(千峰教育)

前景 · Go语言中文文档

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值