go源码解析-Println的故事

Println函数接受参数a,其类型为…interface{}。用过Java的对这个应该比较熟悉,Java中也有…的用法。其作用是传入可变的参数,而interface{}类似于Java中的Object,代表任何类型。

所以,…interface{}转换成Java的概念,就是Object args ...。

Println函数中没有什么实现,只是return了Fprintln函数。

func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }

而在此处的…放在了参数的后面。我们知道...interface{}是代表可变参数,即函数可接收任意数量的参数,而且参数参数分开写的。

当我们再调用这个函数的时候,我们就没有必要再将参数一个一个传给被调用函数了,直接使用a…就可以达到相同的效果。

Fprintln

该函数接收参数os.Stdout.write,和需要打印的数据作为参数。

func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { p := newPrinter() p.doPrintln(a) n, err = w.Write(p.buf) p.free() return } sync.Pool

从广义上看,newPrinter申请了一个临时对象池。我们逐行来看newPrinter函数做了什么。

var ppFree = sync.Pool{ New: func() interface{} { return new(pp) }, } // newPrinter allocates a new pp struct or grabs a cached one. func newPrinter() *pp { p := ppFree.Get().(*pp) p.panicking = false p.erroring = false p.wrapErrs = false p.fmt.init(&p.buf) return p }

sync.Pool是go的临时对象池,用于存储被分配了但是没有被使用,但是未来可能会使用的值。以此来减少 GC的压力。

ppFree.Get

ppFree.Get()上有大量的注释。

Get selects an arbitrary item from the Pool, removes it from the Pool, and returns it to the caller.

Get may choose to ignore the pool and treat it as empty. Callers should not assume any relation between values passed to Put and the values returned by Get.

If Get would otherwise return nil and p.New is non-nil, Get returns the result of calling p.New.

麻瓜翻译一波。

Get会从临时对象池中任意选一个printer返回给调用者,并且将此项从对象池中移除。

Get也可以选择把临时对象池当成空的忽略。调用者不应该假设传递给Put方法的值和Get返回的值之间存在任何关系。

如果Get返回nil或者p,New就一定不为空。Get将返回调用p.New的结果。

上面提到的Put方法,作用是将对象加入到临时对象池中。

p := ppFree.Get().(*pp)下面的三个参数分别代表什么呢?

参数名 用途
p.panicking   由catchPanic设置,是为了避免在panic和recover中无限循环  
p.erroring   当打印错误的标识符的时候,防止调用handleMethods  
p.wrapErrs   当格式字符串包含了动词时的设置  
fmt.init   初始化 fmt 配置,会设置 buf 并且清空 fmtFlags 标志位  

然后就返回这个新建的printer给调用方。

doPrintln

接下来是doPrintln函数。

doPrintln就跟doPrint类似,但是doPrintln总是会在参数之间添加一个空格,并且在最后一个参数后面添加换行符。以下是两种输出方式的对比。

fmt.Println("test", "hello", "word") // test hello word fmt.Print("test", "hello", "word") // testhelloword%

看了样例,我们再具体看一下doPrintln的具体实现。

func (p *pp) doPrintln(a []interface{}) { for argNum, arg := range a { if argNum > 0 { p.buf.writeByte(' ') } p.printArg(arg, 'v') } p.buf.writeByte('\n') }

这个函数的思路很清晰。遍历所有传入的需要print的参数,在除了第一个 参数以外的所有参数的前面加上一个空格,写入buffer中。然后调用printArg函数,再将换行符写入buffer中。

writeByte的实现很简单,使用了append函数,将传入的参数,append到buffer中。

func (b *buffer) writeByte(c byte) { *b = append(*b, c) } printArg

从上可以看出,调用printArg函数的时候,传入了两个参数。

第一个是需要打印的参数,第二个则是verb,在doPrintln中我们传的是单引号的v。那么在go中的单引号和双引号有什么区别呢?下面我们通过一个表格来对比一下在不同的语言中,单引号和双引号的区别。

语言 单引号 双引号
Java   char   String  
JavaScript   string   string  
go   rune   String  
Python   string   string  
rune

那么rune到底是什么类型呢?rune是int32的别名,在任何方面等于int32相同,用于区分字符串和整形。其实现很简单,type rune = int32,rune常用来表示Unicode中的码点,其例子如下所示。

str := "hello 你好" fmt.Println([]rune(str)) // [104 101 108 108 111 32 20320 22909]

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwdgfd.html