fmt

fmt介绍

fmt 用于格式化输入和输出数据。这个库的名称 “fmt” 来自于 “format”,因为它主要用于格式化数据。

fmt 包的主要功能包括:

  1. 格式化输出:fmt 包提供了函数如 Print, Printf, Println, Fprint, Fprintf, 和 Fprintln 用于将数据输出到标准输出或指定的 io.Writer。你可以使用这些函数将数据以不同的格式打印到屏幕上或文件中。
  2. 格式化输入:fmt 包也支持从输入源(通常是标准输入)读取数据,并根据格式规范解析数据。这是通过 Scan, Scanf, 和 Scanln 函数实现的。这对于从用户获取输入数据非常有用。
  3. 字符串格式化:你可以使用 Sprintf 函数将数据格式化为字符串而不是直接输出到标准输出,这对于构建日志消息或其他需要格式化的字符串很有用。
  4. 错误格式化:fmt 包也提供了 Errorf 函数,用于将格式化的错误消息作为 error 类型返回,方便错误处理。
  5. 格式化占位符:在格式化字符串中,你可以使用占位符来指定如何格式化数据。常见的占位符包括 %d(整数),%f(浮点数),%s(字符串)等。

输出

Print

  • Print:接受任意数量的参数,并将它们串联成一个字符串输出,不会添加换行符。
  • Printf:格式化输出,根据格式化字符串的占位符将参数格式化并输出。
  • Println:类似于 Print,但会在输出后自动添加一个换行符。
func Print(a ...interface{}) (n int, err error) 
func Printf(format string, a ...interface{}) (n int, err error) 
func Println(a ...interface{}) (n int, err error)

例子:

func main() { 
    fmt.Print("Hello, ", "world") 
    name := "Alice" 
    age := 30 
    fmt.Printf("Hello, %s. You are %d years old.\n", name, age) 
    fmt.Println("Hello, world") 
    }

Fprint

Fprint 用于将文本输出到指定的 io.Writer 接口,而不仅仅是标准输出。你可以将文本输出到文件、网络连接等。这些函数的参数列表包括一个 io.Writer 参数,以及任意数量的参数。

func Fprint(w io.Writer, a ...interface{}) (n int, err error) 
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) 
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

举个例子:

func main() { 
    // 向标准输出写入内容 
    fmt.Fprintln(os.Stdout, "向标准输出写入内容") 
    fileObj, err := os.OpenFile("./output.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 
    if err != nil { 
        fmt.Println("打开文件出错,err:", err) 
        return 
        } 
        name := "jarvis" // 向打开的文件句柄中写入内容 
        fmt.Fprintf(fileObj, "往文件中写如信息:%s", name) 
        }

这个示例创建了一个名为 “output.txt” 的文件,并将数据写入文件中。

Sprint

Sprint 用于将文本输出到字符串中,而不是标准输出或文件。它们将文本格式化为字符串并返回结果。

func Sprint(a ...interface{}) string 
func Sprintf(format string, a ...interface{}) string 
func Sprintln(a ...interface{}) string

简单的示例代码如下:

func main() { 
    s1 := fmt.Sprint("jarvis") 
    name := "jarvis" 
    age := 18 
    s2 := fmt.Sprintf("name:%s,age:%d", name, age) 
    s3 := fmt.Sprintln("jarvis") 
    fmt.Println(s1, s2, s3) 
    }

Errorf

Errorf 用于创建格式化的错误消息并返回一个 error 类型的值。这允许你将格式化的错误消息返回给调用者,以便更好地进行错误处理。这些函数的用法类似于 Sprintf,但它们返回一个 error 值而不是字符串。

  • Errorf:根据format参数生成格式化字符串并返回一个包含该字符串的错误。
func Errorf(format string, a ...interface{}) error

通常使用这种方式来自定义错误类型,例如:

err := fmt.Errorf("这是一个错误")

格式化占位符

*printf系列函数都支持format格式化参数,在这里我们按照占位符将被替换的变量类型划分,方便查询和记忆。

通用占位符

通用占位符用于格式化不同类型的数据:

占位符 说明
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 打印值的类型
%% 百分号

代码示例:

func main() { 
    fmt.Printf("%v\n", 100) 
    fmt.Printf("%v\n", false) 
    o := struct{ name string }{"jarvis"} 
    fmt.Printf("%v\n", o) 
    fmt.Printf("%#v\n", o) 
    fmt.Printf("%T\n", o) 
    fmt.Printf("100%%\n") }

布尔型

占位符 说明
%t true或false

整型

占位符 说明
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于”U+%04X”
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示

示例代码如下:

n := 65 
fmt.Printf("%b\n", n) 
fmt.Printf("%c\n", n) 
fmt.Printf("%d\n", n) 
fmt.Printf("%o\n", n) 
fmt.Printf("%x\n", n) 
fmt.Printf("%X\n", n)

浮点数与复数

占位符 说明
%b 无小数部分、二进制指数的科学计数法,如-123456p-78
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

示例代码如下:

f := 12.34 
fmt.Printf("%b\n", f) 
fmt.Printf("%e\n", f) 
fmt.Printf("%E\n", f) 
fmt.Printf("%f\n", f) 
fmt.Printf("%g\n", f) 
fmt.Printf("%G\n", f)

字符串和[]byte

占位符 说明
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f
%X 每个字节用两字符十六进制数表示(使用A-F)

示例代码如下:

s := "jarvis" 
fmt.Printf("%s\n", s) 
fmt.Printf("%q\n", s) 
fmt.Printf("%x\n", s) 
fmt.Printf("%X\n", s)

指针

占位符 说明
%p 表示为十六进制,并加上前导的0x

示例代码如下:

a := 18 
fmt.Printf("%p\n", &a) 
fmt.Printf("%#p\n", &a)

宽度标识符

宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下

占位符 说明
%f 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0

示例代码如下:

n := 88.88 
fmt.Printf("%f\n", n) 
fmt.Printf("%9f\n", n) 
fmt.Printf("%.2f\n", n) 
fmt.Printf("%9.2f\n", n) 
fmt.Printf("%9.f\n", n)

其他flag

占位符 说明
‘+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
’ ’ 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格
‘-’ 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
‘#’ 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;
‘0’ 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;

举个例子:

 s := 123
 fmt.Printf("8%d8\n", s)
 fmt.Printf("8%5d8\n", s)
 fmt.Printf("8%-5d8\n", s)
 fmt.Printf("8%5.4d8\n", s)
 fmt.Printf("8%05d8\n", s)
 fmt.Printf("8%+d8\n", -s)

 s1 := 123.45
 fmt.Printf("8%f8\n", s1)
 fmt.Printf("8%.2f8\n", s1)
 fmt.Printf("8%15f8\n", s1)
 fmt.Printf("8%15.2f8\n", s1)
 fmt.Printf("8%015.2f8\n", s1)

 

 s2 := "abc"
 fmt.Printf("8%08s8\n", s2)
 fmt.Printf("8% x8\n", s2)

获取输入

Go 语言的 fmt 包提供了 fmt.Scan、fmt.Scanf 和 fmt.Scanln 这三个函数,用于从标准输入获取用户的输入。这些函数允许你与用户交互,从标准输入流中读取不同类型的数据并将其存储在相应的变量中

Scan

Scan 函数用于从标准输入中获取用户的输入,并将输入的数据存储在变量中。它根据空格分隔输入,适合获取多个输入值。

函数定义如下:

func Scan(a ...interface{}) (n int, err error)
  • Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。

具体代码示例如下:

package main
import "fmt"
func main() { 
    var name string 
    var age int
    fmt.Print("Enter your name: ") 
    fmt.Scan(&name) 
    fmt.Print("Enter your age: ") 
    fmt.Scan(&age)
    fmt.Printf("Name: %s, Age: %d\n", name, age) 
    }

Scanln(未理解)

Scanln 函数用于从标准输入中获取用户的输入,并将输入的数据存储在变量中,每行一个变量。它通常用于获取多个输入值,每个值在单独的行中输入。

函数定义如下:

func Scanln(a ...interface{}) (n int, err error)
  • Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。

Scanf

Scanf 函数用于根据格式规范解析输入,并将数据存储在变量中。它允许你指定输入的格式,并可以处理不同类型的数据。

函数签名如下:

func Scanf(format string, a ...interface{}) (n int, err error)
  • Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。

代码示例如下:

package main
import "fmt"
func main() { 
    var name string 
    var age int
    fmt.Print("Enter your name and age: ") 
    fmt.Scanf("%s %d", &name, &age)
    fmt.Printf("Name: %s, Age: %d\n", name, age) }

使用 bufio 包获取输入

bufio 包提供了一种更灵活的方式来处理输入,特别是在需要完整读取一行或多行输入的情况下。你可以使用 bufio.NewReader 创建一个输入缓冲区,然后使用 ReadString 函数来读取输入,直到指定的分隔符(例如换行符 \n)。这允许你获取包含空格在内的完整输入内容。

func bufioDemo() { 
    reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象 
    fmt.Print("请输入内容:") 
    text, _ := reader.ReadString('\n') // 读取直到换行符 
    text = strings.TrimSpace(text) 
    fmt.Printf("%#v\n", text) 
    }

使用 Fscan 函数

Fscan 系列函数允许你从 io.Reader 接口中读取数据,而不仅仅是标准输入。这些函数与 fmt.Scan、fmt.Scanf 和 fmt.Scanln 类似,但允许你从任何实现 io.Reader 接口的地方读取数据。

  • Fscan:从 io.Reader 中读取数据。
  • Fscanln:从 io.Reader 中读取一行数据。
  • Fscanf:根据指定的格式从 io.Reader 中读取数据。
func Fscan(r io.Reader, a ...interface{}) (n int, err error) 
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

代码示例:

package main
import ( "fmt" "strings" )
func main() { 
    input := "42 John" 
    reader := strings.NewReader(input) // 从字符串生成读对象
    var age int 
    var name string
    n, err := fmt.Fscanf(reader, "%d %s", &age, &name) 
    if err != nil { 
        fmt.Println("Error:", err) 
        return 
        }
    fmt.Printf("Read %d values: Age: %d, Name: %s\n", n, age, name) 
    }

使用 Sscan 函数

Sscan 系列函数允许你从字符串中读取数据,而不仅仅是从标准输入。这些函数与 fmt.Scan、fmt.Scanf 和 fmt.Scanln 类似,但允许你从字符串中读取数据。

func Sscan(str string, a ...interface{}) (n int, err error) 
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

代码示例:

package main
import ( "fmt" )
func main() { 
    input := "Alice 30" 
    var name string 
    var age int
    n, err := fmt.Sscanf(input, "%s %d", &name, &age) 
    if err != nil { 
        fmt.Println("Error:", err) 
        return 
        }
    fmt.Printf("Read %d values: Name: %s, Age: %d\n", n, name, age) }

io

io 包提供了许多用于处理 I/O(输入输出) 操作的接口和函数。

Reader

io.Reader 表示任何可以读取数据的对象,定义了基本的 Read 方法。

type Reader interface { Read(p []byte) (n int, err error) }

Read 方法读取长度为 len(p) 字节的数据到 p 中,返回两个参数,读取数据的字节数(0 <= n <= len(p))和 error 信息。当 Read 在读取 n>0 个字节后遇到错误或 EOF 时,返回读取的字节数和一个可能为非空的 error,下次接着读取时,会返回 0 和 非空 error。看个示例:

package main

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

func main() {
 f, err := os.Open("test.txt")
 if err != nil {
  panic(err)
 }
 defer f.Close()
 buf := make([]byte, 4) // 实例化一个长度为4的[]byte
 i := 0
 for {
  i++
  n, err := f.Read(buf)
  fmt.Printf("第 %d 次读取, ", i)
  fmt.Printf("返回行数:%d,error 信息:%v ", n, err)
  if n == 0 || err == io.EOF {
   fmt.Println("文件以读取完毕")
   break
  }
  fmt.Printf("读取内容:%s \n", string(buf[:n]))
 }
}

将 test.txt 文件内容设置为 5 个字节 luduoe,运行看下效果:

第 1 次读取, 返回行数:4,error 信息:<nil> 读取内容:ludu 
第 2 次读取, 返回行数:4,error 信息:<nil> 读取内容:oe

第 3 次读取, 返回行数:3,error 信息:<nil> 读取内容:qwe
第 3 次读取, 返回行数:0,error 信息:EOF 文件以读取完毕

io.Writer

Writer 接口表示一个可以写入数据的对象,定义了基本的 Write 方法。

type Writer interface { Write(p []byte) (n int, err error) }

Write方法写入长度为 len(p) 字节的数据到基本数据流中,返回实际写入的字节数 n 和 error 信息。若 Write 返回的 n < len(p),同时会返回一个非 nil 的 error。简单示例如下:

package main 
import ( 
    "fmt" 
    "os" 
) 
func main() { 
    file, err := os.Create("file.txt") 
    if err != nil { 
        panic(err) 
        } 
        defer file.Close() 
        data := []byte("luduoxin's blog") 
        n, err := file.Write(data) 
        if err != nil { 
            panic(err) 
        } 
        fmt.Printf("%d bytes written", n) 
        }

io.Closer

Closer 接口表示可以关闭的对象,定义了一个基本的 Close 方法,通常在完成读写后关闭IO流以释放资源。

type Closer interface { Close() error }

Close 方法用于释放资源,返回可能出现的 error,简单示例如下:

import ( 
    "os" 
) 
func main() { 
    file, err := os.Create("file.txt") 
    if err != nil { 
        panic(err) 
    } // 在文件使用结束后,需要调用 Close 方法释放资源。 
    defer file.Close() // 文件读写操作... 
    }

io.Seeker

Seeker 接口表示可以随机读写的对象,定义了基本的 Seek 方法。Seek方法定位到给定偏移量位置,返回新的 offset 和 error 信息。

type Seeker interface { Seek(offset int64, whence int) (int64, error) }

Seek 方法将当前读或写位置设置为距离偏移量 offset 个字节之后的位置。参数 whence 可以是 0、1 或 2:

  1. 参数 whence 为 0 时,表示相对于文件起始位置,offset 必须为非负数。
  2. 参数 whence 为 1 时,表示相对于当前位置,offset 可以为负数。
  3. 参数 whence 为 2 时,表示相对于文件结尾,offset 可以为负数。

Seek 方法将返回新的偏移量和可能出现的错误。示例如下:

package main 
import ( 
    "fmt" 
    "io" 
    "os" 
) 
func main() { 
    file, err := os.Create("file.txt") 
    if err != nil { 
        panic(err) 
        } 
    defer file.Close() // 写入数据到文件中... 
    file.Write([]byte("hello world")) // 将文件指针移动到文件头 
    file.Seek(0, io.SeekStart) // 从文件中读取数据到切片中 
    buf := make([]byte, 5) 
    file.Read(buf) 
    fmt.Println(string(buf)) // 输出 hello // 将文件指针移动文件末尾 
    file.Seek(0, io.SeekEnd) // 在文件末尾进行写入操作 
    file.Write([]byte("golang")) }

通过调用 Seek 方法将文件指针移动到文件开头,并从文件中读取了数据,接下来将文件指针移动到文件末尾,并在文件末尾写入了新的数据。

os

函数 功能
func Mkdir(name string, perm FileMode) error 创建目录
func MkdirAll(path string, perm FileMode) error 递归创建目录
func MkdirTemp(dir, pattern string) (string, error) 创建临时目录
func Remove(name string) error 删除文件
func RemoveAll(path string) error 删除文件夹及其子文件夹
func Rename(oldpath, newpath string) error 重命名文件
func Symlink(oldname, newname string) error 创建符号链接
func WriteFile(name string, data []byte, perm FileMode) error 写入文件
func ReadFile(name string) ([]byte, error) 读取文件
func Readlink(name string) (string, error) 读取符号链接
func Create(name string) (*File, error) 创建文件
func Open(name string) (*File, error) 打开文件
func OpenFile(name string, flag int, perm FileMode) (*File, error) 打开文件
func CreateTemp(dir, pattern string) (*File, error) 创建临时文件
func TempDir() string 获取临时目录路径
func (f *File) Close() error 关闭文件
func (f *File) Name() string 获取文件名
func (f *File) Read(b []byte) (n int, err error) 读取文件
func (f *File) ReadAt(b []byte, off int64) (n int, err error) 读取文件
func (f *File) Write(b []byte) (n int, err error) 写入文件
func ReadDir(name string) ([]DirEntry, error) 读取文件夹
func (f *File) ReadDir(n int) ([]DirEntry, error)
func (f *File) Readdir(n int) ([]FileInfo, error)
func (f *File) Readdirnames(n int) (names []string, err error)

strconv

以下是将提供的函数和功能整理成Markdown表格形式:

函数声明 功能
func (e *NumError) Error() string NumError类型的方法,返回错误信息字符串
func IsPrint(r rune) bool 返回一个字符是否是可打印的
func CanBackquote(s string) bool 返回字符串是否可以表示为反引号字符串
func Quote(s string) string 返回字符串在Go语法下的双引号字面值表示
func QuoteToASCII(s string) string 返回字符串在Go语法下的双引号字面值表示,非ASCII字符会进行转义
func QuoteRune(r rune) string 返回字符在Go语法下的单引号字面值表示
func QuoteRuneToASCII(r rune) string 返回字符在Go语法下的单引号字面值表示,非ASCII字符会进行转义
func Unquote(s string) (t string, err error) 解析字符串并返回其值
func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) 解析字符的Go语法字符串并返回其值。函数假设s是一个表示字符的go语法字符串,解析它并返回四个值:1) value,表示一个rune值或者一个byte值;2) multibyte,表示value是否是一个多字节的utf-8字符;3) tail,表示字符串剩余的部分;4) err,表示可能存在的语法错误
func ParseBool(str string) (value bool, err error) 解析字符串并返回其布尔值,它接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE
func ParseInt(s string, base int, bitSize int) (i int64, err error) 解析字符串并返回其整数值。base指定进制(2到36),如果base为0,则会从字符串前置判断,“0x”是16进制,“0”是8进制,否则是10进制。bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。
func ParseUint(s string, base int, bitSize int) (n uint64, err error) 解析字符串并返回其无符号整数值
func ParseFloat(s string, bitSize int) (f float64, err error) 解析字符串并返回其浮点数值。函数会返回最为接近s表示值的一个浮点数(使用IEEE754规范舍入)。bitSize指定了期望的接收类型,32是float32(返回值可以不改变精确值的赋值给float32),64是float64
func FormatBool(b bool) string 根据布尔值返回字符串
func FormatInt(i int64, base int) string 格式化整数为字符串
func FormatUint(i uint64, base int) string 格式化无符号整数为字符串
func FormatFloat(f float64, fmt byte, prec, bitSize int) string 格式化浮点数为字符串
func Atoi(s string) (i int, err error) 将字符串转换为整数
func Itoa(i int) string 将整数转换为字符串
func AppendBool(dst []byte, b bool) []byte 将布尔值追加到字节切片
func AppendInt(dst []byte, i int64, base int) []byte 将整数追加到字节切片
func AppendUint(dst []byte, i uint64, base int) []byte 将无符号整数追加到字节切片
func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int) []byte 将浮点数追加到字节切片
func AppendQuote(dst []byte, s string) []byte 将字符串追加到字节切片并转义
func AppendQuoteToASCII(dst []byte, s string) []byte 将字符串追加到字节切片并转义ASCII字符
func AppendQuoteRune(dst []byte, r rune) []byte 将字符追加到字节切片并转义
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte 将字符追加到字节切片并转义ASCII字符

sync

RWMutex

Go语言包中的 sync 包提供了两种锁类型:sync.Mutex 和 sync.RWMutex。其中RWMutex是基于Mutex实现的,只读锁的实现使用类似引用计数器的功能。 特点:

  • 读锁不能阻塞读锁
  • 读锁需要阻塞写锁,直到所有读锁都释放
  • 写锁需要阻塞读锁,直到所有写锁都释放
  • 写锁需要阻塞写锁
func (rw *RWMutex) RLock() //读锁,当有写锁时,无法加载读锁,当只有读锁或者没有锁时,可以加载读锁,读锁可以加载多个,所以适用于“读多写少”的场景。
func (rw *RWMutex) RUnlock() //读锁解锁,RUnlock 撤销单次RLock调用,它对于其它同时存在的读取器则没有效果。若rw并没有为读取而锁定,调用RUnlock就会引发一个运行时错误。
func (rw *RWMutex) Lock() //写锁,如果在添加写锁之前已经有其他的读锁和写锁,则Lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的Lock调用会从获得的锁中排除新的读取锁,即写锁权限高于读锁,有写锁时优先进行写锁定。
func (rw *RWMutex) Unlock() //写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误。
package main

import (
 "fmt"
 "sync"
 "time"
)

var rwMutex *sync.RWMutex
var wg *sync.WaitGroup

func main() {
 rwMutex = new(sync.RWMutex)
 wg = new(sync.WaitGroup)

 //wg.Add(2)

 //多个同时读取
 // go readData(1)
 // go readData(2)

 wg.Add(3)
 go writeData(1)
 go readData(2)
 go writeData(3)

 wg.Wait()
 fmt.Println("main..over...")
}

func writeData(i int) {
 defer wg.Done()
 fmt.Println(i, "开始写:write start。。")
 rwMutex.Lock() //写操作上锁
 fmt.Println(i, "正在写:writing。。。。")
 time.Sleep(3 * time.Second)
 rwMutex.Unlock()
 fmt.Println(i, "写结束:write over。。")
}

func readData(i int) {
 defer wg.Done()

 fmt.Println(i, "开始读:read start。。")

 rwMutex.RLock() //读操作上锁
 fmt.Println(i, "正在读取数据:reading。。。")
 time.Sleep(3 * time.Second)
 rwMutex.RUnlock() //读操作解锁
 fmt.Println(i, "读结束:read over。。。")
}

Mutex

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

WaitGroup

它的使用场景是在一个goroutine等待一组goroutine执行完成。sync.WaitGroup拥有一个内部计数器。当计数器等于0时,则Wait()方法会立即返回。否则它将阻塞执行Wait()方法的goroutine直到计数器等于0时为止。

要增加计数器,我们必须使用Add(int)方法。要减少它,我们可以使用Done()(将计数器减1),也可以传递负数给Add方法把计数器减少指定大小,Done()方法底层就是通过Add(-1)实现的。

wg := &sync.WaitGroup{}

for i := 0; i < 8; i++ {
  wg.Add(1)
  go func() {
    // Do something
    wg.Done()
  }()
}

wg.Wait()

main goroutine会在八个goroutine都执行wg.Done()将计数器变为0后才能继续执行。

Once

Once是一个简单而强大的原语,可确保一个函数仅执行一次。

//func (o *Once) Do(f func())
once := &sync.Once{}
for i := 0; i < 4; i++ {
    i := i
    go func() {
        once.Do(func() {
            fmt.Printf("first %d\n", i)
        })
    }()
}

如果once.Do(f)被多次调用,只有第一次调用会执行f,即使f每次调用Do 提供的f值不同。需要给每个要执行仅一次的函数都建立一个Once类型的实例。只有f返回后Do方法才会返回,f若引起了Do的调用,会导致死锁。

Pool

Get() interface{} //用来从并发池中取出元素。
Put(interface{}) //将一个对象加入并发池。

pool := &sync.Pool{
  New: func() interface{} {
    return NewConnection()
  },
}

connection := pool.Get().(*Connection)

这样每次调用Get()时,将返回由在pool.New中指定的函数创建的对象

特点:

  • sync.Pool 本质用途是增加临时对象的重用率,减少 GC 负担;
  • 不能对 Pool.Get 出来的对象做预判,有可能是新的(新分配的),有可能是旧的(之前人用过,然后 Put 进去的);
  • 不能对 Pool 池里的元素个数做假定;
  • sync.Pool 本身的 Get, Put 调用是并发安全的,sync.New 指向的初始化函数会并发调用,里面安不安全只有自己知道;
  • 当用完一个从 Pool 取出的实例时候,一定要记得调用 Put,否则 Pool 无法复用这个实例,通常这个用 defer 完成;
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// 用来统计实例真正创建的次数
var numCalcsCreated int32

// 创建实例的函数
func createBuffer() interface{} {
    // 这里要注意下,非常重要的一点。这里必须使用原子加,不然有并发问题;
    atomic.AddInt32(&numCalcsCreated, 1)
    buffer := make([]byte, 1024)
    return &buffer
}

func main() {
    // 创建实例
    bufferPool := &sync.Pool{
        New: createBuffer,
    }

    // 多 goroutine 并发测试
    numWorkers := 1024 * 1024
    var wg sync.WaitGroup
    wg.Add(numWorkers)

    for i := 0; i < numWorkers; i++ {
        go func() {
            defer wg.Done()
            // 申请一个 buffer 实例
            buffer := bufferPool.Get()
            //buffer := createBuffer()
            _ = buffer.(*[]byte)
            // 释放一个 buffer 实例
            defer bufferPool.Put(buffer)
        }()
    }
    wg.Wait()
    fmt.Printf("%d buffer objects were created.\n", numCalcsCreated)
}

flag

// 这些示例演示了 flag 包的更复杂用法。
package main

import (
 "errors"
 "flag"
 "fmt"
 "strings"
 "time"
)

// 示例 1: 一个名为 "species" 的单个字符串标志,默认值为 "gopher"。
var species = flag.String("species", "gopher", "我们正在研究的物种")

// 示例 2: 两个共享变量的标志,以便我们可以有一个简写。
// 初始化顺序是未定义的,因此确保两者使用相同的默认值。它们必须使用 init 函数进行设置。
var gopherType string

func init() {
 const (
  defaultGopher = "pocket"
  usage         = "gopher 的种类"
 )
 flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
 flag.StringVar(&gopherType, "g", defaultGopher, usage+" (简写)")
}

// 示例 3: 用户自定义的标志类型,一个持续时间切片。
type interval []time.Duration

// String 是 flag.Value 接口的方法,用于格式化标志的值。
// String 方法的输出将用于诊断。
func (i *interval) String() string {
 return fmt.Sprint(*i)
}

// Set 是 flag.Value 接口的方法,用于设置标志的值。
// Set 方法的参数是一个要解析以设置标志的字符串。
// 它是一个逗号分隔的列表,因此我们将其拆分。
func (i *interval) Set(value string) error {
 // 如果我们想允许标志被设置多次,以累积值,
 // 我们将删除这个 if 语句。
 // 那样就允许使用例如
 // -deltaT 10s -deltaT 15s
 // 和其他组合。
 if len(*i) > 0 {
  return errors.New("间隔标志已设置")
 }
 for _, dt := range strings.Split(value, ",") {
  duration, err := time.ParseDuration(dt)
  if err != nil {
   return err
  }
  *i = append(*i, duration)
 }
 return nil
}

// 定义一个累积持续时间的标志。由于它有一个特殊类型,
// 我们需要使用 Var 函数,因此在初始化期间创建标志。
var intervalFlag interval

func init() {
 // 将命令行标志与 intervalFlag 变量关联起来,并设置一个用法消息。
 flag.Var(&intervalFlag, "deltaT", "使用事件之间的间隔的逗号分隔列表")
}

func main() {
 // 上面声明的所有有趣的部分都在上面声明的变量中,
 // 但要使 flag 包看到那些定义的标志,
 // 通常在 main 的开头(不是 init!)调用:
 flag.Parse()
 //测试命令:
 //go run main.go --species cat -gopher_type groundhog --deltaT 10s,20s,30s
}

1、创建一个json数据的文件(users.json)

{
    "users": [
      {
        "name": "Aric",
        "age": 27,
        "social": {
          "zhihu": "https://zhihu.com",
          "weibo": "https://weibo.com"
        }
      },
      {
        "name": "gloria",
        "age": 22,
        "social": {
          "zhihu": "https://zhihu.com",
          "weibo": "https://weibo.com"
        }
      }
    ]
  }

2、构造users.json文件中的结构体Struct

我们需要根据users.json文件中的json格式,构造结构体。

可以利用网上开源工具:https://mholt.github.io/json-to-go/

这样我们构造结构体的时候,能达到事半功倍的效果。

type AutoGenerated struct {
 Users []struct {
  Name   string `json:"name"`
  Age    int    `json:"age"`
  Social struct {
   Zhihu string `json:"zhihu"`
   Weibo string `json:"weibo"`
  } `json:"social"`
 } `json:"users"`
}

我们得到了这样的json结构体,看上去比较复杂,不宜读。接下来,我们对结构体进行拆解:

type Users struct {
    Users []User
}
type User struct {
  Name   string `json:"name"`
  Age    int    `json:"age"`
  Social Social `json:"social"`
 }
type Social struct{
 Zhihu string `json:"zhihu"`
 Weibo string `json:"weibo"`

}

这样来看,整个结构体就相当的清晰了。

3、解析Json文件的数据

这里会用到json.Unmarshal()函数

package main

import (
 "encoding/json"
 "fmt"
 "io/ioutil"
 "os"
 "strconv"
)

type Users struct {
    Users []User
}
type User struct {
  Name   string `json:"name"`
  Age    int    `json:"age"`
  Social Social `json:"social"`
 }
type Social struct{
 Zhihu string `json:"zhihu"`
 Weibo string `json:"weibo"`

}

func main() {
 jsonFile, err := os.Open("user.json")
 if err != nil {
   fmt.Println(err)
 }
 fmt.Println("Successfully Opened users.json")
 defer jsonFile.Close()

 byteValue, err := ioutil.ReadAll(jsonFile)
 if err != nil {
  fmt.Println("Error reading file:", err)
  return
 }

 var users Users

 json.Unmarshal(byteValue, &users)

 for i := 0; i < len(users.Users); i++ {
   fmt.Println("User Name: " + users.Users[i].Name)
   fmt.Println("User Age: " + strconv.Itoa(users.Users[i].Age))
   fmt.Println("Zhihu Url: " + users.Users[i].Social.Zhihu)
   fmt.Println("Weibo Url: " + users.Users[i].Social.Weibo)


 b, err := json.MarshalIndent(users, "  ", "    ")
 fmt.Println(string(b))
 }
}