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操作封装了下面几个包中:

  • ioio原语提供基本接口
  • io/ioutil 封装了一些使用的i/o函数。
  • fmt 提供格式化i/o
  • bufio实现了带缓冲io

基本的i/o 接口

在io包中最重要的两个接口:ReaderWriter

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,实现了包含ReaderWriter等接口。它包装一个io.Readerio.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(扫描一旦终止,将无法再继续):

  1. 遇到io.EOF
  2. 遇到读写错误
  3. “匹配部分”的长度超过了缓存的长度

常用函数:

函数名 描述
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系列。

函数系列 描述
print 单纯打印日志
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函数区别

区别:

  1. make只能用于分配及初始化类型为slicemapchan。 而new可以为任意类型分配内存空间,例如:int,struct等。
  2. new分配返回的是指针,即*Typemake返回的是类型,即Type
  3. new 分配的空间默认为零值。而 make分配后会进行初始化。之所以不同,是因为这三种类型背后引用了使用前必须初始化的数据结构。
  4. 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.Readerio.ReadAtio.WriterToio.Seekerio.ByteScannerio.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.Readerio.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.Typereflect.Value两部分组成,并且通过reflect.TypeOf()reflect.ValueOf()两个函数来获取任意对象的Value和Type。

使用场景:

  1. 函数传递参数时,不知道传递的参数类型
  2. 有时候需要根据某些条件在程序运行期间动态的执行函数

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

*/
posted @ 2022-06-30 17:53  学习记录13  阅读(64)  评论(0编辑  收藏  举报