内容主要来源:https://www.topgoer.cn/docs/golangstandard/golangstandard-1cmkseoplqqul
fmt库
主要用于格式化 IO
fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf. 格式“占位符”衍生自C,但比C更简单。
以下例子中用到的类型或变量定义:
type Website struct {
Name string
}
// 定义结构体变量
var site = Website{Name:"studygolang"}
Print函数
Print函数
的主要功能是输出字符到控制台,主要有3个函数
// \n是换行符
fmt.Print("控制台输出,不带换行 \n")
fmt.Println("控制台输出,带换行")
fmt.Printf("格式化输出, %s 字符串占位符 \n", "zhangsan")
格式化占位符
普通占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%v | 相应值的默认格式,在打印结构体时,“加号”标记(%+v)会添加字段名 | Printf(“%v”, site),Printf(“%+v”, site) | {studygolang},{Name:studygolang} |
%#v | 相应值的Go语法表示 | Printf(“#v”, site) | main.Website{Name:“studygolang”} |
%T | 相应值的类型的Go语法表示 | Printf(“%T”, site) | main.Website |
%% | 字面上的百分号,并非值的占位符 | Printf(“%%”) | % |
布尔占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%t | true 或 false | Printf(“%t”, true) | true |
整数占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 二进制 | Printf(“%b”, 5) | 101 |
%c | 相应Unicode码点所表示的字符 | Printf(“%c”, 0x4E2D) | 中 |
%d | 十进制 | Printf(“%d”, 0x12) | 18 |
%o | 八进制 | Printf(“%o”, 10) | 12 |
%q | 单引号围绕的字符字面值,由Go语法安全地转义 | Printf(“%q”, 0x4E2D) | ‘中’ |
%x | 十六进制,字母为小写 a-f | Printf(“%x”, 13) | d |
%X | 十六进制,字母为大写 A-F | Printf(“%x”, 13) | D |
%U | Unicode格式:U+1234,等同于 “U+%04X” | Printf(“%U”, 0x4E2D) | U+4E2D |
浮点数和复数的组成部分(实部和虚部)
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat的 ‘b’ 转换格式一致。例如 -123456p-78 | ||
%e | 科学计数法,例如 -1234.456e+78 | Printf(“%e”, 10.2) | 1.020000e+01 |
%E | 科学计数法,例如 -1234.456E+78 | Printf(“%E”, 10.2) | 1.020000E+01 |
%f | 有小数点而无指数,例如 123.456 | Printf(“%f”, 10.2) | 10.200000 |
%g | 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf(“%g”, 10.20) | 10.2 |
%G | 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf(“%G”, 10.20+2i) | (10.2+2i) |
字符串与字节切片
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%s | 输出字符串表示(string类型或[]byte) | Printf(“%s”, []byte(“Go语言中文网”)) | Go语言中文网 |
%q | 双引号围绕的字符串,由Go语法安全地转义 | Printf(“%q”, “Go语言中文网”) | “Go语言中文网” |
%x | 十六进制,小写字母,每字节两个字符 | Printf(“%x”, “golang”) | 676f6c616e67 |
%X | 十六进制,大写字母,每字节两个字符 | Printf(“%X”, “golang”) | 676F6C616E67 |
指针
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%p | 十六进制表示,前缀 0x | Printf(“%p”, &site) | 0x4f57f0 |
这里没有 ‘u’ 标记。若整数为无符号类型,他们就会被打印成无符号的。类似地,这里也不需要指定操作数的大小(int8,int64)。
宽度与精度的控制格式以 Unicode 码点为单位。(这点与C的 printf 不同,它以字节数为单位)二者或其中之一均可用字符 ‘*’ 表示,此时它们的值会从下一个操作数中获取,该操作数的类型必须为 int。
对数值而言,宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。 但对于 %g/%G 而言,精度为所有数字的总数。例如,对于123.45,格式 %6.2f 会打印123.45,而 %.4g 会打印123.5。%e 和 %f 的默认精度为6;但对于 %g 而言,它的默认精度为确定该值所必须的最小位数。
对大多数的值而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。对字符串而言,精度为输出的最大字符数,如果必要的话会直接截断。
其它标记
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
+ | 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。 | Printf(“%+q”, “中文”) | “\u4e2d\u6587” |
- | 在右侧而非左侧填充空格(左对齐该区域) | ||
# | 备用格式:为八进制添加前导 0(%#o),为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x;如果可能的话,%q(%#q)会打印原始(即反引号围绕的)字符串;如果是可打印字符,%U(%#U)会写出该字符的Unicode 编码形式(如字符 x 会被打印成 U+0078 ‘x’) | Printf(“%#U”, ‘中’) | U+4E2D ‘中’ |
’ ’ | (空格)为数值中省略的正负号留出空白(% d);以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开 | ||
0 | 填充前导的0而非空格;对于数字,这会将填充移到正负号之后 |
标记有时会被占位符忽略,所以不要指望它们。例如十进制没有备用格式,因此 %#d 与 %d 的行为相同。
对于每一个 Printf 类的函数,都有一个 Print 函数,该函数不接受任何格式化,它等价于对每一个操作数都应用 %v。另一个变参函数 Println 会在操作数之间插入空白,并在末尾追加一个换行符。
不考虑占位符的话,如果操作数是接口值,就会使用其内部的具体值,而非接口本身。 因此:
var i interface{} = 23
fmt.Printf("%v\n", i)
会打印 23。
Fprint函数
将内容输出到一个io.Writer
接口类型的变量中,同样有3个函数
package main
import (
"fmt"
"os"
)
func main() {
// os.Stdout 代表控制台
fmt.Fprint(os.Stdout, "向标准控制台输出")
fmt.Fprintln(os.Stdout, "向标准控制台输出")
fmt.Fprintf(os.Stdout, "向标准控制台输出 %s", "zhangsan")
// 创建文件
// 第一个参数代表文件名
// 第二个参数代表文件可追加、可读可写、可创建
// 第三个参数代表文件权限,os.ModePerm就是0777,0代表8进制,777代表文件权限,-rwxrwxrwx
file, _ := os.OpenFile("test.txt", os.O_APPEND|os.O_RDWR|os.O_CREATE, os.ModePerm)
fmt.Fprint(file, "向test.txt文件追加内容")
}
Sprint
对传入的数据进行拼接返回新的字符串
package main
import "fmt"
func main() {
s1 := "hello"
num := 3
str1 := fmt.Sprint(s1, "+", num)
fmt.Println(str1)
fmt.Printf("%T\n", str1)
str2 := fmt.Sprintf("%s:%d", s1, num)
fmt.Println(str2)
fmt.Printf("%T\n", str2)
}
hello+3
string
hello:3
string
Errorf
根据format参数生成格式化字符串并返回一个包含该字符串的错误
package main
import "fmt"
func main() {
msg := "密码错误"
code := 1234
err := fmt.Errorf("错误信息:%s,错误代码:%d", msg, code)
fmt.Println(err)
}
Scan
从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符
package main
import "fmt"
func main() {
var (
name string
age int
)
// 从控制台中输入数据,以空格分隔
fmt.Scan(&name, &age)
fmt.Printf("name=%s,age=%d\n", name, age)
}
os库
os库主要用来操作文件,在创建文件时,需要指明文件的权限,go语言中为uint32类型,一般使用0777这样的形式表示
- 0代表8进制
- 777代表文件权限,
-rwxrwxrwx
权限项 | 文件类型 | 读 | 写 | 执行 | 读 | 写 | 执行 | 读 | 写 | 执行 |
---|---|---|---|---|---|---|---|---|---|---|
字符表示 | (d|l|c|s|p) | r | w | x | r | w | x | r | w | x |
数字表示 | 4 | 2 | 1 | 4 | 2 | 1 | 4 | 2 | 1 | |
权限分配 | 文件所有者 | 文件所有者 | 文件所有者 | 文件所属组用户 | 文件所属组用户 | 文件所属组用户 | 其他用户 | 其他用户 | 其他用户 |
第一位:
-
: 代表这是一个普通文件(regular)
d
: 目录文件(directory)
l
: 链接文件(link)
b
: 块设备文件(block)
c
: 字符设备文件(character)
s
: 套接字文件(socket)
p
: 管道文件(pipe)
一共有三组rwx
- 第一组:该文件拥有者的权限
- 第二组:该文件拥有者所在组的其他成员对该文件的操作权限
- 第三组:其他用户组的成员对该文件的操作权限
Create
创建一个文件,默认权限为0666(可读可写,不可执行),如果已存在则覆盖原文件
package main
import (
"fmt"
"os"
)
func main() {
// 创建一个文件
file, err := os.Create("a.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file.Name())
}
Mkdir
创建目录,主要有两个函数
Mkdir
创建单级目录MkdirAll
创建多级目录
package main
import (
"fmt"
"os"
)
func main() {
// 创建单级目录
err := os.Mkdir("test", os.ModePerm)
if err != nil {
fmt.Println(err)
}
// 创建多级目录
err = os.MkdirAll("a/b/c", os.ModePerm)
if err != nil {
fmt.Println(err)
}
}
Remove
删除目录或文件,主要有两个函数
Remove
删除一个空目录或文件RemoveAll
强制删除目录及目录内的所有文件
package main
import (
"fmt"
"os"
)
func main() {
// 只能删除空目录或文件,如果目录内有文件则无法删除
err := os.Remove("a")
if err != nil {
fmt.Println(err)
}
// 强制删除所有东西
err = os.RemoveAll("a")
if err != nil {
fmt.Println(err)
}
}
Getwd
获取工作目录
package main
import (
"fmt"
"os"
)
func main() {
// 获取当前项目的目录,不是该go文件的目录
dir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
fmt.Println(dir)
}
TempDir
获得临时目录
package main
import (
"fmt"
"os"
)
func main() {
// 获得临时目录
dir := os.TempDir()
fmt.Println(dir)
}
Chmod
修改文件属性
package main
import (
"fmt"
"os"
)
func main() {
// 更改文件属性,只可执行
err := os.Chmod("test.txt", 0111)
if err != nil {
fmt.Println(err)
}
}
文件
文件的打开模式
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
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 // 如果可能,打开时清空文件
)
文件打开
os.Open
用于打开文件,主要有两个函数
package main
import (
"fmt"
"os"
)
func main() {
// 打开文件,只能读,不能写
// 如果文件不存在则报错
file, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(file.Name())
// 以指定的形式打开文件
file1, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(file1.Name())
}
其实os.Create
等价于:OpenFile(name, O_RDWF|O_CREATE|O_TRUNK, 0666)
文件读取
// 获取文件信息,里面有文件的名称,大小,修改时间等
func (f *File) Stat() (fi FileInfo, err error)
// 从文件中一次性读取b大小的数据,当读到文件结尾时,返回一个EOF错误
func (f *File) Read(b []byte) (n int, err error)
// 从文件中指定的位置(off)一次性读取b大小的数据
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
// 读取目录并返回排好序的文件以及子目录名切片
func ReadDir(name string) ([]DirEntry, error)
//Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
stat
获取文件信息
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
if err != nil {
fmt.Println(err)
return
}
// 获取文件的信息
fileInfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(fileInfo)
}
read
读取文件内容
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
if err != nil {
fmt.Println(err)
return
}
// 容器,保存文件的内容
var body []byte
for {
// 创建一个缓冲区,储存每次读到文件的内容
buf := make([]byte, 4)
// 读取内容到缓冲区中
n, err := file.Read(buf)
// 代表文件读完
if err == io.EOF {
break
}
// 把每次缓冲区的内容读取到容器中
body = append(body, buf[:n]...)
}
fmt.Println("文件内容:", string(body))
}
注意:读取文件时需要指明每次读取的长度,而容器的长度通常不会去指定,所以定义一个临时的缓冲区储存每次读取的内容在追加到容器中
readAt
从第n位开始读
//Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
if err != nil {
fmt.Println(err)
return
}
// 光标定位到第3个字符后
file.Seek(3, 0)
var body []byte
// 从第4个字符开始读
for {
buf := make([]byte, 4)
n, err := file.Read(buf)
if err == io.EOF {
break
}
body = append(body, buf[:n]...)
}
fmt.Println(string(body))
}
文件写
//Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func (f *File) Write(b []byte) (n int, err error)
//WriteString类似Write,但接受一个字符串参数。
func (f *File) WriteString(s string) (ret int, err error)
//WriteAt在指定的位置(相对于文件开始位置)写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
if err != nil {
fmt.Println(err)
return
}
b := []byte("hello world")
file.Write(b)
}
如果文件设置了os.O_APPEND
,则在文件最后追加内容,如果没有,则从头覆盖掉相同长度的内容,原文件后续内容不变
14. 进程相关
// 让当前程序以给出的状态码(code)退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
func Exit(code int)
// 获取调用者的用户id
func Getuid() int
// 获取调用者的有效用户id
func Geteuid() int
// 获取调用者的组id
func Getgid() int
// 获取调用者的有效组id
func Getegid() int
// 获取调用者所在的所有组的组id
func Getgroups() ([]int, error)
// 获取调用者所在进程的进程id
func Getpid() int
// 获取调用者所在进程的父进程的进程id
func Getppid() int
func osInfo() {
// 获得当前正在运行的进程id
fmt.Println("---------")
fmt.Printf("os.Getpid(): %v\n", os.Getpid())
// 父id
fmt.Printf("os.Getppid(): %v\n", os.Getppid())
// 设置新进程的属性
attr := &os.ProcAttr{
// files指定新进程继承的活动文件对象
// 前三个分别为,标准输入、标准输出、标准错误输出
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
// 新进程的环境变量
Env: os.Environ(),
}
// 开始一个新进程
p, err := os.StartProcess("c:\\windows\\system32\\notepad.exe", []string{"c:\\windows\\system32\\notepad.exe", "d:\\test.txt"}, attr)
if err != nil {
fmt.Println(err)
}
fmt.Println(p)
fmt.Println("进程ID:", p.Pid)
// 通过进程ID查找进程
p2, _ := os.FindProcess(p.Pid)
fmt.Println(p2)
// 等待10秒,执行函数
time.AfterFunc(time.Second*10, func() {
// 向p进程发出退出信号
p.Signal(os.Kill)
})
// 等待进程p的退出,返回进程状态
ps, _ := p.Wait()
fmt.Println(ps.String())
}
type Signal
type Signal interface {
String() string
Signal() // 用来区分其他实现了Stringer接口的类型
}
Signal代表一个操作系统信号。一般其底层实现是依赖于操作系统的:在Unix中,它是syscall.Signal类型。
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)
仅有的肯定会被所有操作系统提供的信号,Interrupt(中断信号)和Kill(强制退出信号)。
var (
ErrInvalid = errors.New("invalid argument")
ErrPermission = errors.New("permission denied")
ErrExist = errors.New("file already exists")
ErrNotExist = errors.New("file does not exist")
)
一些可移植的、共有的系统调用错误。
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
Stdin、Stdout和Stderr是指向标准输入、标准输出、标准错误输出的文件描述符。
var Args []string
Args保管了命令行参数,第一个是程序名。
14.1 Signal
func main() {
go a()
go b()
ch := make(chan os.Signal)
signal.Notify(ch, os.Kill, os.Interrupt)
c := <-ch
log.Println(c)
}
func b() {
fmt.Println("b")
}
func a() {
fmt.Println("a")
}
15. 环境相关
// 获取主机名
func Hostname() (name string, err error)
// 获取某个环境变量
func Getenv(key string) string
// 设置一个环境变量,失败返回错误,经测试当前设置的环境变量只在 当前进程有效(当前进程衍生的所有的go程都可以拿到,子go程与父go程的环境变量可以互相获取);进程退出消失
func Setenv(key, value string) error
// 删除当前程序已有的所有环境变量。不会影响当前电脑系统的环境变量,这些环境变量都是对当前go程序而言的
func Clearenv()
func osEnv() {
// 获得所有环境变量
s := os.Environ()
fmt.Printf("s: %v\n", s)
// 获得某个环境变量
s2 := os.Getenv("GOPATH")
fmt.Printf("s2: %v\n", s2)
// 设置环境变量
os.Setenv("env1", "env1")
s2 = os.Getenv("aaa")
fmt.Printf("s2: %v\n", s2)
fmt.Println("--------------")
// 查找
s3, b := os.LookupEnv("env1")
fmt.Printf("b: %v\n", b)
fmt.Printf("s3: %v\n", s3)
// 清空环境变量
// os.Clearenv()
}
time库
Time类型
-
Time代表一个纳秒精度的时间点。
-
程序中应使用Time类型值来保存和传递时间,而不能用指针。就是说,表示时间的变量和字段,应为time.Time类型,而不是*time.Time.类型。
-
一个Time类型值可以被多个goroutine同时使用。
-
时间点可以使用
Before
、After
和Equal
方法进行比较。 -
Sub
方法让两个时间点相减,生成一个Duration类型值(代表时间段)。 -
Add
方法给一个时间点加上一个时间段,生成一个新的Time类型时间点。 -
Time零值代表时间点January 1, year 1, 00:00:00.000000000 UTC。因为本时间点一般不会出现在使用中,
IsZero
方法提供了检验时间是否显式初始化的一个简单途径。 -
每一个时间都具有一个地点信息(及对应地点的时区信息),当计算时间的表示格式时,如
Format
、Hour
和Year
等方法,都会考虑该信息。 -
Local
、UTC
和In
方法返回一个指定时区(但指向同一时间点)的Time。修改地点/时区信息只是会改变其表示;不会修改被表示的时间点,因此也不会影响其计算。
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间
now := time.Now()
fmt.Println("time:", now)
year := now.Year() //年
month := now.Month() //月
day := now.Day() //日
hour := now.Hour() //小时
minute := now.Minute() //分钟
second := now.Second() //秒
//02d输出的整数不足两位 用0补足
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
时间戳
时间戳是自1970年1月1日0时0分0秒至当前时间的总毫秒数。它也被称为Unix时间戳(UnixTimestamp)。
这里指的是UTC时间,比北京时间晚8个小时。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Unix()) // 秒
fmt.Println(now.UnixMilli()) // 毫秒
fmt.Println(now.UnixMicro()) // 微秒
fmt.Println(now.UnixNano()) // 纳秒
}
Parse解析时间
func Parse(layout, value string) (Time, error)
解析一个格式化的时间字符串并返回它代表的时间,如果缺少表示时区的信息,Parse会将时区设置为UTC。
func ParseInLocation(layout, value string, loc *Location) (Time, error)
ParseInLocation类似Parse但有两个重要的不同之处。第一,当缺少时区信息时,Parse将时间解释为UTC时间,而ParseInLocation将返回值的Location设置为loc;第二,当时间字符串提供了时区偏移量信息时,Parse会尝试去匹配本地时区,而ParseInLocation会去匹配loc。
layout
的时间必须是"2006-01-02 15:04:05"
这个时间,当然格式不一定是这个,时间一定得是,这是go诞生的时间
package main
import (
"fmt"
"time"
)
func main() {
time1, err := time.Parse("2006-01-02 15:04:05", "2000-01-01 01:01:01")
if err != nil {
fmt.Println(err)
}
fmt.Printf("time类型:%T\n", time1)
fmt.Println(time1)
// 根据时区解析时间
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
}
time2, err := time.ParseInLocation("2006-01-02 15:04:05", "2000-01-01 01:01:01", loc)
fmt.Printf("time类型:%T\n", time2)
fmt.Println(time2)
}
Format格式化时间
func (t Time) Format(layout string) string
Format根据layout指定的格式返回t代表的时间点的格式化文本表示。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// 24小时制
time1 := now.Format("2006-01-02 15:04:05")
fmt.Println(time1)
// 12小时制
time2 := now.Format("2006-01-02 03:04:05 PM")
fmt.Println(time2)
}
time.Unix()
func Unix(sec int64, nsec int64) Time
Unix创建一个本地时间,对应sec和nsec表示的Unix时间(从January 1, 1970 UTC至该时间的秒数和纳秒数)。
nsec的值在[0, 999999999]范围内是合法的。
func timestampDemo2(timestamp int64) {
timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
fmt.Println(timeObj)
year := timeObj.Year() //年
month := timeObj.Month() //月
day := timeObj.Day() //日
hour := timeObj.Hour() //小时
minute := timeObj.Minute() //分钟
second := timeObj.Second() //秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
时间间隔
time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration表示一段时间间隔,可表示的最长时间段大约290年。
time包中定义的时间间隔类型的常量如下:
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
例如:time.Duration表示1纳秒,time.Second表示1秒。
时间计算
Add
新增时间,返回新增后的时间
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05"))
// 当前时间增加5个小时
now = now.Add(5 * time.Hour)
fmt.Println(now.Format("2006-01-02 15:04:05"))
}
Sub
求两个时间之间的差值
func (t Time) Sub(u Time) Duration
返回一个时间段t-u。如果结果超出了Duration可以表示的最大值/最小值,将返回最大值/最小值。
要获取时间点t-d(d为Duration),可以使用t.Add(-d)
。
注意:时间会受到时区的影响,time.Now()默认是本地时间,time.Parse()解析的时间默认是UTC时间
package main
import (
"fmt"
"time"
)
func main() {
time1 := time.Now()
fmt.Println(time1.Format("2006-01-02 15:04:05"))
// 当前时间增加5个小时
time2 := time1.Add(5 * time.Hour)
fmt.Println(time2.Format("2006-01-02 15:04:05"))
// 时间的差值
fmt.Println(time2.Sub(time1))
}
7.3 Equal
func (t Time) Equal(u Time) bool
判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较。
本方法和用t==u不同,这种方法还会比较地点和时区信息。
7.4 Before
func (t Time) Before(u Time) bool
如果t代表的时间点在u之前,返回真;否则返回假。
7.5 After
func (t Time) After(u Time) bool
如果t代表的时间点在u之后,返回真;否则返回假。
定时器
使用time.Tick(时间间隔)来设置定时器,定时器的本质上是一个通道(channel)
func tickDemo() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
for i := range ticker {
fmt.Println(i)//每秒都会执行的任务
}
}
time.AfterFunc(time.Second*10, func() {
fmt.Println("10秒后执行")
})
package main
import (
"fmt"
"sync"
"time"
)
/**
*ticker只要定义完成,从此刻开始计时,不需要任何其他的操作,每隔固定时间都会触发。
*timer定时器,是到固定时间后会执行一次
*如果timer定时器要每隔间隔的时间执行,实现ticker的效果,使用 func (t *Timer) Reset(d Duration) bool
*/
func main() {
var wg sync.WaitGroup
wg.Add(2)
//NewTimer 创建一个 Timer,它会在最少过去时间段 d 后到期,向其自身的 C 字段发送当时的时间
timer1 := time.NewTimer(2 * time.Second)
//NewTicker 返回一个新的 Ticker,该 Ticker 包含一个通道字段,并会每隔时间段 d 就向该通道发送当时的时间。它会调
//整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。如果d <= 0会触发panic。关闭该 Ticker 可
//以释放相关资源。
ticker1 := time.NewTicker(2 * time.Second)
go func(t *time.Ticker) {
defer wg.Done()
for {
<-t.C
fmt.Println("get ticker1", time.Now().Format("2006-01-02 15:04:05"))
}
}(ticker1)
go func(t *time.Timer) {
defer wg.Done()
for {
<-t.C
fmt.Println("get timer", time.Now().Format("2006-01-02 15:04:05"))
//Reset 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期。如果调用时t
//还在等待中会返回真;如果 t已经到期或者被停止了会返回假。
t.Reset(2 * time.Second)
}
}(timer1)
wg.Wait()
}
log库
golang内置了log包,实现简单的日志服务。通过调用log包的函数,可以实现简单的日志打印功能。
log包定义了Logger类型,该类型提供了一些格式化输出的方法。
log包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用。
1说明
log包中有3个系列的日志打印函数,分别为print系列
、panic系列
、fatal系列
.
函数系列 | 说明 | 作用 |
---|---|---|
Print/Printf/Println | 单纯打印日志 | |
Panic | Panic/Panicf/Panicln | 打印日志,抛出panic异常 |
Fatal | Fatal/Fatalf/Fatalln | 打印日志,强制结束程序(os.Exit(1)),defer函数不会执行 |
打印日志
package main
import "log"
func main() {
log.Print("这是一条日志")
log.Printf("这是格式化日志:%d", 100) // 格式化输出
log.Println("这是日志")
}
Panic
打印出日志并且抛出panic异常,在panic之后声明的代码将不会执行。
package main
import (
"fmt"
"log"
)
func main() {
defer fmt.Println("发生了 panic错误!")
log.Print("这是日志")
log.Panic("panic错误")
fmt.Println("运行结束。。。")
}
Fatal
将日志内容打印输出,接着调用系统的os.Exit(1)
接口,强制退出程序并返回状态1,但是有一点需要注意的是,由于直接调用系统os接口退出,defer函数不会调用。
package main
import (
"fmt"
"log"
)
func main() {
// 不会执行
defer fmt.Println("defer。。。")
log.Print("这是日志")
log.Fatal("fatal日志")
// 不会执行
fmt.Println("运行结束。。。")
}
日志配置
默认情况下log只会打印出时间,但是实际情况下我们还需要获取文件名,行号等信息,log包提供给我们定制的接口。
方法 | 说明 |
---|---|
func Flags() int | 返回标准log输出配置 |
func SetFlags(flag int) | 设置标准log输出配置 |
const (
// 控制输出日志信息的细节,不能控制输出的顺序和格式。
Ldate = 1 << iota // 日期
Ltime // 时间
Lmicroseconds // 微秒级别的时间
Llongfile // 文件全路径名+行号
Lshortfile // 文件名+行号
LUTC // 使用UTC时间
LstdFlags = Ldate | Ltime // 标准logger的初始值
)
package main
import (
"fmt"
"log"
)
func main() {
i := log.Flags()
fmt.Println(i)
log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
log.Print("这是日志")
}
前缀配置
方法 | 说明 |
---|---|
func Prefix() string | 返回日志的前缀配置 |
func SetPrefix(prefix string) | 设置日志前缀 |
package main
import (
"fmt"
"log"
)
func main() {
s := log.Prefix()
fmt.Println(s)
log.SetPrefix("[MyLog] ")
s = log.Prefix()
fmt.Println(s)
log.Print("这是日志")
}
输出到文件
log包提供了func SetOutput(w io.Writer)
函数,将日志输出到文件中。
package main
import (
"log"
"os"
)
func main() {
f, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)
if err != nil {
log.Fatal(err)
}
log.SetOutput(f)
log.Print("这是文件日志")
}
自定义Logger
log包中提供了func New(out io.Writer, prefix string, flag int) *Logger
函数来实现自定义logger。
package main
import (
"log"
"os"
)
var logger *log.Logger
func init() {
logFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
logger = log.New(logFile, "[Mylog]", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
logger.Println("自定义logger")
}
errors库
errors包实现了操作错误的函数。go语言使用error类型来返回函数执行过程中遇到的错误,如果返回的error值为nil,则表示未遇到错误,否则error会返回一个字符串,用于说明遇到了什么错误。
type error interface {
Error() string
}
error不一定表示一个错误,它可以表示任何信息,比如io包中就用error类型的io.EOF
表示数据读取结束,而不是遇到了什么错误。
Errors.New()
func New(text string) error
package main
import (
"errors"
"log"
)
func main() {
str := ""
err := IsEmpty(str)
if err != nil {
log.Fatal(err)
}
}
func IsEmpty(str string) error {
if len(str) == 0 {
return errors.New("字符串不能为空")
}
return nil
}
自定义错误
可以灵活定义错误类型,只要实现Error接口下的Error() string
方法即可
package main
import (
"fmt"
"log"
)
func main() {
str := ""
err := Empty(str)
if err != nil {
log.Fatal(err)
}
}
type MyError struct {
code int
msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误信息:%s,状态码:%d", e.msg, e.code)
}
func Empty(str string) error {
if len(str) == 0 {
return &MyError{
code: 400,
msg: "字符串不能为空",
}
}
return nil
}
bytes库
bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。
常用函数
转换
函数 | 说明 |
---|---|
func ToUpper(s []byte) []byte | 将 s 中的所有字符修改为大写格式返回。 |
func ToLower(s []byte) []byte | 将 s 中的所有字符修改为小写格式返回 |
func ToTitle(s []byte) []byte | 将 s 中的所有字符修改为标题格式返回 |
func ToUpperSpecial(_case unicode.SpecialCase, s []byte) []byte | 使用指定的映射表将 s 中的所有字符修改为大写格式返回 |
func ToLowerSpecial(_case unicode.SpecialCase, s []byte) []byte | 使用指定的映射表将 s 中的所有字符修改为小写格式返回 |
func ToTitleSpecial(_case unicode.SpecialCase, s []byte) []byte | 使用指定的映射表将 s 中的所有字符修改为标题格式返回 |
func Title(s []byte) []byte | 将 s 中的所有单词的首字符修改为 Title 格式返回。(缺点:不能很好的处理以 Unicode 标点符号分隔的单词。) |
strings库
主要用于操作字符串
string 类型可以看成是一种特殊的 slice 类型,因此获取长度可以用内置的函数 len;同时支持 切片 操作
字符串比较
// Compare 函数,用于比较两个字符串的大小,如果两个字符串相等,返回为 0。如果 a 小于 b ,返回 -1 ,反之返回 1 。不推荐使用这个函数,直接使用 == != > < >= <= 等一系列运算符更加直观。
func Compare(a, b string) int
// EqualFold 函数,计算 s 与 t 忽略字母大小写后是否相等。
func EqualFold(s, t string) bool
package main
import (
"fmt"
"strings"
)
func main() {
var str1 string = "hello"
var str2 string = "world"
// 比较字符串
fmt.Println(strings.Compare(str1, str2)) // -1
fmt.Println(strings.Compare(str1, str1)) // 0
fmt.Println(strings.Compare(str2, str1)) // 1
fmt.Println(strings.EqualFold("HELLO", "hello")) // true
fmt.Println(strings.EqualFold("HEllo", "hello")) // true
fmt.Println(strings.EqualFold("HEllo", "world")) // false
}
是否存在某个字符或子串
主要有3个函数
// 子串 substr 在 s 中,返回 true
func Contains(s, substr string) bool
// chars 中任何一个 Unicode 代码点在 s 中,返回 true
func ContainsAny(s, chars string) bool
// Unicode 代码点 r 在 s 中,返回 true
func ContainsRune(s string, r rune) bool
package main
import (
"fmt"
"strings"
)
func main() {
str1 := "hello,zhangsan"
fmt.Println(strings.Contains(str1, "hello")) // true
fmt.Println(strings.Contains(str1, "hezhang")) // false
fmt.Println(strings.Contains(str1, "he zhang")) // false
fmt.Println("-------------------------------------")
fmt.Println(strings.ContainsAny(str1, "h")) // true
fmt.Println(strings.ContainsAny(str1, "b")) // false
fmt.Println(strings.ContainsAny(str1, "h b")) // true
fmt.Println(strings.ContainsAny(str1, ", b")) // true
fmt.Println(strings.ContainsAny(str1, "hb")) // true
fmt.Println("-------------------------------------")
fmt.Println(strings.ContainsRune(str1, 'h')) // true
}
-
strings.ContainsAny()
中第二个参数 chars 中任意一个字符(Unicode Code Point)如果在第一个参数 s 中存在,则返回 true -
strings.ContainsRune()
函数只能用于检查字符串是否包含单个字符,不能用于检查字符串中是否包含多个字符,第二个参数为 rune 类型,需要用单引号''
来定义
子串出现次数
func Count(s, sep string) int
在 Count 的实现中,处理了几种特殊情况,属于字符匹配预处理的一部分。这里要特别说明一下的是当 sep 为空时,Count 的返回值是:utf8.RuneCountInString(s) + 1
Count 是计算子串在字符串中出现的无重叠的次数
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Count("cheese", "e")) // 3
fmt.Println(len("你好张三")) // 12
fmt.Println(strings.Count("你好张三", "")) // 5
fmt.Println(strings.Count("fivevevev", "vev")) // 2
}
字符串分割为[]string
该包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。
Fields 和 FieldsFunc
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields 用一个或多个连续的空格分隔字符串 s,返回子字符串的数组(slice)。如果字符串 s 只包含空格,则返回空列表 ([]string 的长度为 0)。其中,空格的定义是 unicode.IsSpace
常见间隔符包括:’\t’, ‘\n’, ‘\v’, ‘\f’, ‘\r’, ‘ ‘, U+0085 (NEL), U+00A0 (NBSP)
由于是用空格分隔,因此结果中不会含有空格或空子字符串
FieldsFunc 用这样的 Unicode 代码点 c 进行分隔:满足 f© 返回 true。该函数返回[]string。如果字符串 s 中所有的代码点 (unicode code points) 都满足 f© 或者 s 是空,则 FieldsFunc 返回空 slice。
package main
import (
"fmt"
"strings"
)
func main() {
s := strings.Fields(" hello world zhangsan")
fmt.Println(len(s)) // 3
fmt.Println(s) // [hello world zhangsan]
// 以字符c进行分割
s = strings.FieldsFunc("abcabcabcab", func(r rune) bool {
return r == 'c'
})
fmt.Println(s) // [ab ab ab ab]
}
Split 和 SplitAfter、 SplitN 和 SplitAfterN
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
这四个函数都是通过 sep 进行分割,返回[]string。如果 sep 为空,相当于分成一个个的 UTF-8 字符,如 Split("abc","")
,得到的是[a b c]。
Split(s, sep) 和 SplitN(s, sep, -1) 等价;SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。
Split 和 SplitAfter 的区别是后者会保留要分割的 sep 字符
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Split("hello,world,zhang", ",")) // [hello world zhang]
fmt.Println(strings.SplitAfter("hello,world,zhang", ","))// [hello, world, zhang]
}
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数,当 n < 0 时,返回所有的子字符串;当 n == 0 时,返回的结果是 nil;当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割,比如:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.SplitN("hello,world,zhang", ",", -1)) // [hello world zhang]
fmt.Println(strings.SplitN("hello,world,zhang", ",", 0)) // []
fmt.Println(strings.SplitN("hello,world,zhang", ",", 1)) // [hello,world,zhang]
fmt.Println(strings.SplitN("hello,world,zhang", ",", 2)) // [hello world,zhang]
fmt.Println(strings.SplitN("hello,world,zhang", ",", 3)) // [hello world zhang]
}
官方例子,注意结果
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
结果:
["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[""]
字符串是否有某个前缀或后缀
// s 中是否以 prefix 开始
func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}
// s 中是否以 suffix 结尾
func HasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
如果 prefix 或 suffix 为 “” , 返回值总是 true。
示例:
fmt.Println(strings.HasPrefix("Gopher", "Go"))
fmt.Println(strings.HasPrefix("Gopher", "C"))
fmt.Println(strings.HasPrefix("Gopher", ""))
fmt.Println(strings.HasSuffix("Amigo", "go"))
fmt.Println(strings.HasSuffix("Amigo", "Ami"))
fmt.Println(strings.HasSuffix("Amigo", ""))
输出结果:
true
false
true
true
false
true
字符或子串在字符串中出现的位置
// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int
// 在 s 中查找字节 c 的第一次出现,返回第一次出现的索引
func IndexByte(s string, c byte) int
// chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int
// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int
// 有三个对应的查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
Contain 相关的函数内部调用的是响应的 Index 函数
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "hello,world,zhang"
fmt.Println(strings.Index(s, "o")) // 4
fmt.Println(strings.Index(s, "ll")) // 2
fmt.Println(strings.IndexAny(s, "l w")) // 2
fmt.Println(strings.IndexAny(s, "b w")) // 6
fmt.Println(strings.LastIndex(s, "o")) // 7
fmt.Println(strings.LastIndexAny(s, "b n")) // 15
// 看字符串中是否有汉字
han := func(c rune) bool {
return unicode.Is(unicode.Han, c) // 汉字
}
fmt.Println(strings.IndexFunc("Hello, world", han))
fmt.Println(strings.IndexFunc("Hello, 世界", han))
}
字符串 JOIN 操作
将字符串数组(或 slice)连接起来可以通过 Join 实现
func Join(a []string, sep string) string
package main
import (
"fmt"
"strings"
)
func main() {
s := []string{"hello", "world", "zhang"}
fmt.Println(strings.Join(s, ",")) // hello,world,zhang
fmt.Println(strings.Join(s, " ")) // hello world zhang
}
字符串重复几次
func Repeat(s string, count int) string
将 s 重复 count 次,如果 count 为负数或返回值长度 len(s)*count 超出 string 上限会导致 panic
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println("ba" + strings.Repeat("na", 2))
}
字符串子串替换
进行字符串替换时,考虑到性能问题,能不用正则尽量别用,应该用这里的函数。
// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string
// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string
使用示例:
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo"))
输出:
oinky oinky oink
moo moo moo
moo moo moo
如果我们希望一次替换多个,比如我们希望替换 This is <b>HTML</b>
中的 <
和 >
为 <
和 >
,可以调用上面的函数两次。但标准库提供了另外的方法进行这种替换。
大小写转换
func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
大小写转换包含了 4 个相关函数,ToLower,ToUpper 用于大小写转换。ToLowerSpecial,ToUpperSpecial 可以转换特殊字符的大小写。
举个例子:
fmt.Println(strings.ToLower("HELLO WORLD"))
fmt.Println(strings.ToLower("Ā Á Ǎ À"))
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "壹"))
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "HELLO WORLD"))
fmt.Println(strings.ToLower("Önnek İş"))
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş"))
fmt.Println(strings.ToUpper("hello world"))
fmt.Println(strings.ToUpper("ā á ǎ à"))
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "一"))
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "hello world"))
fmt.Println(strings.ToUpper("örnek iş"))
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş"))
输出结果:
hello world
ā á ǎ à
壹
hello world
önnek iş
önnek iş
HELLO WORLD
Ā Á Ǎ À // 汉字拼音有效
一 // 汉字无效
HELLO WORLD
ÖRNEK IŞ
ÖRNEK İŞ // 有细微差别
修剪
// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string
// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string
// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string
// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string
// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string
// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string
// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string
// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string
// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string
包含了 9 个相关函数用于修剪字符串。
举个例子:
x := "!!!@@@你好,!@#$ Gophers###$$$"
fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))
fmt.Println(strings.TrimPrefix(x, "!"))
fmt.Println(strings.TrimSuffix(x, "$"))
f := func(r rune) bool {
return !unicode.Is(unicode.Han, r) // 非汉字返回 true
}
fmt.Println(strings.TrimFunc(x, f))
fmt.Println(strings.TrimLeftFunc(x, f))
fmt.Println(strings.TrimRightFunc(x, f))
输出结果:
你好,!@#$ Gophers
你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers
Hello, Gophers
!!@@@你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers###$$
你好
你好,!@#$ Gophers###$$$
!!!@@@你好
strconv库
主要用于字符串和基本数据类型之间转换
这里的基本数据类型包括:布尔、整型(包括有 / 无符号、二进制、八进制、十进制和十六进制)和浮点型等。
strconv 包转换错误处理
由于将字符串转为其他数据类型可能会出错,strconv 包定义了两个 error 类型的变量:ErrRange 和 ErrSyntax。其中,ErrRange 表示值超过了类型能表示的最大范围,比如将 “128” 转为 int8 就会返回这个错误;ErrSyntax 表示语法错误,比如将 “” 转为 int 类型会返回这个错误。
然而,在返回错误的时候,不是直接将上面的变量值返回,而是通过构造一个 NumError 类型的 error 对象返回。NumError 结构的定义如下:
// A NumError records a failed conversion.
type NumError struct {
Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
Num string // the input
Err error // the reason the conversion failed (ErrRange, ErrSyntax)
}
可见,该结构记录了转换过程中发生的错误信息。该结构不仅包含了一个 error 类型的成员,记录具体的错误信息,而且它自己也实现了 error 接口:
func (e *NumError) Error() string {
return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error()
}
包的实现中,定义了两个便捷函数,用于构造 NumError 对象:
func syntaxError(fn, str string) *NumError {
return &NumError{fn, str, ErrSyntax}
}
func rangeError(fn, str string) *NumError {
return &NumError{fn, str, ErrRange}
}
在遇到 ErrSyntax 或 ErrRange 错误时,通过上面的函数构造 NumError 对象。
字符串和整型之间的转换
字符串转为整型
func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (n uint64, err error)
func Atoi(s string) (i int, err error)
其中,Atoi 是 ParseInt 的便捷版,内部通过调用 ParseInt(s, 10, 0) 来实现的;ParseInt 转为有符号整型;ParseUint 转为无符号整型,着重介绍 ParseInt。
参数 base 代表字符串按照给定的进制进行解释。一般的,base 的取值为 2~36,如果 base 的值为 0,则会根据字符串的前缀来确定 base 的值:”0x” 表示 16 进制; “0” 表示 8 进制;否则就是 10 进制。
参数 bitSize 表示的是整数取值范围,或者说整数的具体类型。取值 0、8、16、32 和 64 分别代表 int、int8、int16、int32 和 int64。
这里有必要说一下,当 bitSize==0 时的情况。
Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目前的实现是,32 位系统占 4 个字节;64 位系统占 8 个字节。当 bitSize==0 时,应该表示 32 位还是 64 位呢?这里没有利用 runtime.GOARCH 之类的方式,而是巧妙的通过如下表达式确定 intSize:
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Println(strconv.ParseInt("100", 10, 64)) // 100 nil
fmt.Println(strconv.ParseInt("100", 2, 64)) // 4 nil
fmt.Println(strconv.ParseInt("100", 8, 64)) // 64 nil
}
整型转为字符串
实际应用中,我们经常会遇到需要将字符串和整型连接起来,在 Java 中,可以通过操作符 “+” 做到。不过,在 Go 语言中,你需要将整型转为字符串类型,然后才能进行连接。
func FormatUint(i uint64, base int) string // 无符号整型转字符串
func FormatInt(i int64, base int) string // 有符号整型转字符串
func Itoa(i int) string
其中,Itoa 内部直接调用 FormatInt(i, 10) 实现的。base 参数可以取 2~36(0-9,a-z)。
转换的基本原理(以 10 进制的 127 转 string 为例) :
oconst digits = "0123456789abcdefghijklmnopqrstuvwxyz"
u := uint64(127)
var a [65]byte
i := len(a)
b := uint64(base)
for u >= b {
i--
a[i] = digits[uintptr(u%b)]
u /= b
}
i--
a[i] = digits[uintptr(u)]
return string(a[1:])
即将整数每一位数字对应到相应的字符,存入字符数组中,最后字符数组转为字符串即为结果。
具体实现时,当 base 是 2 的幂次方时,有优化处理(移位和掩码);十进制也做了优化。
标准库还提供了另外两个函数:AppendInt 和 AppendUint,这两个函数不是将整数转为字符串,而是将整数转为字符数组 append 到目标字符数组中。(最终,我们也可以通过返回的 []byte 得到字符串)
除了使用上述方法将整数转为字符串外,经常见到有人使用 fmt 包来做这件事。如:
fmt.Sprintf("%d", 127)
那么,这两种方式我们该怎么选择呢?我们主要来考察一下性能。
startTime := time.Now()
for i := 0; i < 10000; i++ {
fmt.Sprintf("%d", i)
}
fmt.Println(time.Now().Sub(startTime))
startTime := time.Now()
for i := 0; i < 10000; i++ {
strconv.Itoa(i)
}
fmt.Println(time.Now().Sub(startTime))
我们分别循环转换了 10000 次。Sprintf 的时间是 3.549761ms,而 Itoa 的时间是 848.208us,相差 4 倍多。
Sprintf 性能差些可以预见,因为它接收的是 interface,需要进行反射等操作。个人建议使用 strconv 包中的方法进行转换。
注意:别想着通过 string(65) 这种方式将整数转为字符串,这样实际上得到的会是 ASCCII 值为 65 的字符,即 ‘A’。
字符串和布尔值之间的转换
Go 中字符串和布尔值之间的转换比较简单,主要有三个函数:
// 接受 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False 等字符串;
// 其他形式的字符串会返回错误
func ParseBool(str string) (value bool, err error)
// 直接返回 "true" 或 "false"
func FormatBool(b bool) string
// 将 "true" 或 "false" append 到 dst 中
// 这里用了一个 append 函数对于字符串的特殊形式:append(dst, "true"...)
func AppendBool(dst []byte, b bool)
字符串和浮点数之间的转换
类似的,包含三个函数:
func ParseFloat(s string, bitSize int) (f float64, err error)
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int)
函数的命名和作用跟上面讲解的其他类型一致。
关于 FormatFloat 的 fmt 参数, 在第一章第三节格式化 IO 中有详细介绍。而 prec 表示有效数字(对 fmt=’b’ 无效),对于 ‘e’, ‘E’ 和 ‘f’,有效数字用于小数点之后的位数;对于 ‘g’ 和 ‘G’,则是所有的有效数字。例如:
strconv.FormatFloat(1223.13252, 'e', 3, 32) // 结果:1.223e+03
strconv.FormatFloat(1223.13252, 'g', 3, 32) // 结果:1.22e+03
由于浮点数有精度的问题,精度不一样,ParseFloat 和 FormatFloat 可能达不到互逆的效果。如:
s := strconv.FormatFloat(1234.5678, 'g', 6, 64)
strconv.ParseFloat(s, 64)
另外,fmt=’b’ 时,得到的字符串是无法通过 ParseFloat 还原的。
特别地(不区分大小写),+inf/inf,+infinity/infinity,-inf/-infinity 和 nan 通过 ParseFloat 转换分别返回对应的值(在 math 包中定义)。
同样的,基于性能的考虑,应该使用 FormatFloat 而不是 fmt.Sprintf。
flag库
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。
os.Args
如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用os.Args来获取命令行参数。
package main
import (
"fmt"
"os"
)
//os.Args demo
func main() {
//os.Args是一个[]string
if len(os.Args) > 0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v\n", index, arg)
}
}
}
将上面的代码执行go build -o "args_demo"编译之后,执行:
$ ./args_demo a b c d
args[0]=./args_demo
args[1]=a
args[2]=b
args[3]=c
args[4]=d
os.Args是一个存储命令行参数的字符串切片,它的第一个元素是执行文件的名称。
flag包概述
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”。 |
定义 flag 的两种方式
- flag.Xxx(),其中
Xxx
可以是 Int、String 等;返回一个相应类型的指针,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
- flag.XxxVar(),将 flag 绑定到一个变量上,如:
var flagvar int
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
自定义 Value
另外,还可以创建自定义 flag,只要实现 flag.Value 接口即可(要求 receiver
是指针),这时候可以通过如下方式定义该 flag:
flag.Var(&flagVal, "name", "help message for flagname")
例如,解析我喜欢的编程语言,我们希望直接解析到 slice 中,我们可以定义如下 Value:
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) Get() interface{} { return []string(*s) }
func (s *sliceValue) String() string { return strings.Join([]string(*s), ",") }
之后可以这么使用:
var languages []string
flag.Var(newSliceValue([]string{}, &languages), "slice", "I like programming `languages`")
这样通过 -slice "go,php"
这样的形式传递参数,languages
得到的就是 [go, php]
。
flag 中对 Duration 这种非基本类型的支持,使用的就是类似这样的方式。
解析 flag
通过以上两种方法定义好命令行flag参数后,需要通过调用flag.Parse()
来对命令行参数进行解析。
支持的命令行参数格式有以下几种:
- -flag xxx (使用空格,一个-符号)
- –flag xxx (使用空格,两个-符号)
- -flag=xxx (使用等号,一个-符号)
- –flag=xxx (使用等号,两个-符号)
其中,布尔类型的参数必须使用等号的方式指定。
Flag解析在第一个非flag参数(单个”-“不是flag参数)之前停止,或者在终止符”–“之后停止。
示例
定义flag_demo.go
package main
import (
"flag"
"fmt"
"time"
)
func main() {
//定义命令行参数方式1
var name string
var age int
var married bool
var delay time.Duration
flag.StringVar(&name, "name", "张三", "姓名")
flag.IntVar(&age, "age", 18, "年龄")
flag.BoolVar(&married, "married", false, "婚否")
flag.DurationVar(&delay, "d", 0, "延迟的时间间隔")
//解析命令行参数
flag.Parse()
fmt.Println(name, age, married, delay)
//返回命令行参数后的其他参数
fmt.Println(flag.Args())
//返回命令行参数后的其他参数个数
fmt.Println(flag.NArg())
//返回使用的命令行参数个数
fmt.Println(flag.NFlag())
}
执行go build flag_demo.go
命令
命令行参数使用提示:
$ ./flag_demo -help
Usage of ./flag_demo:
-age int
年龄 (default 18)
-d duration
时间间隔
-married
婚否
-name string
姓名 (default "张三")
正常使用命令行flag参数:
$ ./flag_demo -name pprof --age 28 -married=false -d=1h30m
pprof 28 false 1h30m0s
[]
0
4
使用非flag命令行参数:
$ ./flag_demo a b c
张三 18 false 0s
[a b c]
3
0