目录
一、异常处理
Go 语言的异常处理机制与传统的面向对象语言(如 Java、C#)所使用的 try-catch 结构有所不同,它采用了自己独特的设计理念和方法。
错误处理与异常区分
在 Go 中,错误通常用来表示预期可能会发生的运行时问题,如文件未找到、网络请求失败等。这类问题不是程序本身的严重错误,而是程序运行过程中可能遇到的常态。Go 通过返回值(通常是第二个返回值)来传递错误信息,使得函数调用者能够检查并处理这些错误。常见的做法是在函数签名中添加一个
error
类型的返回值,如:
func SomeFunction(arg string) (result int, err error) {
// ...
if someErrorCondition {
return 0, errors.New("An expected error occurred")
}
// ...
}
调用者需要显式检查返回的 err
是否为 nil
来判断是否发生错误:
result, err := SomeFunction("input")
if err != nil {
// Handle the error here
log.Printf("An error occurred: %v", err)
} else {
// Use the result
fmt.Println(result)
}
Panic 与 Recover
Panic 是 Go 语言中用于表示异常或不可恢复的错误情况的一种机制。当程序遇到严重的、无法正常处理的问题,如内部逻辑错误、资源耗尽、不兼容的数据状态等,可以主动调用 panic() 函数来触发恐慌。恐慌会立即中断当前 goroutine(协程)的正常执行流程,并开始执行 栈展开(stack unwinding),即逐级回溯当前 goroutine 的调用栈,执行沿途遇到的 defer 语句,直到整个 goroutine 终止。
func dangerousFunction() {
if someCriticalCondition {
panic("A critical error occurred!")
}
// ...
}
Recover 是与
panic
配合使用的另一个关键函数,它用于捕获发生在当前 goroutine 中的恐慌,并允许程序从恐慌状态中恢复过来。recover()
只能在defer
函数中有效,因为它依赖于栈展开的过程:
func guardedFunction() {
defer func() {
if r := recover(); r != nil {
// Handle the panic here
log.Printf("Recovered from panic: %v", r)
// Optionally, re-throw or convert to a regular error
}
}()
dangerousFunction()
}
在上述例子中,如果 dangerousFunction()
触发了恐慌,defer
语句中的匿名函数会被调用,此时 recover()
返回恐慌值(通常是 interface{}
类型,可以转换为适当的类型进行处理)。如果没有发生恐慌,recover()
返回 nil
。
使用原则
- 错误处理 应作为程序常态的一部分,对于可预见的问题使用返回错误的方式处理,鼓励程序员显式检查并适当地处理这些错误。
- Panic 应仅用于表示程序中的严重错误或不一致性,通常是那些不应该在正常运行时出现的情况,或者在出现时程序无法安全地继续执行的情况。
- Recover 通常用于特定的错误边界,如服务入口点、重要组件的外部接口等,以防止局部的恐慌导致整个程序崩溃。对于大多数常规业务逻辑,直接让恐慌向上冒泡到顶层,由程序的主 goroutine 或全局的 recovery 机制来处理更为合适。
- 使用
panic
和recover
时应保持谨慎,过度使用可能导致代码难以理解和维护。良好的编程实践是尽可能通过返回错误来处理问题,而非频繁引发恐慌。
综上所述,Go 语言通过明确区分错误处理与异常处理,提倡简洁、显式的错误传播机制,并提供 panic
和 recover
作为处理程序运行时严重问题的手段,构建了一套独特的异常处理体系。开发者应遵循这些原则,确保程序在遇到问题时能够优雅、可控地响应。
二、泛型
Go 语言的泛型(Generics)是该语言的一个重要特性,它允许开发者编写具有类型参数的通用代码,从而提高代码的复用性、减少重复,并增强代码的可读性和安全性。Go 语言的泛型设计经过多年的讨论和迭代,在 Go 1.18 版本(2022年3月发布)中正式引入。以下是对 Go 语言泛型的关键概念、基本用法以及设计特点的概述:
泛型函数
如果我们要实现一个对int类型的求和函数
func add(a, b int) int {
return a + b
}
但是这样写了之后,如果参数是float类型,就没办法使用了,难道要为每个类型都写一个这样的函数吗?
显然这就不合理
这个时候,泛型就上场了
func add[T int | float64 | int32](a, b T) T {
return a + b
}
泛型结构体
package main
import (
"encoding/json"
"fmt"
)
type Response[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data T `json:"data"`
}
func main() {
type User struct {
Name string `json:"name"`
}
type UserInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
//user := Response{
// Code: 0,
// Msg: "成功",
// Data: User{
// Name: "os_lee",
// },
//}
//byteData, _ := json.Marshal(user)
//fmt.Println(string(byteData))
//userInfo := Response{
// Code: 0,
// Msg: "成功",
// Data: UserInfo{
// Name: "os_lee",
// Age: 18,
// },
//}
//byteData, _ = json.Marshal(userInfo)
//fmt.Println(string(byteData))
var userResponse Response[User]
json.Unmarshal([]byte(`{"code":0,"msg":"成功","data":{"name":"os_lee"}}`), &userResponse)
fmt.Println(userResponse.Data.Name)
var userInfoResponse Response[UserInfo]
json.Unmarshal([]byte(`{"code":0,"msg":"成功","data":{"name":"os_lee","age":18}}`), &userInfoResponse)
fmt.Println(userInfoResponse.Data.Name, userInfoResponse.Data.Age)
}
泛型切片
package main
type MySlice[T any] []T
func main() {
var mySlice MySlice[string]
mySlice = append(mySlice, "枫枫")
var intSlice MySlice[int]
intSlice = append(intSlice, 2)
}
泛型Map
package main
import "fmt"
type MyMap[K string | int, V any] map[K]V
func main() {
var myMap = make(MyMap[string, string])
myMap["name"] = "os_lee"
fmt.Println(myMap)
}
三、文件操作
1.基础io
Reader interface
将len(p)个字节读取到p中
type Reader interface {
Read(p []byte) (n int, err error)
}
Writer interface
用于将p中的数据写入到对象的数据流中
type Writer interface {
Write(p []byte) (n int, err error)
}
Seek interface
offset是指针移动的偏移量
whence表示指针移动的方式
- 0 从数据的头部开始移动指针
- 1 从数据的当前指针位置开始移动指针
- 2 从数据的尾部移动指针
seek设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
Close interface
关闭文件
type Closer interface {
Close() error
}
文件打开模块
O_RDONLY int = syscall.O_RDONLY // 只读
O_WRONLY int = syscall.O_WRONLY // 只写
O_RDWR int = syscall.O_RDWR // 读写
O_APPEND int = syscall.O_APPEND // 追加
O_CREATE int = syscall.O_CREAT // 如果不存在就创建
O_EXCL int = syscall.O_EXCL // 文件必须不存在
O_SYNC int = syscall.O_SYNC // 同步io
O_TRUNC int = syscall.O_TRUNC // 打开时清空文件
1.文件读取
ioutil.ReadFile(name string) ([]byte, error)
os.ReadFile(name string) ([]byte, error)
os.Open(name string) (file *File, err error)
os.OpenFile(name string, flag int, perm FileMode) (*File, error)
一次性读取
package main
import (
"fmt"
"os"
)
func main() {
bs, err := os.ReadFile("./test.txt")
if err != nil {
fmt.Printf("Read file error: %v\n", err)
return
}
fmt.Printf("%s\n", bs)
}
获取当前go文件的路径
可以通过获取当前go文件的路径,然后用相对于当前go文件的路径去打开文件
package main
import (
"fmt"
"runtime"
)
// GetCurrentFilePath 获取当前文件路径
func GetCurrentFilePath() string {
_, file, _, _ := runtime.Caller(1)
return file
}
func main() {
path := GetCurrentFilePath()
fmt.Println(path)
}
分片读
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
panic(err)
}
defer file.Close()
for {
buf := make([]byte, 32)
_, err = file.Read(buf)
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
fmt.Printf("%s", buf)
}
}
按缓冲读
按行读
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Printf("Open file error: %v\n", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
// 按行读取
line, _, err := reader.ReadLine()
fmt.Println(string(line))
if err != nil {
break
}
}
}
光标操作
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Printf("Open file error: %v\n", err)
return
}
defer file.Close()
// 开始位置前进5个字节
var whence = 0
var offset int64 = 5
pos, _ := file.Seek(offset, whence)
fmt.Println("Jump forward 5 bytes from start position:", pos)
// 当前位置回退2个字节
whence = 1
offset = -2
pos, _ = file.Seek(offset, whence)
fmt.Println("Jump back 2 bytes from current position:", pos)
reader := bufio.NewReader(file)
for {
// 按行读取
line, err := reader.ReadString('\n')
if err == io.EOF {
break
}
fmt.Print(line)
}
}
按分割符读
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Printf("Open file error: %v\n", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords) // 按照单词读
//scanner.Split(bufio.ScanLines) // 按照行读
//scanner.Split(bufio.ScanRunes) // 按照中文字符读
//scanner.Split(bufio.ScanBytes) // 按照字节读读,中文会乱码
for scanner.Scan() {
fmt.Println(scanner.Text())
}
err1 := scanner.Err()
if err1 != nil {
panic(err1)
}
}
2.文件写入
文件的打开方式
os.OpenFile(name string, flag int, perm FileMode) (file *File, err error)
os.Create(name string) (*File, error)
io/ioutil.Write(filename string, data []byte, perm fs.FileMode) error
文件操作模式:
覆盖写:os.O_WRONLY | os.O_TRUNC
追加写:os.O_WRONLY | os.O_APPEND
读写并追加:os.O_RDWR | os.OS_APPEND
完整的
const (
O_RDONLY int = syscall.O_RDONLY // 只读
O_WRONLY int = syscall.O_WRONLY // 只写
O_RDWR int = syscall.O_RDWR // 读写
O_APPEND int = syscall.O_APPEND // 追加
O_CREATE int = syscall.O_CREAT // 如果不存在就创建
O_EXCL int = syscall.O_EXCL // 文件必须不存在
O_SYNC int = syscall.O_SYNC // 同步打开
O_TRUNC int = syscall.O_TRUNC // 打开时清空文件
)
文件的权限
主要用于linux系统,在windows下这个参数会被无视,代表文件的模式和权限位,
三个占位符
第一个是文件所有者所拥有的权限
第二个是文件所在组对其拥有的权限
第三个占位符是指其他人对文件拥有的权限
根据UNIX系统的权限模型,文件或目录的权限模式由三个数字表示,分别代表 所有者(Owner) 、组(Group) 和 其他用户(Other) 的权限。每个数字由三个比特位组成,分别代表读、写和执行权限。因此,对于一个mode参数值,它的每一个数字都是一个八进制数字,代表三个比特位的权限组合
R:读,Read的缩写,八进制值为 4;
W:写,Write的缩写,八进制值为 2;
X:执行,Execute的缩写,八进制值为 1;
0444 表示三者均为只读的权限;
0666 表示三者均为“读写”的权限;
0777 表示三者均为读写执行的权限;
0764 表示所有者有读写执行(7=4+2+1)的权限,组有读写(6=4+2)的权限,其他用户则为只读(4=4);
常规写
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test1.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
panic(err)
}
defer file.Close()
byteSlice := []byte("hello world!")
bytesWritten, err := file.Write(byteSlice)
if err != nil {
panic(err)
}
fmt.Printf("Wrote %d bytes\n", bytesWritten)
}
快速写
package main
import "io/ioutil"
func main() {
err := ioutil.WriteFile("abc.txt", []byte("add a new line"), 0644)
if err != nil {
panic(err)
}
}
缓冲写
package main
import (
"bufio"
"os"
)
func main() {
file, err := os.OpenFile("./test1.txt", os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
panic(err)
}
defer file.Close()
msg := "Hello World!\n"
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.Write([]byte(msg))
}
writer.Flush()
}
3.文件复制
将src文件的内容复制到dst文件
package main
import (
"fmt"
"io"
"os"
)
func main() {
read, _ := os.Open("./file1.txt")
write, _ := os.Create("./file3.txt") // 默认是 可读可写,不存在就创建,清空文件
n, err := io.Copy(write, read)
fmt.Println(n, err)
}
4.目录操作
package main
import (
"fmt"
"os"
)
func main() {
dir, _ := os.ReadDir("../go_study")
for _, entry := range dir {
info, _ := entry.Info()
fmt.Println(entry.Name(), info.Size()) // 文件名,文件大小,单位比特
}
}