《Go語(yǔ)言四十二章經(jīng)》第三十二章 fmt包與日志log包
作者:李驍
32.1 fmt包格式化I/O
上一章我們有提到fmt格式化I/O,這一章我們就詳細(xì)來(lái)說(shuō)說(shuō)。在fmt包,有關(guān)格式化輸入輸出的方法就兩大類:Scan 和 Print ,分別在scan.go 和 print.go 文件中。
print.go文件中定義了如下函數(shù):
func Printf(format string, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Sprintf(format string, a ...interface{}) string
func Print(a ...interface{}) (n int, err error)
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Sprint(a ...interface{}) string
func Println(a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Sprintln(a ...interface{}) string
這9個(gè)函數(shù),按照兩個(gè)維度來(lái)說(shuō)明,基本上可以說(shuō)明白了。當(dāng)然這兩個(gè)維度是我個(gè)人為了記憶而分,并不是官方的說(shuō)法。
一:如果把"Print"理解為核心關(guān)鍵字,那么后面跟的后綴有"f"和"ln"以及"",著重的是內(nèi)容輸出的結(jié)果;
如果后綴是"f", 則指定了format
如果后綴是"ln", 則有換行符
Println、Fprintln、Sprintln 輸出內(nèi)容時(shí)會(huì)加上換行符;
Print、Fprint、Sprint 輸出內(nèi)容時(shí)不加上換行符;
Printf、Fprintf、Sprintf 按照指定格式化文本輸出內(nèi)容。
二:如果把"Print"理解為核心關(guān)鍵字,那么前面的前綴有"F"和"S"以及"",著重的是輸出內(nèi)容的來(lái)源;
如果前綴是"F", 則指定了io.Writer
如果前綴是"S", 則是輸出到字符串
Print、Printf、Println 輸出內(nèi)容到標(biāo)準(zhǔn)輸出os.Stdout;
Fprint、Fprintf、Fprintln 輸出內(nèi)容到指定的io.Writer;
Sprint、Sprintf、Sprintln 輸出內(nèi)容到字符串。
scan.go文件中定義了如下函數(shù):
func Scanf(format string, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
func Scan(a ...interface{}) (n int, err error)
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Sscan(str string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
這9個(gè)函數(shù)可以掃描格式化文本以生成值。同樣也可以按照兩個(gè)維度來(lái)說(shuō)明。
一:如果把"Scan"理解為核心關(guān)鍵字,那么后面跟的后綴有"f"和"ln"以及"",著重的是輸入內(nèi)容的結(jié)果;
如果后綴是"f", 則指定了format
如果后綴是"ln", 則有換行符
Scanln、Fscanln、Sscanln 讀取到換行時(shí)停止,并要求一次提供一行所有條目;
Scan、Fscan、Sscan 讀取內(nèi)容時(shí)不關(guān)注換行;
Scanf、Fscanf、Sscanf 根據(jù)格式化文本讀取。
二:如果把"Scan"理解為核心關(guān)鍵字,那么前面的前綴有"F"和"S"以及"",著重的是輸入內(nèi)容的來(lái)源;
如果前綴是"F", 則指定了io.Reader
如果前綴是"S", 則是從字符串讀取
Scan、Scanf、Scanln 從標(biāo)準(zhǔn)輸入os.Stdin讀取文本;
Fscan、Fscanf、Fscanln 從指定的io.Reader接口讀取文本;
Sscan、Sscanf、Sscanln 從一個(gè)參數(shù)字符串讀取文本。
32.2 格式化verb應(yīng)用
在應(yīng)用上,我們主要講講格式化verb ,fmt包中格式化的主要功能函數(shù)都在format.go文件中。
我們先來(lái)了解下有哪些verb:
| 符號(hào) | 含義 |
|---|---|
| 通用: | |
| %v | 值的默認(rèn)格式表示。當(dāng)輸出結(jié)構(gòu)體時(shí),擴(kuò)展標(biāo)志(%+v)會(huì)添加字段名 |
| %#v | 值的Go語(yǔ)法表示 |
| %T | 值的類型的Go語(yǔ)法表示 |
| %% | 百分號(hào) |
| 符號(hào) | 含義 |
|---|---|
| 布爾值: | |
| %t | 單詞true或false |
| 符號(hào) | 含義 |
|---|---|
| 整數(shù): | |
| %b | 表示為二進(jìn)制 |
| %c | 該值對(duì)應(yīng)的unicode碼值 |
| %d | 表示為十進(jìn)制 |
| %o | 表示為八進(jìn)制 |
| %q | 該值對(duì)應(yīng)的單引號(hào)括起來(lái)的go語(yǔ)法字符字面值,必要時(shí)會(huì)采用安全的轉(zhuǎn)義表示 |
| %x | 表示為十六進(jìn)制,使用a-f |
| %X | 表示為十六進(jìn)制,使用A-F |
| %U | 表示為Unicode格式:U+1234,等價(jià)于"U+%04X" |
| 符號(hào) | 含義 |
|---|---|
| 浮點(diǎn)數(shù)、復(fù)數(shù)的兩個(gè)組分: | |
| %b | 無(wú)小數(shù)部分、二進(jìn)制指數(shù)的科學(xué)計(jì)數(shù)法,如-123456p-78;參見(jiàn)strconv.FormatFloat |
| %e | 科學(xué)計(jì)數(shù)法,如-1234.456e+78 |
| %E | 科學(xué)計(jì)數(shù)法,如-1234.456E+78 |
| %f | 有小數(shù)部分但無(wú)指數(shù)部分,如123.456 |
| %F | 等價(jià)于%f |
| %g | 根據(jù)實(shí)際情況采用%e或%f格式(以獲得更簡(jiǎn)潔、準(zhǔn)確的輸出) |
| %G | 根據(jù)實(shí)際情況采用%E或%F格式(以獲得更簡(jiǎn)潔、準(zhǔn)確的輸出) |
| 符號(hào) | 含義 |
|---|---|
| 字符串和[]byte: | |
| %s | 直接輸出字符串或者[]byte |
| %q | 該值對(duì)應(yīng)的雙引號(hào)括起來(lái)的Go語(yǔ)法字符串字面值,必要時(shí)會(huì)采用安全的轉(zhuǎn)義表示 |
| %x | 每個(gè)字節(jié)用兩字符十六進(jìn)制數(shù)表示(使用a-f) |
| %X | 每個(gè)字節(jié)用兩字符十六進(jìn)制數(shù)表示(使用A-F) |
| 符號(hào) | 含義 |
|---|---|
| 指針: | |
| %p | 表示為十六進(jìn)制,并加上前導(dǎo)的0x |
寬度通過(guò)一個(gè)緊跟在百分號(hào)后面的十進(jìn)制數(shù)指定,如果未指定寬度,則表示值時(shí)除必需之外不作填充。精度通過(guò)(可能有的)寬度后跟點(diǎn)號(hào)后跟的十進(jìn)制數(shù)指定。如果未指定精度,會(huì)使用默認(rèn)精度;如果點(diǎn)號(hào)后沒(méi)有跟數(shù)字,表示精度為0。舉例如下:
| 符號(hào) | 含義 |
|---|---|
| %f: | 默認(rèn)寬度,默認(rèn)精度 |
| %9f | 寬度9,默認(rèn)精度 |
| %.2f | 默認(rèn)寬度,精度2 |
| %9.2f | 寬度9,精度2 |
| %9.f | 寬度9,精度0 |
對(duì)于整數(shù),寬度和精度都設(shè)置輸出總長(zhǎng)度。采用精度時(shí)表示右對(duì)齊并用0填充,而寬度默認(rèn)表示用空格填充。
對(duì)于浮點(diǎn)數(shù),寬度設(shè)置輸出總長(zhǎng)度;精度設(shè)置小數(shù)部分長(zhǎng)度(如果有的話),除了%g/%G,此時(shí)精度設(shè)置總的數(shù)字個(gè)數(shù)。例如,對(duì)數(shù)字123.45,格式%6.2f 輸出123.45;格式%.4g輸出123.5。%e和%f的默認(rèn)精度是6,%g的默認(rèn)精度是可以將該值區(qū)分出來(lái)需要的最小數(shù)字個(gè)數(shù)。
對(duì)復(fù)數(shù),寬度和精度會(huì)分別用于實(shí)部和虛部,結(jié)果用小括號(hào)包裹。因此%f用于1.2+3.4i輸出(1.200000+3.400000i)。
其它flag:
| 符號(hào) | 含義 |
|---|---|
| + | 總是輸出數(shù)值的正負(fù)號(hào);對(duì)%q(%+q)會(huì)生成全部是ASCII字符的輸出(通過(guò)轉(zhuǎn)義); |
| - | 在輸出右邊填充空白而不是默認(rèn)的左邊(即從默認(rèn)的右對(duì)齊切換為左對(duì)齊); |
| # | 切換格式:八進(jìn)制數(shù)前加0(%#o),十六進(jìn)制數(shù)前加0x(%#x)或0X(%#X),指針去掉前面的0x(%#p); 對(duì)%q(%#q),如果strconv.CanBackquote返回真會(huì)輸出反引號(hào)括起來(lái)的未轉(zhuǎn)義字符串; 對(duì)%U(%#U),如果字符是可打印的,會(huì)在輸出Unicode格式、空格、單引號(hào)括起來(lái)的Go字面值; |
| ' ' | 對(duì)數(shù)值,正數(shù)前加空格而負(fù)數(shù)前加負(fù)號(hào);對(duì)字符串采用%x或%X時(shí)(% x或% X)會(huì)給各打印的字節(jié)之間加空格; |
| 0 | 使用0而不是空格填充,對(duì)于數(shù)值類型會(huì)把填充的0放在正負(fù)號(hào)后面; |
verb會(huì)忽略不支持的flag。
下面我們用一個(gè)程序來(lái)演示下:
package main
import (
"fmt"
"os"
)
type User struct {
name string
age int
}
var valF float64 = 32.9983
var valI int = 89
var valS string = "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
var valB bool = true
func main() {
p := User{"John", 28}
fmt.Printf("Printf struct %%v : %v\n", p)
fmt.Printf("Printf struct %%+v : %+v\n", p)
fmt.Printf("Printf struct %%#v : %#v\n", p)
fmt.Printf("Printf struct %%T : %T\n", p)
fmt.Printf("Printf struct %%p : %p\n", &p)
fmt.Printf("Printf float64 %%v : %v\n", valF)
fmt.Printf("Printf float64 %%+v : %+v\n", valF)
fmt.Printf("Printf float64 %%#v : %#v\n", valF)
fmt.Printf("Printf float64 %%T : %T\n", valF)
fmt.Printf("Printf float64 %%f : %f\n", valF)
fmt.Printf("Printf float64 %%4.3f : %4.3f\n", valF)
fmt.Printf("Printf float64 %%8.3f : %8.3f\n", valF)
fmt.Printf("Printf float64 %%-8.3f : %-8.3f\n", valF)
fmt.Printf("Printf float64 %%e : %e\n", valF)
fmt.Printf("Printf float64 %%E : %E\n", valF)
fmt.Printf("Printf int %%v : %v\n", valI)
fmt.Printf("Printf int %%+v : %+v\n", valI)
fmt.Printf("Printf int %%#v : %#v\n", valI)
fmt.Printf("Printf int %%T : %T\n", valI)
fmt.Printf("Printf int %%d : %d\n", valI)
fmt.Printf("Printf int %%8d : %8d\n", valI)
fmt.Printf("Printf int %%-8d : %-8d\n", valI)
fmt.Printf("Printf int %%b : %b\n", valI)
fmt.Printf("Printf int %%c : %c\n", valI)
fmt.Printf("Printf int %%o : %o\n", valI)
fmt.Printf("Printf int %%U : %U\n", valI)
fmt.Printf("Printf int %%q : %q\n", valI)
fmt.Printf("Printf int %%x : %x\n", valI)
fmt.Printf("Printf string %%v : %v\n", valS)
fmt.Printf("Printf string %%+v : %+v\n", valS)
fmt.Printf("Printf string %%#v : %#v\n", valS)
fmt.Printf("Printf string %%T : %T\n", valS)
fmt.Printf("Printf string %%x : %x\n", valS)
fmt.Printf("Printf string %%X : %X\n", valS)
fmt.Printf("Printf string %%s : %s\n", valS)
fmt.Printf("Printf string %%200s : %200s\n", valS)
fmt.Printf("Printf string %%-200s : %-200s\n", valS)
fmt.Printf("Printf string %%q : %q\n", valS)
fmt.Printf("Printf bool %%v : %v\n", valB)
fmt.Printf("Printf bool %%+v : %+v\n", valB)
fmt.Printf("Printf bool %%#v : %#v\n", valB)
fmt.Printf("Printf bool %%T : %T\n", valB)
fmt.Printf("Printf bool %%t : %t\n", valB)
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
程序輸出:
Printf struct %v : {John 28}
Printf struct %+v : {name:John age:28}
Printf struct %#v : main.User{name:"John", age:28}
Printf struct %T : main.User
Printf struct %p : 0xc000048400
Printf float64 %v : 32.9983
Printf float64 %+v : 32.9983
Printf float64 %#v : 32.9983
Printf float64 %T : float64
Printf float64 %f : 32.998300
Printf float64 %4.3f : 32.998
Printf float64 %8.3f : 32.998
Printf float64 %-8.3f : 32.998
Printf float64 %e : 3.299830e+01
Printf float64 %E : 3.299830E+01
Printf int %v : 89
Printf int %+v : 89
Printf int %#v : 89
Printf int %T : int
Printf int %d : 89
Printf int %8d : 89
Printf int %-8d : 89
Printf int %b : 1011001
Printf int %c : Y
Printf int %o : 131
Printf int %U : U+0059
Printf int %q : 'Y'
Printf int %x : 59
Printf string %v : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %+v : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %#v : "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
Printf string %T : string
Printf string %x : 476f20697320616e206f70656e20736f757263652070726f6772616d6d696e67206c616e67756167652074686174206d616b6573206974206561737920746f206275696c642073696d706c652c202072656c6961626c652c2020616e6420656666696369656e7420736f6674776172652e
Printf string %X : 476F20697320616E206F70656E20736F757263652070726F6772616D6D696E67206C616E67756167652074686174206D616B6573206974206561737920746F206275696C642073696D706C652C202072656C6961626C652C2020616E6420656666696369656E7420736F6674776172652E
Printf string %s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %200s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %-200s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %q : "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
Printf bool %v : true
Printf bool %+v : true
Printf bool %#v : true
Printf bool %T : bool
Printf bool %t : true
a string
an error
我們主要通過(guò)fmt.Printf來(lái)理解這些flag 的含義,這對(duì)我們今后的開(kāi)發(fā)有較強(qiáng)的實(shí)際作用。至于其他函數(shù),我就不一一舉例,有興趣可以進(jìn)一步研究。
32.3 日志log包
Go語(yǔ)言標(biāo)準(zhǔn)包中有日志功能,對(duì)應(yīng)在log包中。主要結(jié)構(gòu)體是:
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix to write at beginning of each line
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
在log包中通過(guò)New函數(shù)得到一個(gè)Logger結(jié)構(gòu)體指針,這個(gè)函數(shù)的三個(gè)參數(shù)分別是out,prefix,flag。pfefix可以指定日志信息的前綴,比如“[Debug]”等,一般根據(jù)實(shí)際需要定義,可根據(jù)情況隨時(shí)通過(guò)SetPrefix()函數(shù)修改。flag是日志的前綴信息(在prefix之后),包括可配置的時(shí)間格式等,一般默認(rèn)為L(zhǎng)stdFlags就可以了。out是日志輸出的目標(biāo),只要實(shí)現(xiàn)了io.Writer接口就可以作為out,log包中默認(rèn)指定stderr為out,所以log包默認(rèn)都是輸出到標(biāo)準(zhǔn)設(shè)備。
var std = New(os.Stderr, "", LstdFlags)
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
}
也可以按照上面的思路,把日志信息寫入到文件。
logfile, err := os.OpenFile("my.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatalln("fail to create log file!")
}
defer logfile.Close()
l:=log.New(logfile, "", log.LstdFlags)
l.Println("test")
num:=5
l.Println("test %d",num)
因?yàn)閘ogfile已經(jīng)實(shí)現(xiàn)了io.Writer,所以這里用做out,日志信息被寫入到文件。log的方法Printf()可以把信息按照一定格式來(lái)寫入。另外,在寫入日志信息時(shí)都有加入并發(fā)鎖,這是mu sync.Mutex的作用。
最后,log包的日志功能基本上能滿足一般的開(kāi)發(fā)需要,但相對(duì)還是比較簡(jiǎn)單,缺少日志分層控制,缺少對(duì)json格式的支持等,所以如果有需要靈活定制或大并發(fā)、大吞吐量的日志開(kāi)發(fā)需求,建議考慮使用其他方法或途徑來(lái)實(shí)現(xiàn)。
本書《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在簡(jiǎn)書同步地址: http://www.itdecent.cn/nb/29056963雖然本書中例子都經(jīng)過(guò)實(shí)際運(yùn)行,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請(qǐng)您指出;如有建議也歡迎交流。
聯(lián)系郵箱:roteman@163.com