输入与输出-fmt包
输入与输出
-
常用输出函数
Print、Printf、Println:直接输出内容
Sprint、Sprintf、Sprintln:生成内容并返回字符串
Fprint:将内容输出到一个io.Writer接口类型的变量,经常用于写入文件
Errorf:根据format参数生成格式化字符串并返回一个包含该字符串的错误
```go
fmt.Println("打开文件出错,err:", err)//输出带换行
s2 := fmt.Sprintf("name:%s,age:%d", name, age)//带格式生成并返回
fmt.Fprintf(fileObj, "往文件中写如信息:%s", name)//带格式写入文件
err := fmt.Errorf("这是一个错误")
```
-
常用占位符
占位符 说明 %v 值的默认格式表示 %+v 类似%v,但输出结构体时会添加字段名 %#v 值的golang语法表示 %T 打印值的类型 %% 百分号 %d 表示10进制数 %b 表示2进制数 %f 浮点数,有小数 %9.2f 宽度9,精度2 %e 科学计数法 %s 直接输出字符串或[]byte %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示 %p 指针,表示未16进制,并加上前缀0x -
常用输入函数
Scan、Scanf、Scanln:可以在程序运行过程中从标准输入获取用户的输入。
Scanln比较常用:在终端扫描标准输入,以空格分隔,直到换行结束扫描
```go
fmt.Scanln(&name, &age, &married)
fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
```
bufio.NewReader:获取完整输入内容
FScan、Fscanf、Fscanln:从文件中获取输入
Sscan、Sscanf、Sscanln:从字符串获取输入
时间与日期-time包
func timeDemo() {
now := time.Now() //获取当前时间
fmt.Printf("current time:%v\n", now)
year := now.Year() //年
month := now.Month() //月
day := now.Day() //日
hour := now.Hour() //小时
minute := now.Minute() //分钟
second := now.Second() //秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
timestamp1 := now.Unix() //时间戳
timestamp2 := now.UnixNano() //纳秒时间戳
fmt.Printf("current timestamp1:%v\n", timestamp1)
fmt.Printf("current timestamp2:%v\n", timestamp2)
timeObj := time.Unix(timestamp1, 0)//时间戳转换成时间对象,再通过类似以上当前时间转换成时间格式
}
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
var _time = 10 * time.Second //10秒
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
func main() {
now := time.Now()
later := now.Add(time.Hour) // 当前时间加1小时后的时间
beforer := now.Add(-time.Hour) // 当前时间减1小时后的时间
fmt.Println(later)
}
package main
import (
"fmt"
"time"
)
func main() {
/*
用sleep实现定时器
*/
fmt.Println(time.Now())
time.Sleep(time.Second)
fmt.Println(time.Now())
/*
用timer实现定时器
*/
timer := time.NewTimer(time.Second)
fmt.Println(<-timer.C)
/*
用after实现定时器
*/
fmt.Println(<-time.After(time.Second))
}
func tickDemo() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
//ticker := time.NewTicker(time.Second)
for i := range ticker {
fmt.Println(i)//每秒都会执行的任务
}
}
命令行参数解析-flag包
flag包是的golang开发命令行工具更为简单。
看一个完整示例,我们就更清楚flag的用途了:
执行命令时要求输入4个参数,并指定了参数的类型与默认值
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())
}
首先flag提供了命令行help功能,执行命令行会给出相应提示:
$ ./flag_demo -help
Usage of ./flag_demo:
-age int
年龄 (default 18)
-d duration
时间间隔
-married
婚否
-name string
姓名 (default "张三")
其次flag提供命令行参数parse解析能力:
注意:Args()\NArg()\NFlag()的含义
$ ./flag_demo -name pprof --age 28 -married=false -d=1h30m
pprof 28 false 1h30m0s
[]
0
4
当然如果我们仅仅只是需要简单命令行输入的参数,我们也可以简单的考虑os.Args来获取命令行参数。
os.Args是一个存储命令行参数的字符串切片,它的第一个元素是执行文件的名称,这和大部分语言命令行模式是类似的(python、php等)
日志-log包
官方标准简单log包,功能有限,更多可以实现流水账的日志记录,如果我们需要更多比如不同级别的日志记录,可以选择第三方日志库:logrus、zap等
//使用标准日志log,设置日志输出到xx.log文件,设置flags支持文件名、行号、日志前缀、时间格式
func main() {
logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("open log file failed, err:", err)
return
}
log.SetOutput(logFile)
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
log.SetPrefix("[JM]")
log.Println("这里记录一条日志。")
}
[JM]2020/01/14 15:32:45.431506 .../log_demo/main.go:13: 这里记录一条日志。
IO操作-os包
os包提供了Create、NewFile、Open、OpenFile、Remove方法
返回的文件对象,提供了读写方法,比如Write、WriteAt、WriteString、Read、ReadAt方法
package main
import (
"fmt"
"os"
)
func main() {
// 只读方式打开当前目录下的main.go文件
file, err := os.Open("./main.go")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
// 关闭文件
file.Close()
}
- 写文件
```go
file.WriteString("ab\n")
file.Write([]byte("cd\n"))
```
我们会更新一篇关于byte与string区别的文章
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 打开文件
file, err := os.Open("./xxx.txt")
if err != nil {
fmt.Println("open file err :", err)
return
}
defer file.Close()
// 定义接收文件读取的字节数组
var buf [128]byte
var content []byte
for {
n, err := file.Read(buf[:])
if err == io.EOF {
// 读取结束
break
}
if err != nil {
fmt.Println("read file err ", err)
return
}
content = append(content, buf[:n]...)
}
fmt.Println(string(content))
}
IO操作-bufio包与ioutil包
bufio包实现了带缓冲区的读写,是对文件读写的封装
前面说到开发者更喜欢使用更上层的读写方法,golang的bufio包除了实现带缓冲区的读写提高效率和稳定性外,还提供按行读方法,ioutil包提供了读取整个文件、写文件方法
bufio、ioutil包更多文件、目录读写详见官方标准库
strconv包
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)
i, err := strconv.ParseInt("-2", 10, 64)
u, err := strconv.ParseUint("2", 10, 64)
s1 := strconv.FormatBool(true)
s2 := strconv.FormatFloat(3.1415, 'E', -1, 64)
s3 := strconv.FormatInt(-2, 16)
s4 := strconv.FormatUint(2, 16)
strconv包中还有Append系列、Quote系列等函数。详细见官方标准库
模板-template包
html/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。
{{.}}
type UserInfo struct {
Name string
Gender string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象
tmpl, err := template.ParseFiles("./hello.html")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "枯藤",
Gender: "男",
Age: 18,
}
// 利用给定数据渲染模板,并将结果写入w
tmpl.Execute(w, user)
}
<body>
<p>Hello {{.Name}}</p>
<p>性别:{{.Gender}}</p>
<p>年龄:{{.Name}}</p>
</body>
{{/* a comment */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。
{{/* a comment */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止,就像这里表示的一样。
{{pipeline}}
pipeline的值的默认文本表示会被拷贝到输出里。
{{if pipeline}} T1 {{end}}
如果pipeline的值为empty,不产生输出,否则输出T1执行结果。不改变dot的值。
Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。
{{if pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值为empty,输出T0执行结果,否则输出T1执行结果。不改变dot的值。
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
用于简化if-else链条,else action可以直接包含另一个if;等价于:
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
{{range pipeline}} T1 {{end}}
pipeline的值必须是数组、切片、字典或者通道。
如果pipeline的值其长度为0,不会有任何输出;
否则dot依次设为数组、切片、字典或者通道的每一个成员元素并执行T1;
如果pipeline的值为字典,且键可排序的基本类型,元素也会按键的顺序排序。
{{range pipeline}} T1 {{else}} T0 {{end}}
pipeline的值必须是数组、切片、字典或者通道。
如果pipeline的值其长度为0,不改变dot的值并执行T0;否则会修改dot并执行T1。
{{template "name"}}
执行名为name的模板,提供给模板的参数为nil,如模板不存在输出为""
{{template "name" pipeline}}
执行名为name的模板,提供给模板的参数为pipeline的值。
{{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
{{eq arg1 arg2 arg3}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>tmpl test</title>
</head>
<body>
<h1>测试嵌套template语法</h1>
<hr>
{{template "ul.html"}}
<hr>
{{template "ol.html"}}
</body>
</html>
{{ define "ol.html"}}
<h1>这是ol.html</h1>
<ol>
<li>AA</li>
<li>BB</li>
<li>CC</li>
</ol>
{{end}}
func tmplDemo(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("./t.html", "./ul.html")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "枯藤",
Gender: "男",
Age: 18,
}
tmpl.Execute(w, user)
}
```
http包
Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现
resp, err := http.Get("http://www.baidu.com/")
resp, err := http.Post("http://www.9ong.com/post", "image/jpeg", &buf)
resp, err := http.PostForm("http://www.9ong.com/form",
url.Values{"key": {"Value"}, "id": {"123"}})
if err != nil {
// handle error
}
//使用完response后必须关闭回复的主体
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
apiUrl := "http://127.0.0.1:9090/get"
// URL param
data := url.Values{}
data.Set("name", "枯藤")
data.Set("age", "18")
u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Printf("parse url requestUrl failed,err:%v\n", err)
}
u.RawQuery = data.Encode() // URL encode
fmt.Println(u.String())
resp, err := http.Get(u.String())
if err != nil {
fmt.Println("post failed, err:%v\n", err)
return
}
// 表单数据
//contentType := "application/x-www-form-urlencoded"
//data := "name=jm&age=20"
// json
contentType := "application/json"
data := `{"name":"jm","age":20}`
resp, err := http.Post(url, contentType, strings.NewReader(data))
http.Get(url,json对象参数|结构体参数)
http.Post(url,options,data) //options负责设置http头等信息,data是参数json对象或结构体
```go
// http server
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9527", nil)
if err != nil {
fmt.Printf("http server failed, err:%v\n", err)
return
}
}
```
自定义server
```go
s := &http.Server{
Addr: ":9527",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
```
context
在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。
介绍goroutine时,我们看到范例并没有在main函数里使用context,goroutine也会自动退出。原因是只有一种情况正在运行的goroutine会因为其他goroutine的结束被终止,就是main函数的退出或程序停止执行.
sync.WaitGroup解决了协程协同同步完成问题,context主要为了解决协程协同取消问题
type Context interface {
Deadline() (deadline time.Time, ok bool) //返回当前Context被取消的时间,也就是完成工作的截止时间
Done() <-chan struct{} //返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个Channel
Err() error //返回当前Context结束的原因
Value(key interface{}) interface{} //从Context中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据
}
注意:以下介绍的都是函数,并不是创建后context上下问对像的方法,而是context包的函数
func gen(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // return结束该goroutine,防止泄露
case dst <- n:
n++
}
}
}()
return dst
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 当我们取完需要的整数后调用cancel
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
// 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
type TraceCode string
func worker(){
...
key := TraceCode("TRACE_CODE")
traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace code
...
}
func main(){
...
// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "2009")
wg.Add(1)
go worker(ctx)
...
}
golang的context设计,让我们明白一个道理,能简单处理好一个问题,就是好的解决方案,没有高贵之分。
json/xml
// 将struct、map转换成json 字符串
json.Marshal(struct|map)
//将json字符串转换成Person结构体
type Person struct{
...
}
jsonStr := []byte(`{"age":"18","name":"5lmh.com","marry":false}`)
var p Person
json.Unmarshal(jsonStr,&p)
xml.Marshal(struct|map)
xml.Unmarshal(xmlStr,&p)
MSGPack是二进制的json,性能更快,更省空间
需要安装第三方包:go get -u github.com/vmihailenco/msgpack
```go
msgpack.Marshal(struct|map)
msgpack.Unmarshal(msgpackbinary,&p)
```
reflect反射
反射是指在程序运行期对程序本身进行访问和修改的能力
reflect包封装了反射相关的方法:
获取类型信息:reflect.TypeOf,是静态的
获取值信息:reflect.ValueOf,是动态的
反射可以获取interface类型信息、获取值信息、修改值信息
反射可以查看结构体字段、类型、方法,修改结构体的值,调用方法
-
空接口结合反射
可以通过 空接口 可以表示任何参数,利用反射判断参数类型