Golang教程五(异常处理,泛型,文件操作)

目录

一、异常处理

错误处理与异常区分

Panic 与 Recover

使用原则

二、泛型

泛型函数

 泛型结构体

泛型切片

泛型Map

三、文件操作

1.基础io

Reader interface

Writer interface

Seek interface

Close interface

文件打开模块

1.文件读取

一次性读取

获取当前go文件的路径

分片读

按缓冲读

按行读

光标操作

按分割符读

2.文件写入

文件的打开方式

文件的权限

常规写

快速写

缓冲写

3.文件复制

4.目录操作


一、异常处理

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()) // 文件名,文件大小,单位比特
	}
}

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值