go源码解析-Println的故事 (2)

说到了rune就不得不说一下byte。同样,我们通过例子来看一下byte和rune的区别。

str := "hello 你好" fmt.Println([]rune(str)) // [104 101 108 108 111 32 20320 22909] fmt.Println([]byte(str)) // [104 101 108 108 111 32 228 189 160 229 165 189]

没错,区别就在类型上。rune是type rune = int32,一个字节;而byte是type byte = uint8,四个字节。实际上,golang中的字符串的底层是靠byte数组实现的。如果我们处理的数据中出现了中文字符,都可用rune来处理。例如。

str := "hello 你好" fmt.Println(len(str)) // 12 fmt.Println(len([]rune(str))) // 8 printArg具体实现 func (p *pp) printArg(arg interface{}, verb rune) { p.arg = arg p.value = reflect.Value{} if arg == nil { switch verb { case 'T', 'v': p.fmt.padString(nilAngleString) default: p.badVerb(verb) } return } switch verb { case 'T': p.fmt.fmtS(reflect.TypeOf(arg).String()) return case 'p': p.fmtPointer(reflect.ValueOf(arg), 'p') return } switch f := arg.(type) { case bool: p.fmtBool(f, verb) case float32: p.fmtFloat(float64(f), 32, verb) case float64: p.fmtFloat(f, 64, verb) case complex64: p.fmtComplex(complex128(f), 64, verb) case complex128: p.fmtComplex(f, 128, verb) case int: p.fmtInteger(uint64(f), signed, verb) case int8: p.fmtInteger(uint64(f), signed, verb) case int16: p.fmtInteger(uint64(f), signed, verb) case int32: p.fmtInteger(uint64(f), signed, verb) case int64: p.fmtInteger(uint64(f), signed, verb) case uint: p.fmtInteger(uint64(f), unsigned, verb) case uint8: p.fmtInteger(uint64(f), unsigned, verb) case uint16: p.fmtInteger(uint64(f), unsigned, verb) case uint32: p.fmtInteger(uint64(f), unsigned, verb) case uint64: p.fmtInteger(f, unsigned, verb) case uintptr: p.fmtInteger(uint64(f), unsigned, verb) case string: p.fmtString(f, verb) case []byte: p.fmtBytes(f, verb, "[]byte") case reflect.Value: if f.IsValid() && f.CanInterface() { p.arg = f.Interface() if p.handleMethods(verb) { return } } p.printValue(f, verb, 0) default: if !p.handleMethods(verb) { p.printValue(reflect.ValueOf(f), verb, 0) } } }

可以看到有一部分类型是通过反射获取到的,而大部分都是switch case出来的,并不是所有的类型都用的反射,相对的提高了效率。

例如,我们传入的是字符串。则接下来就会走到fmtString。

fmtString

从printArg中带来的参数有需要打印的字符串,以及rune类型的'v'。

func (p *pp) fmtString(v string, verb rune) { switch verb { case 'v': if p.fmt.sharpV { p.fmt.fmtQ(v) } else { p.fmt.fmtS(v) } case 's': p.fmt.fmtS(v) case 'x': p.fmt.fmtSx(v, ldigits) case 'X': p.fmt.fmtSx(v, udigits) case 'q': p.fmt.fmtQ(v) default: p.badVerb(verb) } }

p.fmt.sharpV在过程中没有被重新赋值,初始化的零值为false。所以下一步会进入fmtS。

fmtS func (f *fmt) fmtS(s string) { s = f.truncateString(s) f.padString(s) }

如果存在设定的精度,则truncate将字符串s截断为指定的精度。多用于需要输出数字时。

func (f *fmt) truncateString(s string) string { if f.precPresent { n := f.prec for i := range s { n-- if n < 0 { return s[:i] } } } return s }

而padString则将字符串s写入buffer中,最后调用io的包输出就好了。

free func (p *pp) free() { if cap(p.buf) > 64<<10 { return } p.buf = p.buf[:0] p.arg = nil p.value = reflect.Value{} p.wrappedErr = nil ppFree.Put(p) }

在前面讲过,要打印的时候,需要从临时对象池中获取一个对象,避免重复创建。而在此处,用完之后就需要通过Put函数将其放回临时对象池中,已备下次调用。

当然,并不是无限的将用过的变量放入对象池。如果缓冲区的大小超过了设定的阙值也就是65535,就无法再执行后续的操作了。

写在最后

源码是个技术活,其实这篇博客也算是一种尝试。最近看到一个图很有意思,跟大家分享一下。这张图讲的是你以为的看源码

go源码解析-Println的故事

然后是实际上的你看源码。

go源码解析-Println的故事

这张图特别形象。当你打算看一个开源项目的源码的时候,往往像一个饿了很多天没吃饭的人看到一桌美食一样,恨不得几分钟就把桌上的东西全部吃完,最后撑的半死,全部吐了出来;又或许像上面两张图里的水一样,接的太快,最后杯子里剩的反而越少。

相反,如果我们慢慢的品味美食,慢慢的去接水,肚子里的食物和水杯的水就一定会慢慢增加,直到适量为止。

我认为看源码,不应该一口吃成胖子,细水长流。从某一个小功能开始,慢慢的展开,这样才能了解到更多的东西。

参考:

Golang 源码剖析:fmt 标准库 --- Print* 是怎么样输出的?

Go语言字符类型(byte和rune)

go 的 [] rune 和 [] byte 区别

往期文章:

什么?你竟然还没有用这几个chrome插件?

手把手教你从零开始搭建SpringBoot后端项目框架

用go-module作为包管理器搭建go的web服务器

WebAssembly完全入门——了解wasm的前世今身

小强开饭店-从单体应用到微服务

相关:

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

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