Day005 Go 异常、包、strings、time、随机数、定时器、IO、Seeker

125 阅读12分钟

自定义错误

package main

import "fmt"

type IferError struct {
    msg  string
    code int
}

// 调用者是谁
// Error(参数)
// 返回值
func (i *IferError) Error() string {
    // fmt.Sprintf 返回一个 string
    return fmt.Sprintf("错误信息:%s\n,代码:%d\n", i.msg, i.code)
}

// error 的使用,如果函数或方法中有预期错误,都需要抛出
func testErr(i int) (int, error) {
    if i != 0 {
       // 0, 实现 IferError
       return 0, &IferError{msg: "错误数据", code: 500}
    }
    return 0, nil
}

func main() {
    info, err := testErr(1)
    if err != nil {
       fmt.Println(err)
       e, ok := err.(*IferError)
       if ok {
          fmt.Println(e.msg)
          // 返回给前端
       }
    }
    fmt.Println(info)
}

异常

基本操作

抛出 Panic

package main

func main() {

    // 什么时候会发生恐慌 panic,我们不知道什么时候会报错
    // 程序运行的时候会发生 panic
    // 手动抛出 panic,我们在一些能够预知到危险的情况下,可以主动抛出
    // 如果有 panic 发生,我们尽可能接收它,并处理
    
    panic("程序 panic 了")
}

执行顺序

package main

import "fmt"

// defer 是延迟函数,先进后出,处理一些问题
// 出现了 panic 后,如果【前面】有 defer 语句,先执行所有的 defer 语句
func main() {
    testPanic(1)
}
func testPanic(num int) {
    defer fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("3")
    if num == 1 {
       panic("出现预定的异常----panic")
    }
    defer fmt.Println("4")
    fmt.Println("5 ")
}

// 3、2、1、报错

c、3、2、1、b、a、报错

package main

import "fmt"

// defer 是延迟函数,先进后出,处理一些问题
// 出现了 panic 后,如果【前面】有 defer 语句,先执行所有的 defer 语句
func main() {
    defer fmt.Println("a")
    defer fmt.Println("b")
    fmt.Println("c")
    // 也不会继续再向下执行了
    testPanic(1)
    defer fmt.Println("d")
    fmt.Println("e")
}
func testPanic(num int) {
    defer fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("3")
    if num == 1 {
       panic("出现预定的异常----panic")
    }
    defer fmt.Println("4")
    fmt.Println("5 ")
}

recover

package main

import "fmt"

// 1. panic 触发
// 2. 执行前面所有 defer 语句,先进后出
// 3. 直到遇到 recover 处理了这个 panic,函数结束
// 4. main,继续向下执行

func main() {
    defer fmt.Println("a")
    defer fmt.Println("b")
    fmt.Println("c")
    // 如果在函数内部已经处理 panic,那么程序会继续向下执行
    testPanic(1)
    defer fmt.Println("d")
    fmt.Println("e")
}
func testPanic(num int) {
    // 出去函数的时候处理这里面可能发生的panic
    // recover func recover() any 返回panic传递的值
    // panic   func panic(v any)
    defer func() {
       if msg := recover(); msg != nil {
          fmt.Println("recover 执行了,panic msg:", msg)
          fmt.Println("---------程序已恢复----------")
       }
    }()

    defer fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("3")
    // 如果在函数中一旦触发了 panic,会终止后面要执行的代码
    if num == 1 {
       panic("出现预定的异常----panic")
    }
    defer fmt.Println("4")
    fmt.Println("5")
}

Go 程序首先在 GOROOT/src 目录中寻找包目录,如果没有找到,则会去 GOPATH/src 目录中继续找,比如 fmt 包是位于 GOROOT/src 目录的,那么它将会从该目录导入。

导入包的时候,要从 GOPATH/src 去写,需要使用包名.函数名来使用。main 函数所在的包,必须是 main 包,表示程序的入口。一个目录下所有的 go 文件的 package 必须同名。

包分类:标准库、第三方、自定义。

GOPATH 模式下导包

先关闭 Goland 编辑器的 Go mod 模式,可以通过 go env 查看。

go env -w GOWORK=路径

我电脑的 GOPATH 是 C:\Users\dangp\go

C:\Users\dangp\go\src\xuego\hello\main.go

package main

import (
    "xuego/hello/api"
    "xuego/hello/db"
)

func main() {
    api.RestAPI1()
    api.RestAPI2()
    db.MysqlHandler()
    db.RedisHandler()
}

C:\Users\dangp\go\src\xuego\hello\api\http.go

package api

import "fmt"

func RestAPI1() {
    fmt.Println("restAPI1")
}

导包

import (
   // "crypto/rand" // 正常导入
   // R "math/rand" // 可以给包起别名
   // . "math/rand" // 简便模式:可以直接调用该包下的函数,不需要通过包名点的形式
   _ "math/rand" // 匿名导入,只会执行这个包下的 init 方法
)

init 函数

init 函数,在 main 方法执行之前执行,通过 init 函数,通常用来初始化一些全局变量,建立一些第三方的连接(数据库连接)、注册、检查、修复程序状态。init 函数可以有多个。

  1. 如果导入了多个匿名包,按照 main 中导入包的顺序来进行执行
  2. 同一个包下的多个 go 文件,都有 init 的情况下,按照文件排放顺序来执行对应的 init 函数
  3. 单个 go 文件中的多个 init 是顺序执行的
  4. 等所有 go 文件中的 init 函数执行完毕后,才会到 main 包

image.png

Go Mod

go env
go env -w GO111MODULE=on

go mode init # Go 会尝试根据当前目录的位置推断模块路径,如果当前目录不在 `GOPATH` 中,并且没有提供明确的模块路径,Go 就无法确定模块路径,会报错

go mod init # 模块路径

# 如果你的模块路径是 `xuego/hello`,那么在导包时首先要写上 'xuego/hello',后面再拼接上其他路径
# 如果你的模块路径是 `xxx`,那么在导包时,首先要写上 `xxx`,后面再拼接上其他路径

设置包的代理

go env -w GOPROXY="https://goproxy.cn,direct"

如何更改模块路径

如果初始化时指定的模块路径不合适,可以随时修改。

  1. 编辑 go.mod 文件,将 module 行改为新的模块路径。例如:
module github.com/username/xuego/hello
  1. 更新项目中的所有导入路径,确保它们与新的模块路径一致。例如:
import "github.com/username/xuego/hello/api"
  1. 运行以下命令更新依赖:
# 在 go.mod 所在的同级目录
go mod tidy

关于 go mod tidy

假如 main.go 中的代码如下:

package main

import (
	"fmt"
	"xxx/api"

	"github.com/jinzhu/now"
)

func main() {
	api.RestAPI1()
	api.RestAPI2()

	fmt.Println(now.BeginningOfMinute())
}

我可以在 xuego/hello 目录执行 go mod tidy 来拉取包中依赖的第三方包,注意执行命令的位置和 go.mod 在同一目录。

第三方的依赖都会放到 GOMODCACHE 对应的目录,即 GOPATH\pkg\mod 目录。

# 让包记录到自己的项目,不再是 GOMODCACHE 啦
go mod vendor

内置的 strings 包

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 1. 字符是不能修改的
    str := "dva, xxx"
    fmt.Println(str[0]) // 100

    // 2. 判断某个字符是否包含了指定的内容
    fmt.Println(strings.Contains(str, "x")) // true

    // 3. 判断某个字符串是否包含了多个字符串中的某一个
    fmt.Println(strings.ContainsAny(str, "xi")) // true

    // 4. 统计这个字符在指定字符串中出现的数量 Count() 计数
    fmt.Println(strings.Count(str, "x")) // 3

    fileName := "20250301.mp3"
    // 5. 判断用什么开头的
    if strings.HasPrefix(fileName, "2025") {
       fmt.Println("找到 2025 开头的文件:", fileName) // 找到 2025 开头的文件: 20250301.mp3
    }
    // 6. 判断用什么结尾的 HasSuffix()
    if strings.HasSuffix(fileName, ".mp3") {
       fmt.Println("找到 mp3 结尾的文件:", fileName) // 找到 mp3 结尾的文件: 20250301.mp3
    }

    // 7. 寻找这个字符串第一次出现的位置 Index()
    fmt.Println(strings.Index(str, "v")) // 1,找不到会返回 -1

    // 8. 寻找这个字符串最后一次出现的位置 LastIndex()
    fmt.Println(strings.LastIndex(str, "a")) // 2

    // 9. 拼接字符串, 数组或者切片拼接,前端给了我们多个参数
    str2 := []string{"a", "b", "c", "d", "e"}
    fmt.Println(strings.Join(str2, "~")) // a~b~c~d~e

    // 10. 通过某个格式,拆分字符串 Split()
    str3 := strings.Join(str2, "~")       // [a~b~c~d~e]
    fmt.Println(strings.Split(str3, "~")) // [a b c d e]

    // 11. 大小写 ToUpper()
    fmt.Println(strings.ToUpper(str)) // DVA, XXX
    fmt.Println(strings.ToLower(str)) // dva, xxx

    // 12. 替换
    fmt.Println(strings.Replace(str, "x", "🤠", 1)) // dva, 🤠xx

    // 13. 截取某个字符串
    str5 := str[0:3]
    fmt.Println(str5) // dva
}

strconv 类型转换方法

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s1 := "true"
    // 1. 字符串转 bool
    b1, err := strconv.ParseBool(s1)
    if err != nil {
       fmt.Println(err)
       return
    }
    fmt.Printf("%T,%t\n", b1, b1) // bool,true

    // 2. 格式化布尔为字符串
    s2 := strconv.FormatBool(b1)
    fmt.Printf("%T,%s\n", s2, s2) // string,true

    // 3. 字符串转 int
    s3 := "100000"
    // 参数:str、源进制(10)、大小
    i1, _ := strconv.ParseInt(s3, 10, 64)
    fmt.Printf("%T,%d\n", i1, i1) // int64,100000

    // 4. 格式化 int 为字符串
    s4 := strconv.FormatInt(i1, 10)
    fmt.Printf("%T,%s\n", s4, s4) // string,100000

    // 5. 字符串转数字(十进制)
    atoi, _ := strconv.Atoi("-20")
    fmt.Printf("%T,%d\n", atoi, atoi) // int,-20

    // 6. 数字转字符串
    itoa := strconv.Itoa(30)
    fmt.Printf("%T,%s\n", itoa, itoa) // string,30
}

time

package main

import (
    "fmt"
    "time"
)

func main() {
    time1()
}

func time1() {
    // 返回值为 Time 结构体
    now := time.Now()

    year := now.Year()
    month := now.Month()
    day := now.Day()
    hour := now.Hour()
    minute := now.Minute()
    second := now.Second()
    // 整数补位: 02 如果不足两位,左侧用 0 补齐输出
    // 2025-03-01 15:12:31
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
package main

import (
    "fmt"
    "time"
)

func main() {
    time2()
}

func time2() {
    now := time.Now()
    // 时间格式化 2023-02-23 20:43:49
    // 格式化模板:yyyy-MM-dd HH:mm:ss
    // Go 语言诞生的时间作为格式化模板:2006 年 1 月 2 号下午 3 点 4 分
    // Go 语言格式化时间的代码:2006-01-02 15:04:05  (记忆方式:2006 12 345)
    // 固定的:"2006-01-02 15:04:05"
    fmt.Println(now.Format("2006-01-02 15:04:05"))    // 24 小时制
    fmt.Println(now.Format("2006-01-02 03:04:05 PM")) // 12 小时制
    fmt.Println(now.Format("2006/01/02 15:04"))       // 2023/02/23 20:52
    fmt.Println(now.Format("15:04 2006/01/02"))       // 20:52 2023/02/23
    fmt.Println(now.Format("2006/01/02"))             // 2023/02/23
}
package main

import (
    "fmt"
    "time"
)

func main() {
    time3()
}

// 将字符串格式化为 Time 对象 (获取到网页传递的时间字符串,需要转化为 Time 才能在代码中使用)
func time3() {
    // 其他地方的时区格式:https://www.zeitverschiebung.net/cn/all-time-zones.html
    // Asia/Shanghai" 注意大小写,如果不对,会报未知的时间错误
    loc, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
       fmt.Println(err)
       return
    }
    // 将字符串解析为时间 Time
    timeStr := "2025-04-01 15:28:33"
    timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)
    fmt.Println(timeObj)
}
package main

import (
    "fmt"
    "time"
)

func main() {
    time4()
}

// 时间戳:更多时候和随机数结合
func time4() {
    // 格林威治时间自 1970 年 1 月 1 日(00:00:00 GMT) 至当前时间的总秒数
    // 1970.1.1 00:00:00
    now := time.Now()
    timestamp1 := now.Unix()     // 时间戳
    timestamp2 := now.UnixNano() // 纳秒的时间数
    fmt.Println(timestamp1)      // 1740814216
    fmt.Println(timestamp2)      // 1740814216281495700
    // 通过 Unix 转换 time 对象
    timeObj := time.Unix(timestamp1, 0) // 返回 time 对象
    year := timeObj.Year()
    month := timeObj.Month()
    day := timeObj.Day()
    hour := timeObj.Hour()
    minute := timeObj.Minute()
    second := timeObj.Second()
    // 2025-03-01 15:30:16
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

随机数

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // 1. 获取一个随机数
    num1 := rand.Int()
    fmt.Println("num1:", num1) // 7621103522683197901

    // 2. [0, 100)
    num2 := rand.Intn(100)
    fmt.Println("num2:", num2) // 67
}

定时器

package main

import (
    "fmt"
    "time"
)

func main() {
    // 每一秒都会触发
    ticker := time.Tick(time.Second)
    for i := range ticker {
       fmt.Println(i)
    }
}

时间比较

package main

import (
    "fmt"
    "time"
)

func main() {
    // 加、减、比较(在 xxx 之前、在 xxx 之后、相等)
    now := time.Now()
    // 1. 加一个小时
    later := now.Add(time.Hour)
    //2025-03-01 16:43:32.3842393 +0800 CST m=+3600.000000001
    fmt.Println(later)

    // 2. later 和现在时间的差值
    subTime := later.Sub(now)
    fmt.Println(subTime)

    // 3. 比较时间, 校验时间当地时间和网络时间是否一致
    fmt.Println(now.Equal(later))  // fasle
    fmt.Println(now.Before(later)) // true
    fmt.Println(now.After(later))  // fasle
}

IO

文件信息

package main

import (
    "fmt"
    "os"
)

// 文件分为文本文件和二进制文件
func main() {
    fileInfo, err := os.Stat("D:\2025\Golang\src\xuego\hello\go.mod")
    if err != nil {
       return
    }
    fmt.Println(fileInfo.Name())    // go.go
    fmt.Println(fileInfo.IsDir())   // false
    fmt.Println(fileInfo.ModTime()) // 2025-02-25 23:05:11.26645 +0800 CST
    fmt.Println(fileInfo.Size())    // 22 字节数
    fmt.Println(fileInfo.Mode())    // -rw-rw-rw-,r可读、w可写、x可执行
}

还有一种表示权限的标识:八进制,例如 chomd 7 7 7

目录的创建和删除

package main

import (
    "fmt"
    "os"
)

func main() {
    // 1. 打开一个文件夹(存在我就打开,不存在就创建这个文件夹)
    // 0777 => 表示可读可写可执行
    err := os.Mkdir("D:\2025\Golang\src\xuego\hello\test", os.ModePerm)
    if err != nil {
       //  存在就无法创建了,Cannot create a file when that file already exists.
       fmt.Println(err)
    }
    fmt.Println("文件夹创建完毕")

    // 2. 创建多层级文件夹
    err2 := os.MkdirAll("D:\2025\Golang\src\xuego\hello\a\b\c", os.ModePerm)
    if err2 != nil {
       fmt.Println(err2)
    }
    fmt.Println("层级文件夹创建完毕")

    // 3. 删除空文件夹
    // 通过 remove方 法只能删除单个空文件夹
    err3 := os.Remove("D:\2025\Golang\src\xuego\hello\a\b\c")
    if err3 != nil {
       fmt.Println(err3)
       //return
    }
    fmt.Println("file delete success!!")

    // 4. 如果存在多层文件,removeAll,相对来说比较危险,删除这个目录下的所有东西, 强制删除
    err4 := os.RemoveAll("D:\2025\Golang\src\xuego\hello\a")
    if err4 != nil {
       fmt.Println(err4)
       return
    }
    fmt.Println("err4 delete success!!")
}

创建和删除文件

package main

import (
    "fmt"
    "os"
)

func main() {
    // 1. 返回的 file 对象就是我们的文件
    file1, err := os.Create("a.go") // 相对路径
    if err != nil {
       fmt.Println(err)
    }
    fmt.Println(file1)
    // 2. 删除文件
    os.Remove("D:\2025\Golang\src\xuego\hello\a.go")
}

IO 读

package main

import (
    "fmt"
    "os"
)

func main() {
    // 1. 找到这个文件的对象
    file1, err := os.Open("D:\2025\Golang\src\xuego\hello\a.txt")
    if err != nil {
       fmt.Println(err)
    }
    fmt.Println(file1)
    
    // 2. 打开文件
    file2, err2 := os.OpenFile("D:\2025\Golang\src\xuego\hello\a.txt",
       os.O_RDONLY|os.O_WRONLY, os.ModePerm)
    if err2 != nil {
       fmt.Println(err2)
    }
    fmt.Println(file2)
    // 可以操作这个文件对象了
    // ...
}

读取文件内容,为什么多读出了一个汉字?

package main

import (
    "fmt"
    "os"
)

// 读取文件数据
func main() {
    // 我们习惯于在建立连接时候通过defer来关闭连接,保证程序不会出任何问题,或者忘记关闭
    file, _ := os.Open("a.txt")
    // 关闭连接
    defer file.Close()

    // 1. 创建一个容器/切片 (二进制文本文件 => 读取流到一个容器 => 再读取容器里的数据)
    bs := make([]byte, 3, 1024)
    // 2. 读取到缓冲区中
    // 一个汉字占 3 个字节
    n, err := file.Read(bs)
    fmt.Println(string(bs)) // 你

    file.Read(bs)
    fmt.Println(string(bs)) // 好

    // 为什么多读出了一个“好”
    n, err = file.Read(bs)
    fmt.Println(string(bs)) // 好

    fmt.Println(n)   // 0
    fmt.Println(err) // EOF,读取到了文件末尾,就会返回 EOF
}

写入文件内容

package main

import (
    "fmt"
    "os"
)

func main() {

    fileName := "a.txt"
    // 权限:如果我们要向一个文件中追加内容,如果没有,就是从头开始写
    file, _ := os.OpenFile(fileName, os.O_WRONLY|os.O_RDONLY|os.O_APPEND, os.ModePerm)
    defer file.Close()
    // 1. 方式一,通过 file.Write
    /* bs := []byte{65, 66, 67, 68, 69} // A B C D E
    n, err := file.Write(bs)
    if err != nil {
       fmt.Println(err)
    }
    fmt.Println(n) */

    // 2. 方式二,通过 file.WriteString
    n, err := file.WriteString("哈哈哈哈哈哈哈")
    if err != nil {
       fmt.Println(err)
    }
    fmt.Println(n)
}

拷贝文件

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    source := "D:\2025\Golang\src\xuego\hello\a.txt"
    dest := "D:\2025\Golang\src\xuego\hello\b.txt"

    // 打开源文件
    srcFile, err := os.Open(source)
    if err != nil {
       fmt.Printf("打开源文件失败: %v\n", err)
       return
    }
    defer srcFile.Close()

    // 创建目标文件
    destFile, err := os.Create(dest)
    if err != nil {
       fmt.Printf("创建目标文件失败: %v\n", err)
       return
    }
    defer destFile.Close()

    // 使用 io.Copy 进行拷贝
    _, err = io.Copy(destFile, srcFile)
    if err != nil {
       fmt.Printf("文件拷贝失败: %v\n", err)
       return
    }

    fmt.Println("文件拷贝成功!")
}

Seeker

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 读取文件
    file, _ := os.OpenFile("D:\2025\Golang\src\xuego\hello\a.txt",
       os.O_RDWR, os.ModePerm)
    defer file.Close()

    // 偏移量
    // 相对开始位置,io.SeekStart
    // 相对于文件末尾,io.SeekEnd
    file.Seek(0, io.SeekStart)

    buf := []byte{0}
    file.Read(buf)
    fmt.Println(string(buf)) // h

    // 相对于当前位置
    file.Seek(0, io.SeekCurrent)
    file.Read(buf)
    fmt.Println(string(buf)) // e

    file.Seek(0, io.SeekCurrent)
    file.Read(buf)
    fmt.Println(string(buf)) // l

    file.Seek(0, io.SeekCurrent)
    file.Read(buf)
    fmt.Println(string(buf)) // l

    file.Seek(0, io.SeekCurrent)
    file.Read(buf)
    fmt.Println(string(buf)) // o

    // 在结尾追加内容
    // file.Seek(0, io.SeekEnd)
    // file.WriteString("哈哈")
}

bufio

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Open("D:\2025\Golang\src\xuego\hello\a.txt")
    if err != nil {
       log.Println(err)
    }
    defer file.Close()

    // 1. 创建一个 bufio 包下的 reader 对象
    bufioReader := bufio.NewReader(file)
    buf := make([]byte, 1024)
    n, err := bufioReader.Read(buf)
    fmt.Println("读取到了多少个字节:", n)

    // 2. 读取键盘的输入
    // 键盘的输入,实际上是流 os.Stdin
    inputReader := bufio.NewReader(os.Stdin)
    readString, _ := inputReader.ReadString('\n')
    fmt.Println("读取键盘输入的信息:", readString)
}