go标准库
os包
os 包中提供了操作系统函数的接口。os 包的作用主要是在服务器上进行系统的基本操作,如文件操作、目录操作、执行命令、信号与中断、进程、系统状态等。
标准库文档:https://pkg.go.dev/std
常用函数
函数名 | 功能 |
---|---|
os.Hostname() (name, err) | 返回主机名 |
os.Environ()[] string | 返回环境变量,以key=value形式返回string切片 |
os.Setenv(key, value string) error | 设置环境变量 |
os.Getenv(key string) string | 返回指定的环境变量的值 |
os.LookupEnv(key string) (string, bool) | 返回环境变量的值,并返回环境变量是否存在。 |
os.Exit(code int) | 结束程序。defer 的函数不会被执行。 |
os.Getwd() (dir string, err error) | 返回一个对应当前工作目录路径。从根目录开始。 |
os.Mkdir(name string, perm FileMode) error | 指定权限和目录名称创建目录。 os.ModePerm == 0777。目录存在则创建失败。 |
os.MkdirAll(path string, perm FileMode) error | 指定权限和目录名称创建多级目录 |
os.Remove(name string) error | 删除name指定的文件或空的目录 |
os.RemoveAll(path string)error | 删除path路径和包含的所有子目录。不管是否为空。 |
os.IsExit(err error) bool | 判断文件或目录是否存在. |
os.IsNotExit(err error)bool | 判断文件或目录是否不存在 |
os.ReadFile(name string) ([] byte, error) | 读取name文件 |
os.WriteFile(name string, data []byte, perm FileMode) error | 创建并写入文件,会清空之前的内容 |
os.Rename(oldpath, newpath string) error | 重命名,可以是文件,也可以是目录 |
os.Stat(name string) (fs.FileInfo, error) | 查看文件描述信息。返回的err 为nil,说明文件存在,不需要os.IsExist()再次判断。返回非nil,可以使用os.IsExist()和os.IsNotExist()判断。 |
示例:
package main
import (
"fmt"
"os"
)
func main() {
// 主机名
name, _ := os.Hostname()
fmt.Printf("name: %v\n", name)
// 环境变量 以key=value形式返回string切片
s := os.Environ()
fmt.Printf("s: %s\n", s[0])
// 获取指定的环境变量
s2 := os.Getenv("GOPATH")
fmt.Printf("s2: %v\n", s2)
// 获取当前文件目录
dir, _ := os.Getwd()
fmt.Printf("dir: %v\n", dir)
// 设置环境变量
err := os.Setenv("ABC1", "FANZONE")
fmt.Printf("err: %v\n", err)
fmt.Printf("os.Getenv(\"ABC1\"): %v\n", os.Getenv("ABC1"))
// 获取环境变量,并返回是否存在
s, b2 := os.LookupEnv("GOHOME")
fmt.Printf("s: %v\n", s)
fmt.Printf("b2: %v\n", b2)
// 结束程序
// os.Exit(0)
// 创建目录
err2 := os.Mkdir("a", os.ModePerm) // os.ModePerm == 0777
fmt.Printf("err2: %v\n", err2)
// 创建多层目录
err3 := os.MkdirAll("a/b/c", os.ModePerm)
fmt.Printf("err3: %v\n", err3)
// 删除空的文件或目录
err4 := os.Remove("a/b/c")
fmt.Printf("err4: %v\n", err4)
// 删除多级目录
err5 := os.RemoveAll("a/b/c")
fmt.Printf("err5: %v\n", err5)
fmt.Println("end...")
// 判断文件是否存在
_, err6 := os.Stat("a/b/a.txt") // 如果返回nil,则表示文件存在。不为nil则判断错误类型做出处理。
// 判断文件不存在
if _, err := os.Stat("a/b/c.txt"); os.IsNotExist(err) { // 文件未存在的错误,然后处理。
fmt.Println("file does not exist.")
}
// 读取文件
b2, err7 := os.ReadFile("a/b/a.txt")
if err7 == nil {
fmt.Println("file content== ", string(b2))
} else {
fmt.Println("file not exist")
}
// 写入文件
err8 := os.WriteFile("a/b/b.txt", []byte("google.com"), 0755)
if err8 != nil {
fmt.Println("Read file err = ", err8)
return
}
// 重新命名
// 重命名目录
err9 := os.Rename("a/b", "a/bb")
if err9 != nil {
fmt.Println("重命名失败")
return
}
// 重命名文件名
os.Rename("a/b/b.txt", "a/b/aa.txt")
}
FileInfo文件信息
格式:
type FileInfo interface{
Name() string // 文件名
Size() int64 // 文件大小,字节数
Mode() FileMode // 文件权限 -rw-rw-rw-
ModTime() time.Time // 修改时间
IsDir() bool // 是否为文件夹
Sys() intreface{} // 基础数据源接口
}
示例:
package main
import (
"fmt"
"log"
"os"
)
func main() {
fi, err := os.Stat("./test.go")
if err != nil {
log.Fatal(err)
}
s := fi.Name() // 文件名
fmt.Printf("s: %v\n", s)
b := fi.IsDir() // 是否为目录
fmt.Printf("b: %v\n", b1)
i := fi.Size() // 文件大小
fmt.Printf("i: %v\n", i)
fm := fi.Mode() // 文件权限
fmt.Printf("fm: %v\n", fm)
}
File操作
读相关函数
函数名 | 描述 |
---|---|
os.Create(name string) (*File, error) | 创建文件,如果文件不存在则创建,如果文件存在则清空内容。 |
os.Open(name string) (*File, error) | 打开文件,只读。执行写操作会报没有权限的错误。 |
os.OpenFile(name string, flag int, perm os.FileMode) | 打开文件,可指定读写权限。os.O_CREATE | os.O_RDWR 从0位置开始覆盖写 | os.O_APPEND 追加写 | os.O_TRUNC 清空写 |
File.Close() | 关闭文件 |
File.Read([]byte) | 文件读取到切片buf |
File.ReadAt([]byte, off int) | 从off开始读取到buf |
os.ReadDir(name string) | 读取指定name的路径下的文件和目录。 |
File.Seek(off int, whence int) | 定位到指定位置。whence: 0 表示相对于文件的起始处向后读取,1 表示相对于当前的偏移向后读取,而 2 表示相对于其结尾处向后读取。 |
文件打开模式
const (
// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
O_RDONLY int = syscall.O_RDONLY // 只读
O_WRONLY int = syscall.O_WRONLY // 只写, 覆盖写。
O_RDWR int = syscall.O_RDWR // 可读写
// The remaining values may be or'ed in to control behavior.
O_APPEND int = syscall.O_APPEND // 写操作时,数据在尾部追加
O_CREATE int = syscall.O_CREAT // 文件不存在则创建
O_EXCL int = syscall.O_EXCL // 与O_CREATE一起使用, 文件必须不存在.
O_SYNC int = syscall.O_SYNC // 打开文件用于同步 I/O.
O_TRUNC int = syscall.O_TRUNC // 打开文件并清空文件
)
示例:
package main
import (
"fmt"
"os"
)
func createFile() {
f, err := os.Create("a/b/c.txt") // 如果文件存在,则不会报错,会清空内容。
if err != nil {
fmt.Println("create failed")
return
}
fmt.Printf("f.Name(): %v\n", f.Name())
}
func openCloseFile() {
// 打开文件,只能读
f, _ := os.Open("a/b/aa.txt")
fmt.Printf("f.Name(): %v\n", f.Name())
// 打开文件,可以读写或者创建
f3, _ := os.OpenFile("a/b/a.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
fmt.Printf("f3.Name(): %v\n", f3.Name())
// 关闭文件
err := f.Close()
fmt.Printf("err: %v\n", err)
f3.Close()
}
func readFile() {
f, err := os.Open("a.txt")
if err != nil {
fmt.Println("open failed..")
return
}
// 1. 一次性读取
var buf = make([]byte, 10)
f.Read(buf)
fmt.Printf("buf: %v\n", string(buf)) // 读取的内容
// 2. 循环读取
for {
n, err2 := f.Read(buf)
if n == 0 || err2 == io.EOF {
break
}
fmt.Printf("buf: %v\n", string(buf)) // 读取的内容
}
// 3. 从第几个位置开始读
n, _ := f.ReadAt(buf, 5) // 从下标5开始读取内容
fmt.Printf("n: %v\n", n)
fmt.Printf("buf: %v\n", string(buf))
// 4. 读取指定路径下的文件和目录
de, _ := os.ReadDir("./") // 这里使用的是os.ReadDir()
for _, v := range de {
fmt.Printf("v.Name(): %v\n", v.Name())
}
// 5. 指定读取的位置
f.Seek(3, 0) // 0表示开始位置,开始位置偏移3个字节
buf = make([]byte, 10)
f.Read(buf)
fmt.Printf("buf: %v\n", string(buf))
}
func main() {
// 创建文件
// createFile()
// 打开和关闭文件
// openCloseFile()
// 文件读取
readFile()
}
写相关函数
函数名 | 描述 |
---|---|
File.Write([]byte) | 写字节切片 |
File.WriteString(str) | 写字符串 |
File.WriteAt([]byte, off int) | 从off开始写字节切片 |
示例:
package main
import (
"fmt"
"os"
)
func main(){
f, err := os.OpenFile("a.txt", os.O_CREATE|os.O_WRONLY, os.ModePerm) // O_WRONLY 第一次执行写操作时,覆盖写
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
// 1. 写切片
bs := []byte{65, 66, 67, 68, 69, 70}
n, _ := f.Write(bs)
fmt.Printf("n: %v\n", n)
// 2. 写入字符串
str := "hello world"
n2, _ := f.WriteString(str)
fmt.Printf("n2: %v\n", n2)
// 3. 从第几位置开始写切片(从索引下标位置开始写文件)
n3, _ := f.WriteAt(bs, 2)
fmt.Printf("n3: %v\n", n3)
}
文件复制的示例
package main
import (
"fmt"
"io"
"os"
)
// 自己实现文件复制, 可以是文本文件也可以是二进制文件
func copyFile(srcFile, destFile string) (int, error) {
// 源文件操作
f, err := os.Open(srcFile)
if err != nil {
fmt.Println("open failed")
return 0, err
}
defer f.Close()
// 目标文件操作
f2, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
fmt.Println("open write file failed.")
return 0, err
}
defer f2.Close()
var buf = make([]byte, 1024)
n := -1 // 读取的字节数
total := 0
for {
n, err = f.Read(buf)
if n == 0 || err == io.EOF {
break
}
total += n
f2.Write(buf[:n])
}
return total, nil
}
// 使用io包的Copy的函数
func copyFile2(srcFile, destFile string) (int64, error) {
file1, err := os.Open(srcFile) // 源文件
if err != nil {
fmt.Println("open failed")
return 0, err
}
defer file1.Close()
file2, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) // 目标文件
if err != nil {
return 0, nil
}
defer file2.Close()
written, err2 := io.Copy(file2, file1) // 内部实现带缓存了
return written, err2
}
func main() {
// srcFile := "a.txt"
// destFile := "copy_a.txt"
srcFile := "a.png"
destFile := "copy_a.png"
// copyFile(srcFile, destFile) // 方法1
copyFile2(srcFile, destFile) // 方法2
}
断点续传示例
package main
import (
"fmt"
"io"
"os"
"strconv"
)
// 思路: 边复制,边记录复制的总量数
func main() {
srcFile := "a.png"
destFile := "copy_a123.png"
tempFile := "temp.txt" // 记录已经复制字节数
f, err := os.Open(srcFile)
if err != nil {
fmt.Println("open failed")
return
}
defer f.Close()
f2, err2 := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err2 != nil {
fmt.Println("open file failed")
return
}
defer f2.Close()
f3, err3 := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
if err3 != nil {
fmt.Println("open temp file failed")
return
}
defer f3.Close()
// 1. 先去读取临时文件数据
f3.Seek(0, io.SeekStart)
bs := make([]byte, 100)
n, _ := f3.Read(bs)
countStr := string(bs[:n])
count, _ := strconv.ParseInt(countStr, 10, 64)
fmt.Printf("count: %v\n", count)
// 2. 设置读写的位置
f.Seek(count, io.SeekStart)
f2.Seek(count, io.SeekStart)
data := make([]byte, 1024)
n2 := -1 // 读取的数据量
n3 := -1 // 写出的数据量
total := count // 读取的总量
// 3. 复制文件
for {
n2, err = f.Read(data)
if n2 == 0 || err == io.EOF {
fmt.Println("文件复制完毕!")
f3.Close()
os.Remove(tempFile)
break
}
n3, err = f2.Write(data[:n2])
total += int64(n3)
fmt.Printf("total: %v\n", total)
// 将复制的总量,写入到文件中
f3.WriteString(strconv.Itoa(int(total)))
}
}
filepath包
package main
import (
"fmt"
"path"
"path/filepath"
)
func main() {
file1 := "C:/Users/fanzone/Desktop/go学习/golang/exec_go/test.go"
fileName := "a.txt"
b := filepath.IsAbs(file1) // 判断是否为绝对路径
fmt.Printf("b: %v\n", b) // true
b2 := filepath.IsAbs(fileName)
fmt.Printf("b2: %v\n", b2) // false
fmt.Println(filepath.Abs(file1)) // 返回绝对路径
fmt.Println(filepath.Abs(fileName)) // c:\Users\fanzone\Desktop\go学习\golang\exec_go\a.txt
fmt.Printf(" %v\n", path.Join(file1, "..")) // 获取父路径 C:/Users/fanzone/Desktop/go学习/golang/exec_go
}
IO包
在go语言中,将IO
操作封装了下面几个包中:
io
为io
原语提供基本接口io/ioutil
封装了一些使用的i/o函数。fmt
提供格式化i/o
bufio
实现了带缓冲io
基本的i/o 接口
在io包中最重要的两个接口:Reader
和 Writer
。
Reader 接口
type Reader interface{
Read(p []byte)(n int, err error)
}
Writer接口
type Writer interface{
Write(p []byte)(n int, err error)
}
实现了Reader和Writer接口的类型
os.File // 实现了io.Reader 和 os.Writer
strings.Reader // 实现了io.Reader
bufio.Reader / bufio.Writer // 实现了io.Reader 和 os.Writer
bytes.Buffer // 实现了io.Reader 和 os.Writer
bytes.Reader // 实现了io.Reader
示例:
package main
import (
"fmt"
"io"
"log"
"os"
"strings"
)
func testCopy() {
// test1 copy test2
r := strings.NewReader("hello world\n")
_, err := io.Copy(os.Stdout, r) // 内部调用的io.CopyBuffer() 定义了缓存
if err != nil {
log.Fatal(err)
}
}
func testCopyBuffer() {
r1 := strings.NewReader("first reader...\n")
buf := make([]byte, 8)
_, err := io.CopyBuffer(os.Stdout, r1, buf)
if err != nil {
log.Fatal(err)
}
}
func main() {
r := strings.NewReader("hello world") // 实现了Read接口
buf := make([]byte, 10)
r.Read(buf)
fmt.Printf("string(buf): %v\n", string(buf))
// io.Copy(writer, reader)
testCopy()
// io.CopyBuffer
// 借助buffer进行copy,可能一次copy多个字节
testCopyBuffer()
}
ioutil包
函数名 | 描述 |
---|---|
ioutil.ReadAll(r io.Reader) ([]byte, error) | 读取数据,返回读取的字节切片。读取所有内容返回。不适合大文件 |
ioutil.ReadDir(dirname string) ([]os.FileInfo, error) | 读取目录,返回os.FileInfo切片。 |
ioutil.ReadFile(name string) ([] byte, error) | 读取文件,返回文件内容。 一次性读取所有内容。 不适合大文件 |
ioutil.WriteFile(filename string, []byte, perm ) | 写入字节slice, 清空写。 |
ioutil.TempDir(dir string, pattern string) string | 在一个目录中创建指定前缀名pattern的临时目录,返回新临时目录的路径. |
ioutil.TempFile(dir string, pattern string) os.File | 在一个目录创建指定前缀名的临时文件,返回os.File |
示例:
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
func main() {
r := strings.NewReader("go is a general propose language designed\n")
// ioutil.ReadAll(reader) // 读取reader
b, err := ioutil.ReadAll(r) // 把所有内容读取到b
if err != nil {
log.Fatal(err)
}
fmt.Printf("b: %v\n", string(b))
// 读取File
f, _ := os.Open("a/b/a.txt")
defer f.Close()
b, err = ioutil.ReadAll(f) // 读取file, 一次把所有内容读取到b
if err != nil {
log.Fatal(err)
}
fmt.Printf("string(b): %v\n", string(b))
// 读取目录
files, err1 := ioutil.ReadDir("./")
if err1 != nil {
log.Fatal(err1)
}
for _, v := range files {
fmt.Printf("v.Name(): %v\n", v.Name())
}
// 读文件
b2, _ := ioutil.ReadFile("a/b/a.txt")
fmt.Printf("string(b2): %v\n", string(b2))
// 写文件, 清空写
ioutil.WriteFile("a/b/a.txt", []byte("abcdef"), os.ModePerm)
// 创建临时目录
name, _ := ioutil.TempDir("", "example")
fmt.Printf("name: %T,%v\n", name, name) // string /tmp/example273734541
// 创建临时文件
name1, _ := ioutil.TempFile("", "example")
fmt.Printf("name1: %v\n", name1.Name()) // /tmp/example2836320207
}
bufio包
bufio
实现了有缓冲的i/o
,实现了包含Reader
和Writer
等接口。它包装一个io.Reader
或io.Writer
接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O
的帮助函数的对象。
简单的说就是bufio
会把文件内容读取到缓存中(内存),然后再取读取需要的内容的时候,直接在缓存中读取,避免文件的i/o
操作。同样,通过bufio
写入内容,也是先写入到缓存中(内存),然后由缓存写入到文件。避免多次小内容的写入操作I/O
。
bufio读操作
bufio
包提供了两个实例化 bufio.Reader
对象的函数:NewReader(r io.Reader)
和 NewReaderSize(r io.Reader, size int)
。其中,NewReader()
函数是调用 NewReaderSize()
。
实例化:
函数名 | 描述 |
---|---|
bufio.NewReader(r io.Reader) * bufio.Reader | 对io.Reader进行封装,返回带缓冲的Reader |
bufio.NewReaderSize(r io.Reader, size int) *bufio.Reader | 对io.Reader进行封装, 返回指定size大小的带缓冲的Reader |
常量
const (
defaultBufSize = 4096 // 默认缓冲区大小
)
常用函数:
函数名 | 描述 |
---|---|
bufio.Reader.ReadSlice(deim byte) ([]byte, error) | 读取返回字节切片。从输入中读取,直到遇到第一个界定符(delim)为止,并包含delim符号,返回一个指向缓存中字节的 slice。 |
bufio.Reader.ReadString(delim byte) (string, error) | 从实现了Reader接口的结构体,读取字符串,如果有delim,返回内容从开头到包含delim位置。包含delim位置,否则返回所有。 执行一次后,下次执行会在原来基础上读取。不会从开始位置读取。 |
bufio.Reader.Reset(r io.Reader) | 重新设置r为带缓存的Reader。 Reset丢弃缓冲中的数据,清除任何错误,将buf重设为从r读取数据。 |
bufio.Reader.Read(p []byte) | 从buf中读取到p切片。 |
bufio.Reader.ReadByte() (c byte, err error) | 读取并返回一个字节,如果没有可用的数据,会返回错误。 |
bufio.Reader.UnreadByte() error | 吐出最近一次读取的最后一个字节,(只能吐出一个,多次调用会出问题)。 |
bufio.Reader.ReadLine()([]byte, isPrefix bool, error) | 读取一行。 (不建议使用) |
bufio.Reader.WriteTo(w io.Writer) (n int64, err error) | WriteTo方法实现了io.WriterTo接口 |
示例:
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func main() {
// 通过bufio.NewReader(r) 实例化
// 读取字符串
r := strings.NewReader("hello world")
r2 := bufio.NewReader(r) // 通过bufio.NewReader()封装之前的Reader
s, _ := r2.ReadString('o') // hello // 一直读,直到读取到'o',就结束。 包含o
fmt.Printf("s: %v\n", s)
// 读取文件
f, _ := os.Open("a/b/a.txt")
defer f.Close()
r2 = bufio.NewReader(f)
s2, _ := r2.ReadString('c')
fmt.Printf("s2: %v\n", s2) // abc
// 使用bufio.NewReaderSize() 实例化Reader
r = strings.NewReader("hello world\ngoogle.com\nabc.com")
r3 := bufio.NewReaderSize(r, 4096)
// ReadSlice() 读取返回字节切片
line1, _ := r3.ReadSlice('\n')
fmt.Printf("line1: %v\n", string(line1)) // hello world
line2, _ := r3.ReadSlice('\n')
fmt.Printf("line2: %v\n", string(line2)) // google.com
// Reset() 重新设置bufio.Reader对哪个Reader封装
s1 := strings.NewReader("ABCDEF")
s3 := strings.NewReader("123456")
br1 := bufio.NewReader(s1)
s4, _ := br1.ReadString('\n')
fmt.Printf("s4: %v\n", s4)
br1.Reset(s3) // 丢弃之前数据,重新设置s3为带缓冲Reader
s5, _ := br1.ReadString('\n')
fmt.Printf("s5: %v\n", s5)
// Read()
// 从buf中读取到p切片
s1 = strings.NewReader("ABCDEFGHIGKLMN123455667890")
br := bufio.NewReader(s1)
p := make([]byte, 10)
for {
_, err := br.Read(p)
if err == io.EOF {
break
}
fmt.Printf("p: %v\n", string(p))
}
// ReadByte() // 读取一个字节
// UnreadByte()
s1 = strings.NewReader("ABCDEF")
br = bufio.NewReader(s1)
c, _ := br.ReadByte()
fmt.Printf("c: %c\n", c) // A
c, _ = br.ReadByte()
fmt.Printf("c: %c\n", c) // B
br.UnreadByte() // 下次调用ReadByte()应该返回C, 调用UnreadByte,最后返回B
c, _ = br.ReadByte()
fmt.Printf("c: %c\n", c) // B
// 读取一行
// ReadLine()
r = strings.NewReader("hello world helloworld\ngoogle.com\nabc.com")
r3 = bufio.NewReaderSize(r, 16)
line, isPrefix, _ := r3.ReadLine()
fmt.Printf("line: %v\n", string(line)) // hello world hell
fmt.Printf("isPrefix: %v\n", isPrefix) // true
line, isPrefix, _ = r3.ReadLine()
fmt.Printf("line: %v\n", string(line)) // oworld
fmt.Printf("isPrefix: %v\n", isPrefix) // false
// WriteTo()
// 将bufer Reader中字符串写入文件
s111 := strings.NewReader("ABCDEFGHL")
br = bufio.NewReader(s111)
f3, _ := os.OpenFile("a/b/a.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
defer f3.Close()
_, err := br.WriteTo(f3)
fmt.Printf("err: %v\n", err)
}
bufio写操作
实例化:
函数 | 描述 |
---|---|
bufio.NewWriter(w io.Writer) *Writer | 创建一个默认缓冲大小,写入w的*Writer。NewWriter相当于 NewWriter(wr, 4096) |
bufio.NewWriterSize(w io.Writer, size int) *Writer | 创建一个具有最小size缓冲区大小,写入w的Writer。 如果size小于16,则为16大小的缓冲区。 |
常用函数:
函数名 | 描述 |
---|---|
bufio.Writer.Available() | 返回缓冲区中还有多个字节未使用。 |
bufio.Writer.Buffered() | 返回写入当前buffer的字节数 |
bufio.Writer.Write(p []byte) (nn int, err error) | 写入p到缓冲区中。 |
bufio.WriteByte(c byte) error | 写入单个字节。 |
bufio.WriteString(s string)(int, error) | 写入字符串,到[]byte切片或文件。 |
bufio.Writer.Flush() error | Flush()方法将缓冲中的数据写入下次的io.Writer接口 |
bufio.Writer.Reset(w io.Writer) | 重置bufio.Writer缓冲区 |
bufio.Writer.ReadFrom(r io.Reader) (n int64, err error) | 从io.Reader中读取 |
示例:
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, _ := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
defer f.Close()
writer := bufio.NewWriter(f)
fmt.Printf("writer.Available(): %v\n", writer.Available()) // 查看未使用缓存区大小
fmt.Printf("writer.Buffered(): %v\n", writer.Buffered()) // 查看已使用缓冲区大小
p := []byte("hello WORLD")
writer.Write([]byte(p)) //写入p到缓冲区中
writer.Flush() // 刷新缓冲区,写入到文件
// 2. 写入字节
writer.WriteByte('Z')
writer.Flush()
// 3. 写入字符串
writer.WriteString("good jobs")
writer.Flush()
// 4. 重新设置io.Writer
f2, _ := os.OpenFile("a.txt", os.O_CREATE|os.O_APPEND, os.ModePerm)
writer.Reset(f2)
f2.WriteString("fanzone")
writer.Flush()
b := bytes.NewBuffer(make([]byte, 0)) // bytes.NewBuffer也实现了Writer
writer.Reset(b)
writer.WriteString("fanzone 123 hwoo")
writer.Flush()
fmt.Printf("b: %v\n", b)
// 5. 从io.Reader中读取
r := strings.NewReader("LXX")
writer.ReadFrom(r) // 从Reader中读取
writer.Flush() // 刷新缓冲区, 写入到io.Writer
// 6. 如果写入缓冲区内容小于缓冲区大小,不会立即将内容写入到io.Writer,需要手动Flush刷新缓冲区
// 如果写入缓冲区内容大于缓冲区,会立即写入到io.Writer,不需要手动刷新
f3, _ := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)
writer.Reset(f3)
for i := 0; i < 1000; i++ {
writer.WriteString("hello woorld123 fanzone - " + strconv.Itoa(i) + "\n")
}
writer.Flush() // 防止最后内容不够缓冲区大小
}
ReadWriter
可读可写带缓存的ReadWriter。
实例化:
函数名 | 描述 |
---|---|
bufio.NewReadWriter(r *Reader, w * Writer) *ReadWriter | 申请创建一个新的,读写操作的ReadWriter |
实例:
package main
import (
"bufio"
"bytes"
"fmt"
"strings"
)
func main() { // 要求: 从strings.Reader中读, 写入到bytes.Buufer中
reader := strings.NewReader("hello wolrd hello wolrd hello wolrd hello wolrd")
buf := bytes.NewBuffer(make([]byte, 0))
// 1. 创建一个可读可写的ReadWriter
bufReader := bufio.NewReader(reader)
bufWriter := bufio.NewWriter(buf)
readwriter := bufio.NewReadWriter(bufReader, bufWriter)
s, _ := readwriter.ReadString('\n')
fmt.Printf("s: %v\n", s) // 读取的内容 hello world ...
newString := "fanzone"
readwriter.WriteString(newString)
readwriter.Flush()
// 查看写入的内容
fmt.Printf("buf: %v\n", buf) // fanzone
}
Scanner
提供了方便的读取数据的接口。例如:遍历多行文本中的行。Scan 方法会通过 一个“匹配函数”读取数据中符合要求的部分,跳过不符合要求的部分。
本包中提供的匹配函数有“行匹配函数”、“字节匹配函数”、“字符匹配函数” 和“单词匹配函数”
,用户也可以自定义“匹配函数”。默认的“匹配函数”为“行匹配函数”,即ScanLines
,用于获取数据中的一行内容(不包括行尾标记)。
Scanner
使用了缓存,所以匹配部分的长度不能超出缓存的容量。默认缓存容量为 4096 - bufio.MaxScanTokenSize
,同时它也包装了一个 io.Reader 对象,但它没有实现 io.Reader 接口。
Scanner结构体
type Scanner struct {
r io.Reader // The reader provided by the client.
split SplitFunc // The function to split the tokens.
maxTokenSize int // Maximum size of a token; modified by tests.
token []byte // Last token returned by split.
buf []byte // Buffer used as argument to split.
start int // First non-processed byte in buf.
end int // End of data in buf.
err error // Sticky error.
}
SplitFunc
用于定义切分函数类型。
切分函数 | 描述 |
---|---|
bufio.ScanLines | 将内容切分为单行数据,并返回(不包括换行符)。默认方式。 |
bufio.ScanWords | 将内容切分为按空格和回车符\n分隔。(以空格和回车符进行分隔,不包含空格) |
bufio.ScanRunes | 找到data中的单个utf-8字符的编码并返回。 |
bufio.ScanBytes | 找到data中的单个字节并返回。 |
实例化:
函数名 | 描述 |
---|---|
bufio.NewScanner(r io.Reader) * Scanner | NewScanner创建并返回一个从r读取数据的Scanner。 |
注意:
Scan
在遇到下面的情况时会终止扫描并返回 false(扫描一旦终止,将无法再继续):
- 遇到
io.EOF
- 遇到读写错误
- “匹配部分”的长度超过了缓存的长度
常用函数:
函数名 | 描述 |
---|---|
func (s *Scanner) Split(split SplitFunc) | 用于设置Scanner的切分函数,这个函数必须在调用 Scan 前执行。 |
func (s *Scanner) Scan() bool | 在 Scanner 的数据中扫描“指定部分” 找到后,用户可以通过 Bytes() 或 Text() 方法来取出“指定部分” 如果扫描过程中遇到错误,则终止扫描,并返回 false |
func (s *Scanner) Bytes() []byte | 切片返回最后一次扫描出的“指定部分”(引用传递)。 |
func (s *Scanner) Text() string | 字符串返回最后一次扫描出的“指定部分”(值传递)。 |
示例:
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
func main() {
// 1. 基本使用, 默认将数据按照行(\n)进行分割
reader := strings.NewReader("hello\nworld\nfan zone\n哈哈")
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Printf("scanner.Text(): %v\n", scanner.Text())
}
// 2.设置按单词进行分割
reader.Seek(0, io.SeekStart) // 设置string.Reader从其实位置开始读
scanner = bufio.NewScanner(reader)
scanner.Split(bufio.ScanWords) // 设置按单词分割
for scanner.Scan() {
fmt.Printf("ScanWords scanner.Text(): %v\n", scanner.Text())
}
// 3. 设置按byte分割
reader.Seek(0, io.SeekStart)
scanner = bufio.NewScanner(reader)
scanner.Split(bufio.ScanBytes) // 按字节分割
for scanner.Scan() {
fmt.Printf("scanner.Text(): %v\n", scanner.Text())
}
// 4. 按照Rune分割
reader.Seek(0, 0)
scanner = bufio.NewScanner(reader)
scanner.Split(bufio.ScanRunes) // 设置按照rune分割
for scanner.Scan() {
fmt.Printf("scanner.Text(): %v\n", scanner.Text())
}
}
log包
内置了log
包,实现简单的日志功能,通过调用log
包函数,可以实现简单的日志打印功能。
log使用
log
包中有3个系列的日志打印函数,分别print
系列,panic
系列,fatal
系列。
函数系列 | 描述 |
---|---|
单纯打印日志 | |
panic | 打印日志,抛出panic异常。会执行定义在panic之前的defer |
fatal | 打印日志,并强制结束程序(内部调用了os.Exit(1))。在任何位置的defer 函数不会执行。 |
print系列
函数 | 描述 |
---|---|
log.Print(v ...interface{}) | 打印log,默认类型为: 日期 时间 内容 |
log.Printf(format string, v ...interface{}) | 带格式化输出的log |
log.Println(v ...interface{}) | 带空格的输出log |
示例:
package main
import "log"
func main() {
log.Print("my log") // 2022/05/03 11:16:26 my log
log.Printf("my log %d", 1000) // 格式化输出
name := "tom"
age := 20
log.Println(name, " ", age) // 2022/05/03 11:18:49 tom 20
// 可以设置输出格式
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Print("my log") // 2022/05/03 11:22:20 063_log.go:7: my log
}
panic系列
函数 | 描述 |
---|---|
log.Panic(v ...interface{}) | 打印日志,抛出panic异常。会执行定义在panic之前的defer |
log.Panicf(format string, v ...interface{}) | 格式化输出异常。 |
log.Panicln(v ...interface{}) | 打印日志 |
示例:
func testPanic() {
name := "tom"
defer fmt.Println("在panic 之前会执行")
log.Panic("my log") // 会触发panic异常,后续代码不在执行, 但之前的defer会执行
defer fmt.Println("在panic后面,就不再执行")
log.Panicf("my log %d", 100) // 格式化输出
log.Panicln("mylg", name)
}
Fatal系列
函数名 | 描述 |
---|---|
log.Fatal(v ...interface{}) | 打印日志,并强制结束程序(内部调用了os.Exit(1))。在任何位置的defer 函数不会执行。 |
log.Fatalf(format string, v ...interface{}) | 格式化打印log |
log.Fatalln(v ...interface{}) | 打印log |
示例:
func testFatal() {
defer fmt.Println("在Fatal之前的log也不会执行")
// log.Fatal("fatal....")
// log.Fatalf("fatal... %d", 100)
log.Fatalln("hello", 20, "world")
fmt.Println("end..") // 不会执行
}
log的配置
默认情况下,log
只会打印时间,但实际情况下我们可以配置打印的格式。
log
包提供了两个标准log
配置的相关方法:
func Flags() int // 返回标准log输出配置,返回int类型
func SetFlags(flag int) // 设置标准log输出配置
flag参数
const (
Ldate = 1 << iota // 日期
Ltime // 时间
Lmicroseconds // 微秒时间
Llongfile // 带路径文件名和代码行
Lshortfile // 文件名和代码行数
LUTC //utc时间
LstdFlags = Ldate | Ltime // 标准logger的默认值
)
示例:
// log 配置
func init() {
log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
}
func main() {
i2 := log.Flags()
fmt.Printf("i2: %v\n", i2)
log.Print("my log")
}
log前缀配置
log
包提供两个日志前缀配置相关函数:
func Prefix() string // 返回日志的前缀配置
func SetPrefix(prefix string) // 设置日志前缀
示例:
// log 配置
func init() {
// 设置日志前缀
log.SetPrefix("MyLog: ")
fmt.Printf("log.Prefix(): %v\n", log.Prefix()) // MyLog:
}
func main(){
log.Print("hello") // MyLog: 2022/05/03 21:00:23 hello
}
log的输出位置
log
日志还支持输出到文件中。
log.SetOutput(w io.Writer)
示例:
func init(){
// 设置日志输出位置
f, err := os.OpenFile("a.log", os.O_RDWR|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
log.Fatal(err)
}
log.SetOutput(f)
}
func main(){
log.Print("hello")
}
自定义logger
log
包中提供了内置函数,让我们能自定义logger
,也就是将上述的日志格式配置,日志前缀配置,日志输出位置配置整合到一个函数,使得配置不在那么繁琐。
log.New(w io.Writer, prefix string, flags int)
示例:
var logger *log.Logger
func init() {
// 自定义配置logger
f, err := os.OpenFile("a.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
log.Fatal(err)
}
logger = log.New(f, "MyLog: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
logger.Print("hello world")
}
builtin包
这个包提供了一些类型声明,变量和常量声明,还有一些常用的函数,这个包不需要导入,可以直接使用。
常用函数
// 1. append
func append(slice []type, elems ...Type) []type
// 示例:
func testAppend() {
s := []int{}
s = append(s, 1, 2, 3)
fmt.Printf("s: %v\n", s)
s1 := []int{34, 5, 6}
i2 := append(s, s1...)
fmt.Printf("i2: %v\n", i2) // 两个切片合并在一起 [1 2 3 34 5 6]
}
// 2. len()
s := "hello world"
fmt.Printf("len(s): %v\n", len(s))
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("len(s1): %v\n", len(s1)) // 5
// 3. print() 不带换行
// 4. println() 带换行
s1 := "tom"
age := 20
print(s1, age) // tom20
println(s1, age) // tom 20
panic() 抛出异常和recover()函数捕获异常
在golang中引用两个内置的函数panic和recover来触发和终止异常处理流程。
// 示例1: panic之后的语句不会执行
func main() {
defer fmt.Println("写在panic语句前面会执行")
panic("抛出异常...")
defer fmt.Println("写在panic语句后面不会执行")
fmt.Println("后面内容不会在执行了")
}
// 示例2: panic的使用
package main
import "fmt"
func myPrint(s string) {
fmt.Println(s, "----myPrint")
}
func funcA() {
fmt.Println("func a...")
}
func funB() {
fmt.Println("func b...")
defer myPrint("defer funb 1...")
for i := 0; i < 10; i++ {
if i == 5 {
panic("i==5错误了") // 此时触发异常, 但没有处理异常,程序结束。
}
fmt.Printf("i: %v\n", i)
}
defer myPrint("defer funb 2...")
}
func main() {
funcA()
defer myPrint("defer main 3..")
funB()
defer myPrint("defer main 4...")
fmt.Println("end...")
}
/*
func a...
func b...
i = 0
i = 1
i = 2
i = 3
i = 4
defer funb 1 ...
defer main 3..
*/
// 示例3: recover的使用
func funB() {
defer func() {
if msg := recover(); msg != nil {
fmt.Println(msg, "异常处理了...")
}
}()
fmt.Println("func b...")
defer myPrint("defer funb 1...")
for i := 0; i < 10; i++ {
if i == 5 {
panic("i==5错误了")
}
fmt.Printf("i: %v\n", i)
}
defer myPrint("defer funb 2...")
}
func main(){
defer myPrint("defer main 3...")
funB()
defer myPrint("defer main 4...")
fmt.Println("end...")
}
/*
此时会执行:
func b ...
i = 0
i = 1
i = 2
...
i = 4 //panic后,该funB函数后续内容不会执行
defer funb 1...
i==5错误,异常恢复了
end... // 但main函数继续执行
defer main 4...
defer main 3...
*/
注意:
-
recover()
的使用是在函数内部定义匿名函数,然后延迟执行。func funB(){
defer func(){
if msg:= recover(); msg != nil{ // 其中msg为panic()函数传入的。
处理异常。。。
}
}()
}
new函数和make函数区别
区别:
make
只能用于分配及初始化类型为slice
、map
、chan
。 而new
可以为任意类型分配内存空间,例如:int,struct等。new
分配返回的是指针,即*Type
。make
返回的是类型,即Type
。new
分配的空间默认为零值。而make
分配后会进行初始化。之所以不同,是因为这三种类型背后引用了使用前必须初始化的数据结构。int, bool, string
的零值为0 false ""
。slice map chan
的零值为nil
示例:
b := new(bool)
fmt.Printf("b: %T,%v\n", b, *b) // *bool false
i2 := new(int)
fmt.Printf("i2: %T,%v\n", i2, *i2) // *int 0
s := new(string)
fmt.Printf("s: %T, %v\n", s, *s) // *string ""
s1 := new([]int)
fmt.Printf("s1: %T %p\n", s1, *s1) // *[]int 0x0
m1 := new(map[string]int)
fmt.Printf("m1: %T %p\n", m1, *m1) // *map[string]int &map[] 0x0
make示例:
make([]int, 10, 100) // 分配一个有100容量,长度为10的int的切片, 底层数组长度为100。
// 实例
var p *[]int = new([]int)
fmt.Printf("p: %T %p\n", p, *p) // *[]int 0x0
v := make([]int, 100)
fmt.Printf("v: %v\n", v) // [0 0 0 0 0 ... 0]
强制类型转换
package main
import (
"fmt"
"strconv"
)
func main() {
// int 转 float
var a int
a = 100
var b float32
var c float64
b = float32(a)
c = float64(a)
fmt.Printf("b: %T %v\n", b, b) // float32 100
fmt.Printf("c: %T %v\n", c, c) // float64 100
// float 转int
var d int
d = int(b)
d = int(c)
fmt.Printf("d: %T %v\n", d, d) // int 100
// int 转 string
s := strconv.Itoa(a) // 不能直接string(a) 进行转换
fmt.Printf("s: %v\n", s) // s:100
// string 转 int
i, _ := strconv.Atoi(s)
fmt.Printf("i: %v\n", i) // i:100
// float32 转 string
s2 := strconv.FormatFloat(c, 'f', 5, 32) // 5为小数点float32
fmt.Printf("s2: %v\n", s2) // s2: 100.00000
// string 转 float32
f, _ := strconv.ParseFloat(s2, 32)
fmt.Printf("f: %v\n", float32(f)) // f: 100
// []byte 转 string
b2 := []byte{'a', 'b', 'c', 'd'}
s3 := string(b2)
fmt.Printf("s3: %v\n", s3) // s3: abcd
// string 转 []byte
b3 := []byte(s3)
fmt.Printf("b3: %v\n", b3) // [97 98 99 100]
}
bytes包
bytes
包提供了对字节切片进行读写操作的一系列函数。
常用函数
函数名 | 描述 |
---|---|
bytes.Contains(b []byte, subslice []byte) bool | 判断切片是否包含子切片。返回bool |
bytes.Count(b []byte, subslice []byte) int | 统计出现的次数。 |
bytes.Repeat(b []byte, count int) []byte | 重复b切片count次。返回切片 |
bytes.Replace(s []byte, old []byte, new []byte, n int) [] byte | 在s切片中使用new替换old切片。n为替换次数。返回替换后的切片。 |
bytes.ReplaceAll(s []byte, old []byte, new []byte) [] byte | 将s切片中使用new全部替换old切片 |
bytes.Runes(s []byte) []rune | 返回rune切片。 |
bytes.Join(s [][]byte , sep []byte) []byte |
使用sep切片,拼接s二维切片。 |
bytes.Equal(s []byte, s2 []byte) bool | 判断两个切片是否相等,返回bool。 |
示例:
package main
import (
"bytes"
"fmt"
)
func main() {
s := "google.com"
b := []byte(s)
b1 := []byte("baidu.com")
b2 := []byte(".com")
// Contains() 包含
b3 := bytes.Contains(b, b1) // 判断切片是否包含子切片
fmt.Printf("b3: %v\n", b3) // false
b3 = bytes.Contains(b, b2)
fmt.Printf("b3: %v\n", b3) // true
// Count统计出现的次数
b = []byte("hello world")
sep1 := []byte("h")
sep2 := []byte("l")
sep3 := []byte("0")
i2 := bytes.Count(b, sep1)
fmt.Printf("i2: %v\n", i2) // 1
i3 := bytes.Count(b, sep2)
fmt.Printf("i3: %v\n", i3) // 3
i4 := bytes.Count(b, sep3)
fmt.Printf("i4: %v\n", i4) // 0
// Repeat() 重复slice
b = []byte("hi")
b4 := bytes.Repeat(b, 3)
fmt.Printf("b4: %v\n", string(b4)) // hihihi
// Replace() 替换
b = []byte("hello world")
b5 := bytes.Replace(b, []byte("l"), []byte("L"), 2)
fmt.Printf("b5: %v\n", string(b5)) // heLLo world
// ReplaceAll 全部替换
b6 := bytes.ReplaceAll(b, []byte("l"), []byte("L"))
fmt.Printf("b6: %v\n", string(b6)) // heLLo worLd
// Runes
s11 := []byte("你好世界!")
r := bytes.Runes(s11)
fmt.Printf("len(s11): %v\n", len(s11)) // 字节数,15
fmt.Printf("len(r): %v\n", len(r)) // utf-8编码,字符数 5
// Join
s2 := [][]byte{[]byte("你好"), []byte("世界")} // 二维切片
sep4 := []byte(",")
b7 := bytes.Join(s2, sep4)
fmt.Printf("b7: %v\n", string(b7)) // 你好,世界
sep5 := []byte("abc")
b8 := bytes.Join(s2, sep5)
fmt.Printf("b8: %v\n", string(b8)) // 你好abc世界
// 判断切片是否相等
b1 = []byte("hello")
b2 = []byte("world")
b9 := bytes.Equal(b1, b2)
fmt.Printf("b9: %v\n", b9) // false
b10 := bytes.Equal([]byte("hello"), []byte("hello"))
fmt.Printf("b10: %v\n", b10) // true
}
bytes.Reader类型
Reader实现了io.Reader
、io.ReadAt
、io.WriterTo
、io.Seeker
、io.ByteScanner
、io.RuneScanner
接口,可以seek。
需要在通过 bytes.NewReader
方法来初始化bytes.Reader
类型的对象。初始化时传入 []byte
类型的数据。NewReader
函数如下:
func NewReader(b []byte) *Reader
常用函数:
函数名 | 描述 |
---|---|
func (r *Reader) Len() int | 返回Reader中未读的字节数。 |
func (r *Reader) Size() int | 返回Reader中底层数据总长度。 |
func (r *Reader) Read(b []byte) (n int, err error) | 读取数据到缓冲区b中。 |
func (r *Reader) Seek(offset int64, whence int) (int64, error) | 设置读取的偏移位置 |
func (r *Reader) ReadByte() (byte, error) | 每次读一个字节。 |
示例:
// Reader类型
b = []byte("123456")
r2 := bytes.NewReader(b)
buf := make([]byte, 2) // buf大小为2
fmt.Printf("r2.Len(): %v\n", r2.Len()) // 未读取字节数
fmt.Printf("r2.Size(): %v\n", r2.Size()) // 数据总长度
for {
_, err := r2.Read(buf)
if err != nil {
break
}
fmt.Printf("buf: %v\n", string(buf)) // 12 每次读取两个字节
}
r2.Seek(0, 0) //
for {
// 一个字节一个字节的读
b11, err := r2.ReadByte()
if err != nil {
break
}
fmt.Printf("b11: %v\n", string(b11))
}
bytes.Buffer类型
具有读取和写入方法的可变大小的字节缓冲区。
buffer
的零值是准备使用的空缓冲区。
实现了io.Reader
和io.Writer
接口。
声明一个Buffer
的四种方法:
var b bytes.Buffer // 直接声明Buffer,不用初始化,可以直接使用
b := new(bytes.Buffer) // 使用new 返回Buffer变量指针
b := bytes.NewBuffer(s []byte) // 从一个[]byte切片,构造buffer
b := bytes.NewBufferString(s string) // 从string构造buffer
写操作
函数 | 描述 |
---|---|
b.Write(d []byte) | 将切片的d写入到buffer b尾部 |
b.WriteString(s string) | 将字符串s写入到Buffer 尾部 |
b.WriteByte(c byte) | 经字节c写入到buffer尾部 |
b.WriteRune(r rune) | 将一个rune类型的数据写入到缓冲区尾部 |
b.WriteTo(w io.Writer) | 将Buffer中内容输出到一个实现了io.Writer 接口的类型中 |
b.Reset() | 将缓冲区置空 |
示例:
// 写操作
bb1.Write([]byte("hello"))
fmt.Printf("bb1.Bytes(): %v\n", string(bb1.Bytes())) // hello
bb1.WriteString("world")
fmt.Printf("bb1.String(): %v\n", bb1.String()) // hello world
读操作
函数名 | 描述 |
---|---|
b.Read(c) | 一次读取8个byte到c容器中,每次读取新的8个byte覆盖原来的内容。 |
b.ReadByte() (byte error) | 读取一个byte。 |
b.ReadRune() (rune, error) | 读取一个rune |
b.ReadBytes(delimiter byte) (line []byte, error) | 在buffer中查找第一个delimiter位置,返回开始位置到delimiter位置(包含delimiter )的字节切片。 |
b.ReadString(delimiter byte) (line string , error) | 从buffer中读取,查找到第一个delimiter位置,返回开始位置到delimiter位置(包含)的字符串。 |
示例:
// 读操作
buf1 := make([]byte, 2)
bb1.Read(buf1)
fmt.Printf("buf1: %v\n", string(buf1)) // he
b11, _ := bb1.ReadByte() // 读一字节
fmt.Printf("string(b11): %v\n", string(b11)) // l
line, _ := bb1.ReadString('o') // 读取到'o‘
fmt.Printf("line: %v\n", line) // lo
bb1.Reset() // 缓冲区置空
bb1.WriteString("hello world")
line2, _ := bb1.ReadBytes(' ')
fmt.Printf("line2: %v\n", string(line2)) // hello
errors包
errors
包实现了操作错误的函数。
使用error
类型来返回函数执行过程中遇到的错误,如果返回的error
值为nil
表示未遇到错误,否则error
会返回一个字符串,用来说明遇到了什么错误。
error 接口
type error interface{
Error() string
}
注意:
-
可以使用任何类型来实现这个接口,只需要添加
Error()
方法即可,也就是说error
可以是任何类型,返回的error
可以包含任何信息,不一定是字符串。 -
error
不一定就表示错误,可以表示任何信息 -
errors
包实现了一个最简单的error
类型,只包含字符串。
创建error
errors.New(text string) error // 方法1:创建error
fmt.Errorf(format, a ...interface{}) error // 方法2:创建error
示例:
package main
import (
"errors"
"fmt"
)
func main() {
// 创建一个error数据
err1 := errors.New("自己创建的新error")
fmt.Println(err1)
fmt.Printf("%T\n", err1)
// 另一种方法创建error
err2 := fmt.Errorf("错误的信息码:%d\n", 100)
fmt.Println(err2)
fmt.Printf("%T\n", err2)
}
// 设计一个函数:验证年龄是否合法,如果为负数,返回一个error
func checkAge(age int) error {
if age < 0 {
return errors.New("年龄不合法")
}
fmt.Println("年龄是", age)
return nil
}
自定义错误
对error添加字段
package main
import (
"fmt"
"math"
)
func main() {
radius := -3.0
area, err := circleArea(radius)
if err != nil {
fmt.Println(err)
if e, ok := err.(*areaError); ok {
fmt.Printf("半径为%.2f\n", e.radius)
return
}
}
fmt.Println("圆形面积为:", area)
}
type areaError struct {
msg string // 添加字段
radius float64
}
func (err *areaError) Error() string {
return fmt.Sprintf("error: 半径:%.2f, 内容:%s", err.radius, err.msg)
}
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, &areaError{"半径是非法的", radius}
}
return math.Pi * radius * radius, nil
}
error添加方法
package main
import "fmt"
type areaError struct {
msg string
length float64
width float64
}
func (e *areaError) Error() string {
}
func (e *areaError) lengthNavigate() bool {
return e.length < 0
}
func (e *areaError) widthNavigate() bool {
return e.width < 0
}
func rectArea(length, width float64) (float64, error) {
msg := ""
if length < 0 {
msg = "长度小于0"
}
if width < 0 {
if msg == "" {
msg = "宽度小于0"
} else {
msg += ",宽度也小于0"
}
}
if msg != "" {
return 0, &areaError{msg, length, width}
}
return length * width, nil
}
func main() {
length, width := 6.7, 9.1
f, err := rectArea(length, width)
if err != nil {
fmt.Printf("err: %v\n", err)
if err, ok := err.(*areaError); ok { // 类型断言
if err.lengthNavigate() {
fmt.Printf("error: 长度:%.2f小于0", err.length)
}
if err.widthNavigate() {
fmt.Printf("error: 宽度:%.2f小于0", err.width)
}
}
return
}
fmt.Printf("矩形的面积是: %v\n", f)
}
sort包
sort
包提供了排序切片和用户自定义数据类型以及相关功能的函数。
sort
包主要是针对[]int
、[]float64
、[]string
以及其他自定义切片
的排序。
结构体
type IntSlice []int
type Float64Slice []float64
type StringSlice []string
常用函数
函数 | 描述 |
---|---|
func Ints(a []int) | 对整形切片进行升序排序。 |
func IntsAreSorted(a []int) bool | 判断是否有序。 |
func SearchInts(a []int, x int)int | 查找x是否在整形切片a中。返回索引下标。如果找不到返回len(a) |
func Float64s(a []float64) | 对float64进行排序。 |
func Float64sAreSorted(a []float64) bool | 判断float64切片是否有序。 |
func SearchFloat64s(a []float64, x float64) | 在float64切片中查找x。返回索引下标。 |
func Strings(a []string) | 对string类型切片进行排序。 |
func StringsAreSorted(a []string) bool | 判断string类型切片是否有序。 |
func SearchStrings(a []string, x string) int | 在string类型切片中查找x是否存在。返回索引。 |
func Sort(data Interface) | 对自定义data类型排序 |
func Stable(data Interface) | 对自定义data类型进行排序,并保持稳定(元素位置不变)。 |
func Reverse(data Interface) Interface | 对自定义data类型进行逆序,返回逆序data的Interface,需要再次调用sort.Sort(data)进行排序。 |
func ISSorted(data Interface) bool | 判断data是否有序。 |
Interface接口
只要实现了sort.Interface
接口,就可以调用上述data Intraface
方法。
type Interface interface{
Len() int // Len()返回集合中元素个数
Less(i,j int) bool // i > j 该方法返回索引i元素是否比索引j元素小。
Swap(i, j int) // 交换i,j的值。
}
示例:
// 对Interface接口实现
type NewInts []uint
func (n NewInts) Len() int {
return len(n)
}
func (n NewInts) Less(i, j int) bool {
fmt.Println(i, j, n[i] < n[j], n)
return n[i] < n[j]
}
func (n NewInts) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func main() {
n := NewInts{1, 3, 4, 1, 2, 5}
sort.Sort(n) // 调用自定义的切片类型排序
fmt.Printf("n: %v\n", n)
}
// 自定义类型排序2
type Person struct {
Name string
Age uint
}
type Class []Person // 班级
func (p Class) Len() int {
return len(p)
}
func (p Class) Less(i, j int) bool {
return p[i].Age < p[j].Age
}
func (p Class) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func main() {
// 自定义排序类型2
class1 := Class{Person{
"fhz1", 20,
}, Person{
"fhz2", 12,
}, Person{
"fhz3", 123,
}}
sort.Sort(class1)
fmt.Printf("class1: %v\n", class1)
// 调用sort.Reverse()逆序
s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Sort(sort.Reverse(sort.IntSlice(s)))
fmt.Println(s)
// 判断是否有序
b2 := sort.IsSorted(class1)
fmt.Printf("b2: %v\n", b2)
}
// 自定义类型3
type MyMap []map[string]int
func (m MyMap) Len() int {
return len(m)
}
func (m MyMap) Less(i, j int) bool {
return m[i]["a"] < m[j]["a"]
}
func (m MyMap) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func main() {
// 自定义类型3
map1 := MyMap{
{"a": 4, "b": 12},
{"a": 1, "b": 11},
{"a": 3, "b": 14},
}
sort.Sort(map1)
fmt.Printf("map1: %v\n", map1)
}
time包
time
测量和显示时间。
基本使用
打印显示现在的时间。
package main
import (
"fmt"
"time"
)
func main() {
// 1. 获取当前的时间
t := time.Now()
fmt.Printf("%T, %v\n", t, t) // time.Time 2022-05-04 12:02:51.51869218 +0800 CST m=+0.000027222
// 2. 获取时间指定内容
s4 := t2.String() // 输出String
fmt.Printf("%v %T\n", s4, s4) // 2020-01-01 10:00:00 +0800 CST string
year, month, day := t2.Date() // 获取年月日。time类型的方法
hour, min, second := t3.Clock() // 获取时分秒
i := t2.Year() // 年
i2 := t2.YearDay() // 今年过了多少天
m := t2.Month() // 月
i3 := t2.Day() // 日
i4 := t2.Hour() // 时
i5 := t2.Minute() // 分
i6 := t2.Second() // 秒
i7 := t2.Nanosecond() // 纳秒
w := t2.Weekday() // t2是星期几
// 3. 获取指定的时间
t2 := time.Date(2020, 01, 01, 10, 0, 0, 0, time.Local) // 不同于time类型的方法,这个是time的函数。 年月日 时分秒 纳秒 时区
fmt.Printf("t2: %v\n", t2) // t2: 2020-01-01 00:00:00 +0800 CST
// 4. 时间戳
t := time.Date(1970, 1, 1, 1, 0, 0, 0, time.UTC) // 1970年1月1日0时0分0秒的差值
fmt.Printf("t: %v\n", t)
i := t.Unix() // 返回秒的差值
fmt.Printf("i: %v\n", i) // 3600
i2 := t.UnixNano() // 返回纳秒的差值
fmt.Printf("i2: %v\n", i2) // 3600 000 000 000
// 5. 时间戳 -> time.Time 的格式转换
timeStamp := time.Now().Unix()
t2 := time.Unix(timeStamp, 0) // time.Unix(sec int64, nsec int64)
fmt.Printf("t2: %T, %v\n", t2, t2) // time.Time 2022-05-04 23:10:32 +0800 CST
}
操作时间
// func (t Time) Add(d time.Duration) time.Time
// func (t Time) AddDate(year, month, day int) time.Time
// 时间相加
func testAdd() {
now := time.Now() // 2022-06-08 11:36:00 +0800 CST
fmt.Println(now.Add(time.Hour * 10)) // 2022-06-08 21:36:00 +0800 CST
fmt.Println(now.Add(time.Hour*10 + time.Minute*10)) // 2022-06-08 21:46:00 +0800 CST
fmt.Println(now.Add(time.Second * 10)) // 2022-06-08 11:36:10 +0800 CST
t2 := now.AddDate(1, 0,0 ) // 原来基础上添加1年0月0日
}
// func (t Time) Sub(u time.Time) t time.Duration
// 计算两个时间差
func testSub() {
now := time.Now()
time.Sleep(time.Second * 3)
afterNow := time.Now()
fmt.Println(afterNow.Sub(now)) // 3.0124747s 目标时间与此时相比相差3s
}
// func(t Time) Equal(u Time) bool
// 判断两个时间是否相等,会考虑时区的影响。
func testEqual() {
now := time.Now()
b2 := now.Equal(time.Now()) // false
fmt.Printf("equal: %v\n", b2)
}
// func(t Time) Before(u Time) bool
// 判断时间是否在另一个时间之前,如果t代表的时间点在u之前,返回真,否则返回假。
func testBefore() {
now := time.Now()
t := now.Add(time.Second * (10))
fmt.Printf("t: %v\n", t)
b2 := now.Before(t)
fmt.Printf("before: %v\n", b2) // true
}
// func(t Time) After(u Time) bool
// 如果t代表时间点在u之后,返回真,否则返回假。
func testAfter() {
now := time.Now()
b2 := now.After(now.Add(time.Second * (-10)))
fmt.Printf("after: %v\n", b2) // true
}
时间格式化
time -> string
。
时间类型有一个自带的方法Format
进行格式化,需要注意的Go格式化时间模板不是常见的Y-m-d H:M:S
。
而是使用go的诞生时间2006年1月2号15点04分05秒(记忆口诀为2006 1 2 3 4)
格式:
func(t Time)Format(layout string) string
// layout string 模板
// 2006-01-02 // 会把time转为 YYYY-mm-dd的格式
// 2006-01-01 15:04:05 // 会把time转为 YYYY-mm-dd HH:MM:SS的格式
// 2006/01/02 15:04:05.000 Mon Jan // 完整格式
// 2006/01/02 15:04:05 PM // 12小时制
示例:
// 字符串格式时间
t1 := time.Now()
s := t1.Format("2006/01/02 15:04:05.000 Mon Jan") // 按模板格式化
fmt.Printf("s: %v\n", s) // 2022/05/05 10:31:46.280 Thu May
s = t1.Format("2006-01-02 03:04:05.000 PM Mon Jan") // 12小时制
fmt.Printf("s: %v\n", s) // 2022-05-05 10:31:46.280 AM Thu May
s = t1.Format("2006/01/02 15:04") // 年/月/日 时:分
fmt.Printf("s: %v\n", s) // 2022/05/05 10:32
s = t1.Format("2006/01/02") // 年/月/日
fmt.Printf("s: %v\n", s) // 2022/05/05
注意: 如果想要格式化为12小时方式,需要指定PM
。
解析字符串格式的时间
string -> Time
格式:
func parse(layout string, value string)(Time, error)
示例:
// 解析字符串格式的时间
l, err := time.LoadLocation("Asia/Shanghai") // 加载上海时区
s3 := "2022/05/01 11:11:11"
t3, _ := time.Parse("2006/01/02 15:04:05", s3) // 模板需要与字符串格式一样。
// 按照指定时区和指定格式解析字符串时间
t3, _ = time.ParseInLocation("2006/01/02 15:04:05", s3, l) // 按上海时区解析
t3, _ = time.ParseInLocation("2006/01/02 15:04:05", s3, time.Local) // 格式化为本地时区
encoding/json包
这个包可以实现json的编码和解码,就是将json字符串转为struct
,或者将struct
转换为json
。
主要函数:
func Marshal(v interface{}) ([]byte, error) // 将struct编码为json,可以接收任何类型。 interface{} 代表任意类型。
func Unmarshal(data []type, b interface{}) error // 将json转为struct
示例:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string
Age int
Email string
Parents []string
}
func test1Struct2Json() {
p := Person{
Name: "tom",
Age: 20,
Email: "tom@gmail.com",
}
b, _ := json.Marshal(p) // 将结构体转json
fmt.Printf("string(b): %v\n", string(b)) // {"Name":"tom","Age":20,"Email":"tom@gmail.com"}
}
func testJson2Struct() {
b1 := []byte(`{"Name":"tom","Age":20,"Email":"tom@gmail.com"}`)
var m Person
json.Unmarshal(b1, &m) // m为取指针类型。 切片也要取地址
fmt.Printf("m: %v\n", m)
}
func testJson2Struct2() {
b := []byte(`{"Name":"tom","Age":20,"Email":"tom@gmail.com", "Parents": ["big tom", "kite"]}`)
var f interface{}
json.Unmarshal(b, &f)
fmt.Printf("f: %T\n", f) // map[string]interface{}
fmt.Printf("f: %v\n", f) // map[Age:20 Email:tom@gmail.com Name:tom Parents:[big tom kite]]
}
func testStruct2Json2() {
p := Person{
Name: "tom",
Age: 20,
Email: "tom@qq.com",
Parents: []string{"big tom", "kite"},
}
b, _ := json.Marshal(p)
fmt.Printf("b: %v\n", string(b)) // {"Name":"tom","Age":20,"Email":"tom@qq.com","Parents":["big tom","kite"]}
pers := make([]Person, 0) // 定义Person切片,长度为0,
for i := 0; i < 10; i++ {
pers = append(pers, Person{
Name: fmt.Sprintf("%s-%d", "tom", i),
Age: 20 + i,
Email: "tom@qq.com",
Parents: []string{"big tom", "kite"},
})
}
b2, _ := json.Marshal(pers)
fmt.Printf("string(b2): %v\n", string(b2))
}
func main() {
test1Struct2Json() // 结构体转json
testJson2Struct() // json转结构体
testJson2Struct2() // 嵌套json转结构体
testStruct2Json2() // 嵌套struct转json
}
两个结构体
struct Decoder struct{ // 从输入流读取并解析json
}
struct Encoder struct{ // 写json到输出流
}
// 常用方法
json.NewDecoder(r io.Reader)
json.NewEncoder(w io.Writer)
示例:
func testIo() {
// 从文件中读
f, _ := os.Open("a.json")
defer f.Close()
d := json.NewDecoder(f)
var v map[string]interface{}
d.Decode(&v) // json to struct
fmt.Printf("v: %v\n", v)
for key, value := range v { // 打印结果
fmt.Printf("key: %v\n", key)
fmt.Printf("value: %v\n", value)
}
// 写入到文件
f3, err := os.OpenFile("b.json", os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
log.Fatal(err)
}
defer f3.Close()
p := Person{
Name: "tom",
Age: 20,
Email: "tom@qq.com",
Parents: []string{"big tom", "kite"},
}
e := json.NewEncoder(f3)
e.Encode(p)
}
func main() {
testIo() // 读取IO的json
}
encoding/xml包
实现对xml
解析
常用函数
func Marshal(v interface{}) ([]byte, error) // 将struct转为xml, 接收任何类型
func MarshalIndent(v interface{}, prefix string, indent string) // 带缩进的struct 转xml。 prefix前缀, indent 缩进符号
func Unmarshal(data []type, v interface{}) error // 将xml转为strcut结构体
示例:
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Person struct {
Name string
Age int
Email string
Perents []string
}
func testStruct2Xml() {
p := Person{
"tom", 20, "qq.com", []string{"abc", "def"},
}
b2, _ := xml.Marshal(p) // struct to xml string
b2, _ = xml.MarshalIndent(p, "", " ") // 带缩进
fmt.Printf("string(b2): %v\n", string(b2)) // Person><Name>tom</Name><Age>20</Age><Email>qq.com</Email><Perents>abc</Perents><Perents>def</Perents></Person>
}
func testXml2Struct() {
str := "<Person><Name>tom</Name><Age>20</Age><Email>qq.com</Email><Perents>abc</Perents><Perents>def</Perents></Person>"
var p Person
xml.Unmarshal([]byte(str), &p)
fmt.Printf("f: %v\n", p) // {tom 20 qq.com [abc def]}
}
func main() {
testStruct2Xml() // 结构体转xml
testXml2Struct() // xml转结构体
}
读写IO
func testIO() {
// 写入到文件
f3, _ := os.OpenFile("a.xml", os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
encoder := xml.NewEncoder(f3)
p := Person{
"tom", 20, "qq.com", []string{"abc", "def"},
}
encoder.Encode(p)
f3.Close()
// 文件读
f, _ := os.Open("a.xml")
defer f.Close()
var per Person
d := xml.NewDecoder(f)
d.Decode(&per)
fmt.Printf("per: %v\n", per)
}
func main(){
testIO()
}
math包
包含一些常用的数学计算函数。例如三角函数,随机数,绝对值,平方根等。
strconv包
package main
import (
"fmt"
"strconv"
)
func main() {
// 1. string 转为 int
s1 := "1000"
i, _ := strconv.ParseInt(s1, 10, 64) // 10进制 int64
fmt.Printf("%v %T\n", i, i) // 1000 int64
// 2. string 转 int (推荐方法)
i2, _ := strconv.Atoi(s1)
fmt.Printf("%v %T\n", i2, i2) // 1000 int
// 3. int 转string
s := strconv.Itoa(i2)
fmt.Printf("%v %T\n", s, s)
// 4. string 转bool
s2 := "true"
b, _ := strconv.ParseBool(s2)
fmt.Printf("%v %T\n", b, b)
// 5. string 转 float
s3 := "1.234"
f, _ := strconv.ParseFloat(s3, 32)
fmt.Printf("%v %T\n", f, f)
// 6. true转string
s4 := fmt.Sprintf("%t", true)
fmt.Printf("s4: %#v\n", s4) // "true"
s5 := strconv.FormatBool(true)
fmt.Printf("s5: %#v\n", s5) // "true"
}
reflect包
反射是指在程序运行期间对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
go程序在运行期间使用reflect
包访问程序的反射信息,反射主要与interface类型
相关,只有interface类型
才有反射一说。
在go的反射机制中,任何接口都可以理解为由reflect.Type
和reflect.Value
两部分组成,并且通过reflect.TypeOf()
和reflect.ValueOf()
两个函数来获取任意对象的Value和Type。
使用场景:
- 函数传递参数时,不知道传递的参数类型
- 有时候需要根据某些条件在程序运行期间动态的执行函数
TypeOf()函数
在go语言中,使用reflect.TypeOf()
函数可以获取任意值的类型信息(reflect.Type),程序通过类型对象可以访问任意值的类型信息。
package main
import (
"fmt"
"reflect"
)
type MyInt int
type Cat struct {
}
func reflectType(x interface{}) {
v := reflect.TypeOf(x) // 查看x的类型
fmt.Printf("TypeOf(x): %v\n", v)
fmt.Printf("v.Name(): %v v.Kind(): %v\n", v.Name(), v.Kind()) // 类型名 和 底层类型
}
func main() {
var a float64 = 3.14
reflectType(a)
var b int64 = 100
reflectType(b)
var c Cat
reflectType(c)
var mi MyInt
reflectType(mi)
}
/*
TypeOf(x): float64
v.Name(): float64 v.Kind(): float64
TypeOf(x): int64
v.Name(): int64 v.Kind(): int64
TypeOf(x): main.Cat
v.Name(): Cat v.Kind(): struct
TypeOf(x): main.MyInt
v.Name(): MyInt v.Kind(): int
*/
ValueOf()函数
reflect.ValueOf()
函数返回的是reflect.Value
类型,包含了原始值信息。
package main
import (
"fmt"
"reflect"
)
// 1. 通过反射获取值
func reflectValue(a interface{}) {
v := reflect.ValueOf(a)
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("type: int64, value: %v\n", int64(v.Int())) // v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
case reflect.Float32:
fmt.Printf("type: float32, value:%v\n", float32(v.Float())) //v.Float()获取浮点型原始值
case reflect.Float64:
fmt.Printf("type: float64, value:%v\n", float64(v.Float()))
}
}
// 2. 通过反射设置变量的值
func reflectSetValue(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(200) // 值拷贝,不能修改原值, 会引发异常panic
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Int64 { // 反射中使用 Elem()方法获取指针对应的值
v.Elem().SetInt(200)
}
}
func main() {
var a float64 = 3.14
reflectValue(a)
var b int64 = 1000
reflectValue(b)
x := reflect.ValueOf(10) // 将int类型的原始值转换为reflect.Value类型
fmt.Printf("%v\n", x) // 10
fmt.Printf("%T\n", x) // reflect.Value
// 设置值
var c int64 = 100
// reflectSetValue(c) // error
reflectSetValue2(&c) // ok
fmt.Printf("c: %v\n", c) // 200
}
注意:
- 想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。
- 反射中使用专有的
Elem()
方法来获取指针对应的值(即*val)。
结构体反射
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string `json:"name" fanzone:"哈哈"`
Age int `json:"age" fanzone:"嘿嘿"`
}
func main() {
stu1 := Student{"小王子", 11}
t := reflect.TypeOf(stu1)
fmt.Printf("%v %v\n", t.Name(), t.Kind()) // main.Student struct 查看t的类型和底层类型
// 1. 通过for循环遍历结构体所有字段
for i := 0; i < t.NumField(); i++ { // t.NumField() 返回字段个数
filed := t.Field(i)
fmt.Printf("%v %v %v %v\n", filed.Name, filed.Index, filed.Type, filed.Tag)
s := filed.Tag.Get("fanzone") // 通过Get() 获取指定的tag内容
fmt.Printf("s: %v\n", s)
}
// 2. 通过字段名获取指定结构体字段信息
if ageField, ok := t.FieldByName("Age"); ok { // t.FieldByName(name string) 指定结构体字段名
fmt.Printf("%v %v %v %v\n", ageField.Name, ageField.Index, ageField.Type, ageField.Tag)
s := ageField.Tag.Get("json")
fmt.Printf("s: %v\n", s)
}
}
注意:
-
tag格式为:
json:"内容"
// 要加引号,否则找不到 -
其中json和内容之间不要有空格。
练习:配置文件解析
package main
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
)
type MysqlConfig struct {
Address string `ini:"address"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
}
type RedisConfig struct {
Host string `ini:"host"`
Port int `ini:"port"`
Password string `ini:"password"`
Database int `ini:"database"`
Test bool `ini:"test"`
}
type Config struct {
MysqlConfig `ini:"mysql"`
RedisConfig `ini:"redis"`
}
func loadIni(fileName string, data interface{}) (err error) {
// 0. 参数校验。
// 0.1 传进来的data参数必须为指针类型,(赋值)
t := reflect.TypeOf(data)
// fmt.Println(t, t.Kind()) // *main.MysqlConfig ptr 指针类型t.Name()为空
if t.Kind() != reflect.Ptr {
err = fmt.Errorf("data should be a pointer")
return
}
// 0.2 查看传进来的data参数必须为结构体类型指针。(因为配置文件中有个结构体,需要赋值)
if t.Elem().Kind() != reflect.Struct {
err = fmt.Errorf("data should be a struct")
return
}
// 1. 读文件
b, err2 := ioutil.ReadFile(fileName)
if err2 != nil {
err = errors.New("open file failed")
return
}
lineSlice := strings.Split(string(b), "\r\n")
fmt.Printf("lineSlice: %#v\n", lineSlice)
// 2. 一行一行的读数据
var structName string
for idx, line := range lineSlice {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
continue
}
if len(line) == 0 {
continue
}
// 2.2 如果是[],表示是节,
if strings.HasPrefix(line, "[") {
// 处理[
if line[0] != '[' || line[len(line)-1] != ']' {
err = fmt.Errorf("syntx error :%d\n", idx+1)
return
}
// 处理[ ]
sectionName := line[1 : len(line)-1]
if len(strings.TrimSpace(sectionName)) == 0 {
err = fmt.Errorf("syntx error :%d\n", idx+1)
return
}
// 根据sectionName,去data里面根据反射找到对应结构体
for i := 0; i < t.Elem().NumField(); i++ {
filed := t.Elem().Field(i)
if filed.Tag.Get("ini") == sectionName {
structName = filed.Name // 字段名,即mysqlConfig或redisConfig
fmt.Printf("找到%s嵌套的结构体%s\n", sectionName, structName)
}
}
} else {
// 2.3 如果不是[],就是=分割键值对
if strings.Index(line, "=") == -1 || strings.HasSuffix(line, "=") || strings.HasPrefix(line, "=") {
err = fmt.Errorf("line:%d syntax error", idx+1)
return
}
v := reflect.ValueOf(data)
sValue := v.Elem().FieldByName(structName) // 拿到嵌套结构体的值信息
sType := sValue.Type()
if sValue.Kind() != reflect.Struct {
err = fmt.Errorf("data中%v应该为结构体\n", structName)
return
}
index := strings.Index(line, "=")
key := line[:index]
value := line[index+1:]
var filedName string
var filedType reflect.StructField
for i := 0; i < sType.NumField(); i++ {
field := sType.Field(i)
filedType = field
if field.Tag.Get("ini") == key { // mysqlConfig 的tag 对比
// 找到了字段
filedName = field.Name
break
}
}
fieldObj := sValue.FieldByName(filedName)
// fmt.Println(filedName, filedType.Type.Kind())
switch filedType.Type.Kind() {
case reflect.String:
fieldObj.SetString(value)
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
var valueInt int64
valueInt, err = strconv.ParseInt(value, 10, 64)
if err != nil {
err = fmt.Errorf("parse int type stynax error :%d", idx+1)
return
}
fieldObj.SetInt(valueInt)
case reflect.Bool:
var valueBool bool
valueBool, err = strconv.ParseBool(value)
if err != nil {
err = fmt.Errorf("parse bool type stynax error :%d", idx+1)
return
}
fieldObj.SetBool(valueBool)
}
}
}
return nil
}
func main() {
var cfg Config
err := loadIni("config.ini", &cfg)
if err != nil {
fmt.Printf("load ini failed, err:%v\n", err)
return
}
fmt.Println(cfg)
}
net/http包
Go语言内置的net/http
包提供了HTTP客户端和服务端的实现。
http服务端
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func a(int) {
fmt.Println("end..")
}
func f1(w http.ResponseWriter, req *http.Request) {
b, err := ioutil.ReadFile("a.html")
if err != nil {
fmt.Println("read file failed.", err)
return
}
w.Write(b)
}
func f2(w http.ResponseWriter, req *http.Request) {
fmt.Printf("req.URL: %v\n", req.URL) // 请求路径
fmt.Printf("req.Method: %v\n", req.Method) // 请求方法
queryString := req.URL.Query() // 获取query string, 返回URL.Values类型
age := queryString.Get("age")
fmt.Printf("age: %v\n", age)
fmt.Printf("req.Body: %v\n", req.Body) // body的内容
fmt.Println(ioutil.ReadAll(req.Body))
fmt.Printf("req.PostForm: %v\n", req.PostForm) //
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/post", f1)
http.HandleFunc("/xxx", f2)
http.ListenAndServe("127.0.0.1:9090", nil)
}
// 2. 示例2 post请求
func f3(w http.ResponseWriter, req *http.Request) {
req.ParseForm() // 无论PostForm 还是Form都需要调用ParseForm()解析form数据
name := req.Form.Get("name")
password := req.Form.Get("password")
if name == "root" && password == "123" {
w.Write([]byte("ok"))
} else {
w.Write([]byte("密码错误"))
}
}
func main(){
http.HandleFunc("/form", f3)
}
// 3. 示例3, 返回json && 获取json数据
func f4(w http.ResponseWriter, req *http.Request) {
// 获取参数
defer req.Body.Close()
jsonData := make(map[string]interface{}, 10)
err := json.NewDecoder(req.Body).Decode(&jsonData)
if err != nil {
fmt.Println("decode err", err)
return
}
fmt.Printf("jsonData: %#v\n", jsonData)
// 返回内容
header := w.Header()
header.Set("content-type", "application/json")
msg, _ := json.Marshal(map[string]string{"code": "200", "msg": "ok"})
w.Write(msg)
}
http客户端
// 1. 示例1 基本使用
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://127.0.0.1:9090/xxx?age=12")
if err != nil {
fmt.Println("get url err", err)
return
}
// 从服务端读数据
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read failed,", err)
return
}
fmt.Printf("string(b): %v\n", string(b))
}
// 2. url encode, 对中文编码
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main() {
urlPath := "http://127.0.0.1:9090/xxx"
query := url.Values{}
query.Set("name", "张三")
query.Set("age", "20")
query.Encode()
u, err := url.ParseRequestURI(urlPath)
if err != nil {
fmt.Println("parse request uri", err)
return
}
u.RawQuery = query.Encode()
resp, err := http.Get(u.String())
if err != nil {
fmt.Println("get err", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read err", err)
return
}
fmt.Printf("string(b): %v\n", string(b))
}
// 3. post请求示例
func main() {
contentType := "application/x-www-form-urlencoded"
resp, err := http.Post("http://127.0.0.1:9090/form", contentType, strings.NewReader("name=root&password=123")) // 使用req.PostForm.Get("name")进行获取。
if err != nil {
fmt.Println("post err", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read failed, ", err)
return
}
fmt.Printf("string(b): %v\n", string(b))
}
// 4. 发送json数据
func main() {
contentType := "application/json"
d := map[string]interface{}{
"name": "zs",
"age": 21,
"gender": "男",
"hobby": []string{"sing", "dance"},
}
jsonData, _ := json.Marshal(d)
resp, err := http.Post("http://127.0.0.1:9090/json", contentType, strings.NewReader(string(jsonData)))
if err != nil {
fmt.Println("post err", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read failed, ", err)
return
}
fmt.Printf("string(b): %v\n", string(b))
}
// 4. 自定义client
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:9090/xxx", strings.NewReader("name=123&age=123"))
if err != nil {
fmt.Println(err)
return
}
// 设置请求头
req.Header.Set("content-type", "application/json")
req.Header.Set("cookie", "name=zs")
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("string(b): %v\n", string(b))
}
单元测试
go语言中的测试依赖go test
命令。在目录中,所有以_test.go
为后缀名的源代码文件都是go test
测试的一部分,不会被go build
编译到最终的可执行文件。
在*_test.go
文件中有三种类型的函数,单元测试函数,基准测试和示例函数。
类型 | 格式 | 说明 |
---|---|---|
测试函数 | 函数名前缀为Test |
测试程序的逻辑是否正确 |
基准函数 | 函数名前缀为Benchmark |
测试函数的性能 |
示例函数 | 函数名前缀为Example |
为文档提供示例文档 |
go test
命令会遍历所有的*_test.go
文件中符合上述规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行,报告测试结果,最后清理测试中生成的临时文件。
单元测试函数
格式:
func TestFuncName(t *testing.T){
}
// 命令
go test
go test -v // 输出完整的测试结果
go test -run=测试函数名 // 只执行某个测试用例。 例如:go test -v -run=Split2
注意:
- 每个测试函数必须导入
testing
包 - 测试函数的名字必须以
Test
开头
方法名:
func (c *T) Cleanup(func())
func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Helper()
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool
func (c *T) TempDir() string
示例:
// 例如:要测试一个字符串分割的函数
// 1. split.go
package splitstring
import "strings"
func Split(s string, sep string) []string {
var tmp []string
index := strings.Index(s, sep)
for index >= 0 {
tmp = append(tmp, s[:index])
s = s[index+1:]
index = strings.Index(s, sep)
}
tmp = append(tmp, s)
return tmp
}
// 2. 测试文件
// split_test.go
package splitstring
import (
"reflect"
"testing"
)
func TestSplit(t *testing.T) {
got := Split("abcdefb1", "b")
want := []string{"a", "cdef", "1"}
if !reflect.DeepEqual(got, want) { // 比较两个引用类型可以使用
t.Fatalf("got %v but want %v\n", got, want)
}
}
func TestSplit2(t *testing.T) {
got := Split("bb1adefb1", "b1")
want := []string{"b", "adef", ""}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %v but want %v\n", got, want)
}
}
测试组
一个测试函数中对多个测试用例测试。
func TestSplit3(t *testing.T) {
type testCase struct {
str string
sep string
want []string
}
testGroup := []testCase{
{"abcabc123", "b", []string{"a", "ca", "c123"}},
{"bb1abc1", "b1", []string{"b", "abc1"}},
{"上海在海上边", "海", []string{"上", "在", "上边"}},
}
for _, tc := range testGroup {
got := Split(tc.str, tc.sep)
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("got %#v but want %#v\n", got, tc.want)
}
}
}
/* 问题:显示的不清晰
=== RUN TestSplit3
--- PASS: TestSplit3 (0.00s)
PASS
ok learn_test/splitstring 0.573s
*/
子测试
在上面的示例中我们为每一个测试数据编写了一个测试函数,而通常单元测试中需要多组测试数据保证测试的效果。Go1.7+中新增了子测试,支持在测试函数中使用t.Run
执行一组测试用例。
示例:
func TestSplit4(t *testing.T) {
type TestCase struct {
str string
sep string
want []string
}
testGroup := map[string]TestCase{
"case_1": {"abcabc123", "b", []string{"a", "ca", "c123"}},
"case_2": {"bb1abc1", "b1", []string{"b", "abc1"}},
"case_3": {"上海在海上边", "海", []string{"上", "在", "上边"}},
}
for name, v := range testGroup {
t.Run(name, func(t *testing.T) {
got := Split(v.str, v.sep)
if !reflect.DeepEqual(v.want, got) {
t.Fatalf("got %v but want %v\n", got, v.want)
}
})
}
}
/*
go test -v -run=Split4/case_2 // 单独跑某个测试用例
=== RUN TestSplit4
=== RUN TestSplit4/case_2
=== RUN TestSplit4/case_3
=== RUN TestSplit4/case_1
--- PASS: TestSplit4 (0.00s)
--- PASS: TestSplit4/case_2 (0.00s)
--- PASS: TestSplit4/case_3 (0.00s)
--- PASS: TestSplit4/case_1 (0.00s)
PASS
ok learn_test/splitstring 0.300s
*/
测试覆盖率
测试代码覆盖率为60%
,函数覆盖率100%
go test -cover // 查看代码覆盖率
go test -cover -coverprofile=c.out // 测试覆盖率输出到文件中
go tool cover -html c.out // 用浏览器打开查看
基准测试
基准测试就是在一定的工作负载下检测程序性能的一种方法。
格式:
func Benchmark函数名(b *testing.B){
}
// 命令
go test -bench=Split // 执行benchmark测试命令
go test -bench=Split -benchmem // 并显示内存情况
注意:
- 基准测试以
Benchmark
作为前缀,需要有一个b testing.B
作为函数参数。 - 基准测试必须执行
b.N
。 - 默认情况下,基准测试至少1秒,如果没有到1秒,则
b.N
的值会按1,2,5,10,20,50...
增加。
方法:
func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Name() string
func (b *B) ReportAllocs()
func (b *B) ResetTimer()
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool
func (b *B) StartTimer()
func (b *B) StopTimer()
示例:
func BenchmarkSplit(b *testing.B) {
for i := 0; i < b.N; i++ { // 执行了b.N次,这个数字是动态变化的
Split("abcabc123", "b")
}
}
/*
goos: windows
goarch: amd64
pkg: learn_test/splitstring
cpu: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz
BenchmarkSplit-4 4737072 247.2 ns/op // 4核 执行次数 每次操作秒数
PASS
ok learn_test/splitstring 1.738s
*/
/*
go test -bench=Split -benchmem
BenchmarkSplit-4 4623079 247.4 ns/op 112 B/op 3 allocs/op // 每次内存占用 每次操作内存分配次数
*/
性能优化
go语言项目中性能优化主要是以下几个方面:
- CPU:报告CPU的使用情况,按照一定频率去采集应用程序在CPU和寄存器上的数据
- Memory:报告内存的使用情况
- Block:保存goroutine不在运行状态的情况,可以用来分析和查找思索等性能瓶颈。
- goroutine:报告goroutine的使用情况。
go内置了获取程序的运行数据工具,
runtime/pprof // 采集工具型应用运行数据进行分析
net/http/pprof // 采集服务型应用运行时数据进行分析
注意:
pprof
开启后,每个一段时间(10ms)就会采集当下的堆栈信息,获取各个函数的占用CPU以及内存资源,最后通过对这些数据分析,形成一个完整的性能分析报告。
工具型应用
将信息写入到文件中,通过go tool pprof 文件名
工具进行内存性能分析。
// 导入
import "runtime/pprof"
// cpu性能分析
pprof.StartCPUProfile(w io.Writer) // 开启CPU性能分析,写入文件
pprof.StopCPUProfile() // 停止cpu性能分析
// 内存性能优化
pprof.WriteHeapProfile(w io.Writer)
示例:
package main
import (
"flag"
"fmt"
"os"
"runtime/pprof"
"time"
)
func logicCode() {
var ch chan int
for {
select {
case v := <-ch:
fmt.Printf("recv from chan, value:%v\n", v)
default:
time.Sleep(time.Millisecond * 500) // 添加这行代码, 等待的话,**
}
}
}
func main() {
var isCpuPropf bool // 是否开启cpu propf标志位
var isMemPropf bool // 是否开启mem propf标志位
flag.BoolVar(&isCpuPropf, "cpu", false, "turn cpu pprof on")
flag.BoolVar(&isMemPropf, "mem", false, "turn mem pprof on")
flag.Parse()
if isCpuPropf {
f, err := os.Create("cpu.pprof")
if err != nil {
fmt.Println("create cpu file failed.")
return
}
pprof.StartCPUProfile(f) // 写入cpu信息
defer pprof.StopCPUProfile()
}
for i := 0; i < 8; i++ {
go logicCode()
}
time.Sleep(time.Second * 20)
if isMemPropf {
f2, err := os.Create("mem.pprof")
if err != nil {
fmt.Println("create mem file failed")
return
}
pprof.WriteHeapProfile(f2) // 写入内存信息
f2.Close()
}
}
命令行交互界面
go tool pprof cpu.pprof // 进入命令行
示例
// 在交互界面输入top 3来查看程序中占用CPU前3位的函数
(pprof) top 3
Showing nodes accounting for 100.37s, 87.68% of 114.47s total
Dropped 17 nodes (cum <= 0.57s)
Showing top 3 nodes out of 4
flat flat% sum% cum cum%
42.52s 37.15% 37.15% 91.73s 80.13% runtime.selectnbrecv
35.21s 30.76% 67.90% 39.49s 34.50% runtime.chanrecv
22.64s 19.78% 87.68% 114.37s 99.91% main.logicCode
/*
flat: 当前函数占用CPU的耗时
flat%: 当前函数占用CPU的耗时百分比
sum%: 函数占用CPU的耗时累计百分比
cum: 当前函数加上调用当前函数的函数占用CPU的总耗时
cum%:当前函数加上调用当前函数的函数占用CPU的总耗时百分比
*/
// 命令: list 函数名
(pprof) list logicCode
服务型应用
使用net/http/pprof
库,它能够在提供 HTTP 服务进行分析。
// 1. 如果使用了默认的http.DefaultServeMux(通常是代码直接使用 http.ListenAndServe(“0.0.0.0:8000”, nil)),只需要在你的web server端代码中按如下方式导入net/http/pprof
import _ "net/http/pprof"
// 2. 如果使用自定义的 Mux,则需要手动注册一些路由规则
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
// 3. 如果使用的是gin框架, 推荐使用github.com/gin-contrib/pprof
pprof.Register(router)
// 4. 使用前面三种方法后,http服务会多出一个 /debug/pprof 的地址。
/debug/pprof/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载
/debug/pprof/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件
/debug/pprof/block:block Profiling 的路径
/debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系
flag包
命令行工具
导入
import "flag"
flag包支持的命令行参数类型有bool、int、int64、uint、uint64、float float64、string、duration。
flag参数 | 有效值 |
---|---|
字符串flag | 合法字符串 |
整数flag | 1234、0664、0x1234等类型,也可以是负数。 |
浮点数flag | 合法浮点数 |
bool类型flag | 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。 |
时间段flag | 任何合法的时间段字符串。如”300ms”、”-1.5h”、”2h45m”。 合法的单位有”ns”、”us” /“µs”、”ms”、”s”、”m”、”h”。 |
常用方法
// 0. os.Args
slice := os.Args
// 1.flag.类型()
flag.类型(名称, 默认值,帮助信息) *类型
// 示例:
name := flag.String("name", "张三", "请输入姓名") // 返回*string
age := flag.Int("age", 18, "请输入年龄") // 返回*int
married := flag.Bool("married", false, "是否结婚") // 返回*bool
salary := flag.Float64("salary", 0.0, "输入薪资") // 返回*float64
ct := flag.Duration("ct", time.Second, "结婚时间") // 返回*time.Duration
// 2. flag.类型Var()
flag.类型Var(名称,默认值,帮助信息)
// 示例:
var name string
var age int
var married bool
var salary float64
var ct time.Duration
flag.StringVar(&name, "name", "zs", "请输入姓名")
flag.IntVar(&age, "age", 0, "请输入年龄")
flag.BoolVar(&married, "married", false, "是否结婚")
flag.Float64Var(&salary, "salary", 0.0, "薪资")
flag.DurationVar(&ct, "ct", time.Second, "结婚时间")
// 3. flag.Parse()
// 通过调用flag.Parse()来对命令行参数进行解析。
格式:
-flag xxx
--flag xxx
-flag=xxx
--flag=xxx
// 4. flag.Args()
// 返回命令行参数后的其他参数(不包含go文件) [a b c]
示例:
package main
import (
"flag"
"fmt"
"time"
)
func main1() {
name := flag.String("name", "张三", "请输入姓名") // 返回*string
age := flag.Int("age", 18, "请输入年龄") // 返回*int
married := flag.Bool("married", false, "是否结婚") // 返回*bool
salary := flag.Float64("salary", 0.0, "输入薪资") // 返回*float64
ct := flag.Duration("ct", time.Second, "结婚时间")
flag.Parse() // 解析命令行参数
fmt.Printf("name: %v\n", *name)
fmt.Printf("age: %v\n", *age)
fmt.Printf("married: %v\n", *married)
fmt.Printf("salary: %v\n", *salary)
fmt.Printf("ct: %v\n", *ct)
}
func main() {
var name string
var age int
var married bool
var salary float64
var ct time.Duration
flag.StringVar(&name, "name", "zs", "请输入姓名")
flag.IntVar(&age, "age", 0, "请输入年龄")
flag.BoolVar(&married, "married", false, "是否结婚")
flag.Float64Var(&salary, "salary", 0.0, "薪资")
flag.DurationVar(&ct, "ct", time.Second, "结婚时间")
flag.Parse()
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("married: %v\n", married)
fmt.Printf("salary: %v\n", salary)
fmt.Printf("ct: %v\n", ct)
}
/*
go run main.go -name lisi -age 18 -married true -salary 19.1 -ct 100h
*/