标准库 - fmt/format.go 解读
Posted GoLove
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了标准库 - fmt/format.go 解读相关的知识,希望对你有一定的参考价值。
// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // go/src/fmt/format.go // version 1.7 package fmt import ( "strconv" "unicode/utf8" ) // 用于进制转换 const ( ldigits = "0123456789abcdefx" udigits = "0123456789ABCDEFX" ) // 作为参数使用,方便阅读者明白传入的参数是什么含义 const ( signed = true unsigned = false ) // 用于记录“占位符”中是否指定了相应的标记 // 将标志放在一个单独的结构体中便于清理 type fmtFlags struct { widPresent bool // 宽度值 precPresent bool // 精度值 minus bool // - 标记 plus bool // + 标记 sharp bool // # 标记 space bool // 空格标记 zero bool // 0 标记 // 对于特殊格式 %+v 和 %#v,单独设置 plusV/sharpV 标志。 plusV bool // +v sharpV bool // #v } // fmt 是一个原始的格式化器,用于 Printf 等函数中。 // 它将格式化结果输出到一个缓冲区中,缓冲区必须单独指定。 type fmt struct { buf *buffer // *[]byte fmtFlags // 结构体,定义了许多标志 wid int // 宽度 prec int // 精度 // intbuf 足够存储二进制格式的 int64 intbuf [68]byte } // 复位所有标志 func (f *fmt) clearflags() { f.fmtFlags = fmtFlags{} } // 结构体初始化(必须提供缓冲区) func (f *fmt) init(buf *buffer) { f.buf = buf f.clearflags() } // 将数据写入缓冲区(仅写入 n 字节的填充字符) func (f *fmt) writePadding(n int) { if n <= 0 { return } buf := *f.buf // 先做容量判断,如果容量不足,则进行扩充 oldLen := len(buf) newLen := oldLen + n if newLen > cap(buf) { // 默认将容量翻倍,但要保证能够容纳写入的 // 内容,所以要 +n buf = make(buffer, cap(buf)*2+n) copy(buf, *f.buf) } // 确定要写入的字符 padByte := byte(‘ ‘) if f.zero { padByte = byte(‘0‘) } // 开始写入 padding := buf[oldLen:newLen] for i := range padding { padding[i] = padByte } // buf 有可能进行了扩充(地址发生了改变),所以要写回去 *f.buf = buf[:newLen] } // 将数据写入缓冲区(写入字节切片,同时处理宽度填充) func (f *fmt) pad(b []byte) { // 如果没有宽度值,则直接写入 if !f.widPresent || f.wid == 0 { f.buf.Write(b) return } // 宽度值是以字符作为单位,而不是字节(没考虑汉字的感受)。 width := f.wid - utf8.RuneCount(b) if !f.minus { // 指定了 ‘-‘ 标记,在左边填充 f.writePadding(width) f.buf.Write(b) } else { // 未指定 ‘-‘ 标记,在右边填充 f.buf.Write(b) f.writePadding(width) } } // 将数据写入缓冲区(写入字符串,同时处理宽度填充) func (f *fmt) padString(s string) { // 如果没有宽度值,则直接写入 if !f.widPresent || f.wid == 0 { f.buf.WriteString(s) return } // 宽度值是以字符作为单位,而不是字节(没考虑汉字的感受)。 width := f.wid - utf8.RuneCountInString(s) if !f.minus { // 指定了 ‘-‘ 标记,在左边填充 f.writePadding(width) f.buf.WriteString(s) } else { // 未指定 ‘-‘ 标记,在右边填充 f.buf.WriteString(s) f.writePadding(width) } } // 将数据写入缓冲区(布尔值) func (f *fmt) fmt_boolean(v bool) { if v { f.padString("true") } else { f.padString("false") } } // 将数据写入缓冲区(写入 Unicode 码点) // Unicode 码点格式为 "U+FFFF",如果指定了 # 标记,则格式为 "U+FFFF ‘相应字符‘"。 func (f *fmt) fmt_unicode(u uint64) { // 临时缓冲区,容量为 68 字节,如果容量不够用,可以进行进行扩充。 buf := f.intbuf[0:] // 1、判断容量是否够用 // 如果没有指定精度,那么容量肯定够用,因为即便使用 %#U 对 -1 进行格式化, // 所需的最大存储空间也只有 18 字节("U+FFFFFFFFFFFFFFFF"),没有超过 68。 // 只有指定了过大的精度之后(比如 100),才有可能超出容量范围。 // 所以下面对容量的判断,只和精度有关。 // 默认精度为 4(如果码点长度不足 4 位,则添加前导 0,比如 U+0065,如果 // 码点长度超过精度值,则忽略精度) prec := 4 // 如果指定了精度,则判断格式化结果是否会超出 buf 范围 if f.precPresent && f.prec > 4 { prec = f.prec // 估算所需的存储空间:"U+"、精度、" ‘"、相应字符、"‘"。 width := 2 + prec + 2 + utf8.UTFMax + 1 if width > len(buf) { buf = make([]byte, width) } } // 开始格式化 // 从右向左进行格式化更容易一些 i := len(buf) // 2、处理 # 标记 // 在最后添加 ‘相应字符‘。 // 前提是数值必须在 Unicode 码点范围内,并且字符可打印 if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { i-- buf[i] = ‘\‘‘ i -= utf8.RuneLen(rune(u)) utf8.EncodeRune(buf[i:], rune(u)) i-- buf[i] = ‘\‘‘ i-- buf[i] = ‘ ‘ } // 3、将 u 格式化为十六进制数值。 for u >= 16 { i-- // 确定 buf 的写入下标 buf[i] = udigits[u&0xF] // 与 1111 相与,获取十六进制的个位数,然后查表取字符。 prec-- // 精度被用掉一个 u >>= 4 // 丢弃十六进制的个位数 } i-- // 处理最后一个个位数 buf[i] = udigits[u] prec-- // 4、处理精度信息(添加前导 0) for prec > 0 { i-- buf[i] = ‘0‘ prec-- } // 5、处理前导 "U+"。 i-- buf[i] = ‘+‘ i-- buf[i] = ‘U‘ // 6、处理宽度信息(填充空格) oldZero := f.zero f.zero = false f.pad(buf[i:]) f.zero = oldZero } // 将数据写入缓冲区(整数:包括有符号和无符号,处理进制转换) // u:要格式化的整数。base:进制。isSigned:是否有符号。digits:进制转换表格 // 十六进制大小写通过 digits 参数确定 func (f *fmt) fmt_integer(u uint64, base int, isSigned bool, digits string) { // 1、修正参数 // 如果确实有符号,则将 u 中存储的负数变为正数 negative := isSigned && int64(u) < 0 if negative { u = -u // 相当于 -int64(u),类型转换不会改变值内容,所以减哪个都一样,-u 省一步转换操作 } // 临时缓冲区,容量为 68 字节,如果容量不够用,可以进行进行扩充。 buf := f.intbuf[0:] // 2、判断容量是否够用 if f.widPresent || f.precPresent { // 需要额外的 3 个字节来存放带符号的 "0x"。 // 这里为了提高效率,直接将宽度和精度相加(实际上宽度和精度是重叠的), // 因为在大多数情况下,相加的结果都不会太大,width > len(buf) 的情况很 // 少出现,很少会重新分配内存,所以这里只是为了确保安全而已。相反,如果 // 用判断语句计算准确的 width 值,反而会降低效率。 width := 3 + f.wid + f.prec if width > len(buf) { buf = make([]byte, width) } } // 3、确定精度信息 // 注:有两种方式为整数添加前导零:%.3d 或 %08d, // 如果同时使用了这两种写法,那么 0 标记将被忽略,会使用空格实现宽度填充。 // 默认精度为 0,如果指定了精度,则使用指定的精度。 prec := 0 if f.precPresent { prec = f.prec // 如果精度指定为 0,值也指定为 0,则表示无内容,只用空格进行填充。 // 例如:fmt.Printf("%#8.d", 0) if prec == 0 && u == 0 { oldZero := f.zero f.zero = false f.writePadding(f.wid) f.zero = oldZero return } // 如果没有指定精度,但指定了 0 标记和宽度, // 则将宽度值转换为精度值,由精度处理函数去处理前导 0。 } else if f.zero && f.widPresent { prec = f.wid // 如果指定了符号位,则留下一个符号位 if negative || f.plus || f.space { prec-- } } // 从右到左进行格式化更容易一些 i := len(buf) // 4、开始编码 // 使用常数进行除法和取模操作可以更有效率。 // case 顺序按照使用频繁度排序。 switch base { case 10: for u >= 10 { i-- // 确定缓冲区的写入下标 next := u / 10 //去掉个位数 // 这里用了 - 和 * 求余数,而没有用 %,是不是比 % 更快一些? buf[i] = byte(‘0‘ + u - next*10) // 获取个位数 u = next } case 16: for u >= 16 { i-- // 确定缓冲区的写入下标 buf[i] = digits[u&0xF] // 与 1111 相与,获取十六进制的个位数,然后查表取字符。 u >>= 4 // 丢弃十六进制的个位数 } case 8: for u >= 8 { i-- // 确定缓冲区的写入下标 buf[i] = byte(‘0‘ + u&7) // 与 111 相与,获取八进制的个位数,然后转换为字符。 u >>= 3 // 丢弃八进制的个位数 } case 2: for u >= 2 { i-- // 确定缓冲区的写入下标 buf[i] = byte(‘0‘ + u&1) // 与 1 相与,获取二进制的个位数,然后转换为字符。 u >>= 1 // 丢弃二进制的个位数 } default: panic("fmt: unknown base; can‘t happen") // 未知进位制 } i-- // 最后的个位数还没处理,在这里进行处理 buf[i] = digits[u] // 所有进制的个位数都可以查表取字符。 // 5、处理精度信息(添加前导 0) for i > 0 && prec > len(buf)-i { i-- buf[i] = ‘0‘ } // 6、处理前缀:0x、0 等 if f.sharp { switch base { case 8: if buf[i] != ‘0‘ { i-- buf[i] = ‘0‘ } case 16: // 根据参数 digits 确定大小写:0x、0X i-- buf[i] = digits[16] i-- buf[i] = ‘0‘ } } // 7、处理符号位 if negative { i-- buf[i] = ‘-‘ } else if f.plus { i-- buf[i] = ‘+‘ } else if f.space { i-- buf[i] = ‘ ‘ } // 8、处理宽度信息(填充空格) oldZero := f.zero f.zero = false f.pad(buf[i:]) f.zero = oldZero } // 将字符串截断到指定精度 func (f *fmt) truncate(s string) string { if f.precPresent { n := f.prec for i := range s { n-- if n < 0 { return s[:i] } } } return s } // 将数据写入缓冲区(字符串:处理宽度和精度信息) func (f *fmt) fmt_s(s string) { s = f.truncate(s) f.padString(s) } // 将数据写入缓冲区(字符串/字节切片:十六进制格式) func (f *fmt) fmt_sbx(s string, b []byte, digits string) { // 1、计算结果长度 // 获取要处理的字符串或字节切片长度 length := len(b) if b == nil { length = len(s) } // 只处理精度范围内的内容 if f.precPresent && f.prec < length { length = f.prec } // 每个元素(字节)需要 2 个字节存储其十六进制编码。 width := 2 * length if width > 0 { if f.space { // 元素之间有空格,所以需要在每个元素前面都添加 0x 或 0X。 // 每个元素的十六进制编码刚好是 2 个字节,所以乘以 2 之后, // 刚好每个元素多出 2 个字节的空间来存放 0x 或 0X if f.sharp { width *= 2 } // 各个元素之间将被一个空格隔开 width += length - 1 } else if f.sharp { // 元素之间没有空格,只需要在开头添加一个 0x 或 0X 即可。 width += 2 } } else { // 元素为空,则仅用空格填充宽度,比如:fmt.Printf("%8x", "") if f.widPresent { f.writePadding(f.wid) } return } // 2、处理“左”宽度信息 if f.widPresent && f.wid > width && !f.minus { f.writePadding(f.wid - width) } // 3、开始编码 buf := *f.buf // 在第一个元素前面添加前导 0x 或 0X。 if f.sharp { buf = append(buf, ‘0‘, digits[16]) } // 遍历各个元素(字节) var c byte for i := 0; i < length; i++ { // 元素之间添加空格,每个元素前面添加 0x 或 0X。 if f.space && i > 0 { buf = append(buf, ‘ ‘) if f.sharp { buf = append(buf, ‘0‘, digits[16]) } } // 对当前元素进行编码 if b != nil { c = b[i] } else { c = s[i] } buf = append(buf, digits[c>>4], digits[c&0xF]) } // 由于 append 操作,缓冲区可能被扩展 *f.buf = buf // 4、处理“右”宽度信息 if f.widPresent && f.wid > width && f.minus { f.writePadding(f.wid - width) } } // 将数据写入缓冲区(字符串:十六进制格式) func (f *fmt) fmt_sx(s, digits string) { f.fmt_sbx(s, nil, digits) } // 将数据写入缓冲区(字节切片:十六进制格式) func (f *fmt) fmt_bx(b []byte, digits string) { f.fmt_sbx("", b, digits) } // 将数据写入缓冲区(字符串:带双引号,未转义) // 如果指定了 # 标记,而且字符串不包含任何控制字符(除制表符), // 则会返回一个原始字符串(带反引号)。 func (f *fmt) fmt_q(s string) { // 1、处理精度信息 // 将字符串截断到指定精度 s = f.truncate(s) // 2、开始编码 // 处理 # 号 if f.sharp && strconv.CanBackquote(s) { f.padString("`" + s + "`") return } // 临时缓冲区,存放临时编码结果 buf := f.intbuf[:0] // 编码并处理宽度信息 if f.plus { // 非 ASCII 字符将被转换为 Unicode 码点 f.pad(strconv.AppendQuoteToASCII(buf, s)) } else { // 非 ASCII 字符将正常输出 f.pad(strconv.AppendQuote(buf, s)) } } // 将数据写入缓冲区(字符) // 如果字符不是有效的 Unicode 编码,则写入 ‘\ufffd‘ func (f *fmt) fmt_c(c uint64) { r := rune(c) // 超出 Unicode 范围 if c > utf8.MaxRune { r = utf8.RuneError } // 临时缓冲区 buf := f.intbuf[:0] // 对 r 进行编码 w := utf8.EncodeRune(buf[:utf8.UTFMax], r) // 将编码结果经过填充后写入缓冲区 f.pad(buf[:w]) } // 将数据写入缓冲区(字符:带单引号,未转义) // 如果字符不是有效的 Unicode 编码,则写入 ‘\ufffd‘ func (f *fmt) fmt_qc(c uint64) { r := rune(c) // 超出 Unicode 范围 if c > utf8.MaxRune { r = utf8.RuneError } // 临时缓冲区 buf := f.intbuf[:0] // 编码并处理宽度信息 if f.plus { // 非 ASCII 字符将被转换为 Unicode 码点 f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) } else { // 非 ASCII 字符将被正常输出 f.pad(strconv.AppendQuoteRune(buf, r)) } } // 将数据写入缓冲区(float64) // 如果参数所提供的动词是一个有效的格式区分符,则可以在 strconv.AppendFloat 中 // 将其转换为字节类型传入。 func (f *fmt) fmt_float(v float64, size int, verb rune, prec int) { // 1、开始编码 // “占位符”中的精度将覆盖参数中的默认精度 if f.precPresent { prec = f.prec } // 格式化数值,结果写入临时缓冲区,为 + 号预留一个空格 num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) // 2、处理符号 // 如果转换结果中有符号,则去掉预留的空格,否则将预留的空格转换为 + 号 if num[1] == ‘-‘ || num[1] == ‘+‘ { num = num[1:] } else { num[0] = ‘+‘ } // 如果指定了空格标记,而没有指定 + 标记,则将 + 改为空格。 if f.space && num[0] == ‘+‘ && !f.plus { num[0] = ‘ ‘ } // 3、处理无穷大和非数字 // 它看起来不像一个数字,所以不应该用 0 填充。 if num[1] == ‘I‘ || num[1] == ‘N‘ { oldZero := f.zero f.zero = false // 如果没有指定符号,则移除 NaN 前面的符号 if num[1] == ‘N‘ && !f.space && !f.plus { num = num[1:] } f.pad(num) f.zero = oldZero return } // 4、处理填充后写入 f.buf // 指定了 + 号 || 未指定 + 号,且结果不是以 + 开头。 if f.plus || num[0] != ‘+‘ { // 如果用 0 进行了左填充,那么我们希望符号在所有 0 的前面。 if f.zero && f.widPresent && f.wid > len(num) { f.buf.WriteByte(num[0]) // 写入符号 f.writePadding(f.wid - len(num)) // 进行填充 f.buf.Write(num[1:]) // 写入符号之外的内容 return } f.pad(num) return } // 未指定 + 号,但结果是以 + 开头,则去掉 + 号。 f.pad(num[1:]) }
以上是关于标准库 - fmt/format.go 解读的主要内容,如果未能解决你的问题,请参考以下文章