Go语言入门——进阶语法篇(四)

短命女 2024-04-17 22:03 153阅读 0赞

文章目录

  • 异常处理与文件
    • 异常处理
      • error 处理
      • panic 与 recover
      • 延迟处理
    • 文件读写
      • 带缓冲区
      • 使用 ioutil
      • 文件追加
      • 文件的其他操作
        • 获取文件信息
        • 判断文件是否存在
        • 文件拷贝
        • 移动、删除和重命名
  • 欢迎关注我的公众号:编程之路从0到1

异常处理与文件

异常处理

error 处理

Go语言没有类似Java或Python那种try...catch...机制处理异常,Go的哲学是与众不同的,Go的设计者认为主流的异常处理机制是一种被过度滥用的技巧,而且存在很大的潜在危害,Go的异常处理(或者说是错误处理)是一种非常简单直观的方式。通常的,我们在写Java、Python之类的代码时,遇到可能存在的异常,直接用try括起来,使用catch捕获,然后就万事大吉了,当系统长时间的运行时,大大增加了不稳定性,所积累的问题可能在某一刻爆发。而Go者使用一种称为"恐慌的"机制,在有必要时,直接让系统宕机,让问题发生时立刻暴露出来,不必累积。很难说哪种设计更好,但Go语言确实简化了代码。

Go语言引入了一个错误处理的标准接口:error接口,并习惯性的默认将错误作为最后一个返回值,当然,如果有的话。如果我们要自定义错误类型,实现该接口即可

  1. type error interface {
  2. Error() string
  3. }

错误处理示例

  1. type MyError struct {
  2. content string
  3. errorCode int
  4. }
  5. // 让MyError实现error接口
  6. func (this *MyError) Error() string {
  7. return fmt.Sprintf("%d:%s",this.errorCode,this.content)
  8. }
  9. func div(a, b int) (r int, err error) {
  10. var e *MyError = new(MyError)
  11. if b == 0 {
  12. e.errorCode = 101
  13. e.content = "除数不能为0"
  14. return 0, e
  15. }
  16. return a / b, nil
  17. }
  18. func main() {
  19. r, e := div(1, 0)
  20. if e != nil {
  21. // 错误不为空,打印错误
  22. fmt.Println(e)
  23. return
  24. }
  25. fmt.Println(r)
  26. }

打印结果:

  1. 101:除数不能为0

自定义类型实现error接口可以提供更丰富的错误信息,但有时候我们希望快速的生成一个简单的错误,而不是写个结构体,那么Go还提供了一种快捷创建错误的方式,使用errors

  1. package main
  2. import (
  3. "fmt"
  4. "errors"
  5. )
  6. var errByZero = errors.New("除数不能为0")
  7. func div(a, b int) (r int, err error) {
  8. if b == 0 {
  9. return 0, errByZero
  10. }
  11. return a / b, nil
  12. }

注意,为了提升性能,errors.New方法不建议在函数中调用,错误的内容是不会变的,可以在函数外声明好需要的错误,就如同声明一些常量一样。该方法虽然简单,但是包含的错误信息有限,酌情使用。

panic 与 recover

panic词义为恐慌,recover则表示恢复。

仍以除数是0为例

  1. func div(a, b int) int {
  2. if b == 0 {
  3. panic("crash:除数为0")
  4. }
  5. return a / b
  6. }
  7. func main() {
  8. r := div(1, 0)
  9. fmt.Println(r)
  10. }

运行代码后,程序直接奔溃,并输出了调用信息

  1. panic: crash:除数为0
  2. goroutine 1 [running]:
  3. main.div(...)
  4. C:/Users/ysk/Desktop/hello.go:12
  5. main.main()
  6. C:/Users/ysk/Desktop/hello.go:18 +0x41
  7. exit status 2

这样,当我在开发和调试时,出现问题,通过手动调用panic让程序崩溃,及时发现并解决问题,包括生成环境中的测试,而不是等到系统上线运行一段时候之后才发现问题。

有时候,我们可能很害怕奔溃,recover则可以在这种奔溃发生时,恢复程序,使得程序可以继续运行。简单说,panicrecover的组合,可以模拟实现Java中的try...catch机制,将异常捕获,而不是继续向上传递。但这并不是Go语言所推崇的用法。

  1. func main() {
  2. // 加上一段代码,defer后面跟一个匿名函数,匿名函数中使用recover捕获到错误
  3. defer func(){
  4. err := recover()
  5. fmt.Printf("处理:%s\n",err)
  6. }()
  7. r := div(1, 0)
  8. fmt.Println(r)
  9. }

打印结果:

  1. 处理:crash:除数为0

可以看到,使用recover处理后,程序不再奔溃了。

延迟处理

上面示例出现了一个关键字defer,该关键字就是用于延迟处理。我们上面说了Java中的trycatch,那怎么能没有finally呢。defer其实就相当于finally,在整个函数调用完后,最后执行一些关闭句柄的功能。Go中,defer除了关闭句柄,还可用于释放并发锁。

  1. func main() {
  2. defer fmt.Println("这是defer调用")
  3. fmt.Println("Hello,world!")
  4. fmt.Println("Hello,go!")
  5. }

打印结果:

  1. Hello,world!
  2. Hello,go!
  3. 这是defer调用

可以看到,defer语句写在最先,但是却在最后才被执行。同一个函数中是可以使用多个defer语句的,多个defer语句的执行顺序遵循栈结构特点,先进后出,最先的defer语句最后执行

  1. func main() {
  2. defer fmt.Println("这是defer语句1")
  3. defer fmt.Println("这是defer语句2")
  4. fmt.Println("Hello,world!")
  5. defer fmt.Println("这是defer调用3")
  6. fmt.Println("Hello,go!")
  7. defer fmt.Println("这是defer调用4")
  8. }

打印结果:

  1. Hello,world!
  2. Hello,go!
  3. 这是defer调用4
  4. 这是defer调用3
  5. 这是defer语句2
  6. 这是defer语句1

文件读写

带缓冲区

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "io"
  6. "bufio" // 导入缓冲包
  7. )
  8. func main() {
  9. // 1. 创建文件
  10. file , err := os.Create("D:/test.txt")
  11. if err != nil{
  12. fmt.Println("创建文件失败")
  13. return
  14. }
  15. // 2. 关闭文件
  16. defer file.Close()
  17. // 3. 创建写入缓冲
  18. w := bufio.NewWriter(file)
  19. // 4. 写入字符串
  20. len, err := w.WriteString("hello world")
  21. if err != nil {
  22. fmt.Println("写入失败")
  23. }else{
  24. fmt.Printf("写入了%d个字符\n",len)
  25. }
  26. // 4. 刷新缓冲,写入硬盘
  27. w.Flush()
  28. // -------------------分割线---------------------
  29. // 1. 打开文件
  30. rfile, e := os.Open("D:/test.txt")
  31. if e != nil{
  32. fmt.Println("打开文件失败")
  33. return
  34. }
  35. // 2.关闭文件
  36. defer rfile.Close()
  37. // 3. 创建读取缓冲
  38. r := bufio.NewReader(rfile)
  39. // 4. 读取文件。使用带缓冲的方式读取文件,只能一行一行的读取
  40. // 该方式适用于高效的读写大文件
  41. for {
  42. str,err := r.ReadString('\n')
  43. fmt.Println(str)
  44. if err == io.EOF { // io.EOF表示文件的末尾
  45. break
  46. }
  47. }
  48. }

小结

  1. 使用os.Create创建文件会覆盖掉已存在的文件
  2. 缓冲区默认大小为4096,可以使用NewReaderSizeNewWriterSize在创建缓冲时指定大小
  3. 如需以二进制方式读写文件,将WriteStringReadString换成WriteByte(c byte)ReadByte(),亦可使用Write(p []byte)Read(p []byte)方法。区别是WriteByteReadByte每次读写一个字节,WriteRead每次读写一个切片的字节。

使用 ioutil

在操作小文件时,可以不指定缓冲区,那么就可以使用一种更简单的方式读写文件。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main() {
  7. // 1. 字符串转字节切片
  8. buf := []byte("golang write string")
  9. // 2. 将字节切片直接写入文件,文件不存在则创建,存在则覆盖
  10. err := ioutil.WriteFile("D:/io_test.txt", buf, 0666)
  11. if err != nil {
  12. fmt.Println("写入失败")
  13. } else {
  14. fmt.Println("写入成功")
  15. }
  16. // -------------------分割线---------------------
  17. // 1. 读取指定文件,返回字节切片
  18. data, e := ioutil.ReadFile("D:/io_test.txt")
  19. if e != nil {
  20. fmt.Println(e)
  21. } else {
  22. // 2. 将字节切片转字符串输出
  23. fmt.Println(string(data))
  24. }
  25. }

小结

  1. 使用ioutil不需要手动打开和关闭文件,打开和关闭操作已被封装了
  2. 使用ioutil.WriteFile仍然存在覆盖已有文件的问题,如需对文件进行追加操作,应使用其他方式
  3. WriteFileReadFile是以字节的方式操作文件,如需处理文本文件,应手动转换字节与字符串

文件追加

很多时候我们不希望新文件覆盖旧文件,而是在旧文件中继续添加内容。这时候必须使用指定模式的方式来打开文件。

  1. package main
  2. import (
  3. "fmt"
  4. "bufio"
  5. "os"
  6. )
  7. func main() {
  8. // 1. 打开文件,指定文件操作模式: 读写追加
  9. file,err := os.OpenFile("D:/test.txt",os.O_RDWR|os.O_APPEND,0666)
  10. if err != nil{
  11. fmt.Println("打开文件失败")
  12. return
  13. }
  14. // 2. 函数执行结束关闭文件
  15. defer file.Close()
  16. // 3. 创建写缓冲
  17. writer := bufio.NewWriter(file)
  18. writer.WriteString("text content")
  19. // 4. 刷新缓冲
  20. writer.Flush()
  21. }

常用组合






































模式组合 说明
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 可读可写模式
os.O_WRONLY|os.O_CREATE 写|创建
os.O_WRONLY|os.O_TRUNC 写|覆盖
O_WRONLY|os.O_APPEND 写|追加
os.O_RDWR|os.O_APPEND 读写|追加

小结

  • os.OpenFile函数的最后一个参数表示Unix系统中的文件权限,在Windows系统上被忽略。

文件的其他操作

获取文件信息

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. // 获取文件信息
  8. fileInfo, err := os.Stat("D:/test.txt")
  9. if err != nil{
  10. fmt.Println("打开文件失败")
  11. return
  12. }
  13. fmt.Println("文件名:", fileInfo.Name())
  14. fmt.Println("文件大小:", fileInfo.Size())
  15. fmt.Println("文件权限:", fileInfo.Mode())
  16. fmt.Println("最后修改时间:", fileInfo.ModTime())
  17. fmt.Println("是否是文件夹:", fileInfo.IsDir())
  18. fmt.Printf("系统信息:%+v\n", fileInfo.Sys())
  19. }

判断文件是否存在

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. // 定义一个函数,判断文件是否存在
  7. func exists(path string) (bool, error) {
  8. _, err := os.Stat(path)
  9. if err == nil {
  10. return true, nil
  11. }
  12. if os.IsNotExist(err) {
  13. return false, nil
  14. }
  15. return false, err
  16. }
  17. func main() {
  18. if b,_ := exists("D:/test.txt");b {
  19. fmt.Println("文件存在")
  20. }else{
  21. fmt.Println("文件不存在")
  22. }
  23. }

文件拷贝

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "io"
  6. "bufio"
  7. )
  8. func main() {
  9. var dst string = "D:/files/test.txt" // 目标路径
  10. var src string = "D:/test.txt" // 源路径
  11. srcFile ,err := os.Open(src)
  12. if err != nil {
  13. fmt.Println("打开失败:",err)
  14. return
  15. }
  16. defer srcFile.Close()
  17. reader := bufio.NewReader(srcFile)
  18. dstFile,err := os.OpenFile(dst,os.O_WRONLY|os.O_CREATE,0666)
  19. if err != nil {
  20. fmt.Println("打开失败:",err)
  21. return
  22. }
  23. defer dstFile.Close()
  24. writer := bufio.NewWriter(dstFile)
  25. // 使用Coyp函数完成文件拷贝
  26. if _, err = io.Copy(writer, reader); err != nil {
  27. fmt.Println("拷贝失败:",err)
  28. return
  29. }
  30. fmt.Println("拷贝成功!")
  31. }

移动、删除和重命名

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. src ,dst := "D:/test.txt","D:/workspace/test.txt"
  8. // 文件移动(Rename既可重命名也可移动文件)
  9. err := os.Rename(src, dst)
  10. if err != nil {
  11. fmt.Println("移动失败:",err)
  12. return
  13. }
  14. // 文件删除
  15. err = os.Remove("D:/io_test.txt")
  16. if err != nil {
  17. fmt.Println("删除失败:",err)
  18. return
  19. }
  20. // 文件重命名
  21. oldName, newName := "D:/logcat.txt", "D:/log.txt"
  22. err = os.Rename(oldName, newName)
  23. if err != nil {
  24. fmt.Println("重命名失败:",err)
  25. return
  26. }
  27. }

欢迎关注我的公众号:编程之路从0到1

编程之路从0到1

发表评论

表情:
评论列表 (有 0 条评论,153人围观)

还没有评论,来说两句吧...

相关阅读