首页 > 代码库 > 标准库 - fmt/format.go 解读

标准库 - 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.7package fmtimport (	"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 解读