標準庫 - 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

// 格式化輸入輸出的用法請參考:http://www.cnblogs.com/golove/p/3284304.html

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 是一個基礎的格式化器,用於將各類類型的數據進行寬度和精度處理後,
// 寫入緩衝區,緩衝區必須單獨指定。
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]
}

// 下面是寫入各類類型的數據。全部寫入的內容都會處理寬度和精度信息。

// 寫入 []byte
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:]

	// 一、判斷容量是否夠用

	// 若是沒有指定精度,那麼容量確定夠用,由於即使使用 %#U 對 -1 進行格式化,
	// 所需的最大存儲空間也只有 18 字節("U+FFFFFFFFFFFFFFFF"),沒有超過 68。
	// 只有指定了過大的精度以後(好比 100),纔有可能超出容量範圍。
	// 因此下面對容量的判斷,只和精度有關。

	// 默認精度爲 4(若是碼點長度不足 4 位,則添加前導 0,好比 U+0065,若是
	// 碼點長度超過精度值,則忽略精度)
	prec := 4

	// 若是指定了精度,則要確保臨時緩衝區夠用
	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)

	// 二、處理 # 旗標

	// 在最後添加 '相應字符'。
	// 前提是數值必須在 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] = ' '
	}

	// 三、將參數 u 格式化爲十六進制數值。

	for u >= 16 {
		i--                     // 肯定 buf 的寫入下標
		buf[i] = udigits[u&0xF] // 與 1111 相與,獲取十六進制的個位數,而後查表取字符。
		prec--                  // 精度被用掉一個
		u >>= 4                 // 丟棄十六進制的個位數
	}
	i--                         // 處理最後一個個位數
	buf[i] = udigits[u]
	prec--

	// 四、處理精度信息(添加前導 0)

	for prec > 0 {
		i--
		buf[i] = '0'
		prec--
	}

	// 五、處理前導 "U+"。

	i--
	buf[i] = '+'
	i--
	buf[i] = 'U'

	// 六、處理寬度信息(用空格填充,不容許用 0 填充)

	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) {

	// 一、修正參數

	// 若是參數 u 中存入的是負值,則將其轉變爲正值,負號由 negative 保存。
	negative := isSigned && int64(u) < 0
	if negative {
		u = -u // 至關於 -int64(u),類型轉換不會改變值內容,因此減哪一個都同樣,-u 省一步轉換操做
	}

	// 臨時緩衝區,容量爲 68 字節,若是容量不夠用,能夠進行進行擴充。
	buf := f.intbuf[0:]

	// 二、確保容量夠用

	if f.widPresent || f.precPresent {
		width := 3 + f.wid + f.prec
		// 須要額外的 3 個字節來存放帶符號的 "-0x"。
		// 這裏爲了提升效率,直接將寬度和精度相加(實際上寬度和精度是重疊的),
		// 由於在大多數狀況下,相加的結果都不會太大,結果大於 len(buf) 的狀況很
		// 少出現,不多須要從新分配內存,因此這裏只是爲了確保安全而已。相反,如
		// 果用判斷語句計算準確的 width 值,反而會下降效率。
		if width > len(buf) {
			buf = make([]byte, width)
		}
	}

	// 三、肯定精度信息

	// 注:有兩種方式爲整數添加前導零:%.3d 或 %08d,
	// 若是同時使用了這兩種寫法,那麼 0 旗標將被忽略,會使用空格實現寬度填充。
	// 也就是說,%08.3d 至關於 %8.3d。

	// 默認精度爲 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)

	// 四、開始編碼

	// 使用字面量進行除法和取模操做能夠更有效率。
	// 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] // 全部進制的個位數均可以查表取字符。

	// 五、處理精度信息(添加前導 0)

	for i > 0 && prec > len(buf)-i {
		i--
		buf[i] = '0'
	}

	// 六、處理前綴: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'
		}
	}

	// 七、處理符號位

	if negative {
		i--
		buf[i] = '-'
	} else if f.plus {
		i--
		buf[i] = '+'
	} else if f.space {
		i--
		buf[i] = ' '
	}

	// 八、處理寬度信息(因爲指定了精度,因此不能用 0 填充寬度,只能用空格填充)

	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) {

	// 一、計算內容長度

	// 獲取字符串或切片的長度
	length := len(b)
	if b == nil { // 若是二者都提供了,則優先處理 []byte
		length = len(s)
	}
	// 只處理精度範圍內的字節
	if f.precPresent && f.prec < length {
		length = f.prec
	}
	// 每一個元素(字節)須要 2 個字節存儲其十六進制編碼。
	width := 2 * length
	if width > 0 {
		if f.space {
			if f.sharp {
				width *= 2
				// 元素之間有空格,因此須要在每一個元素前面都添加 0x 或 0X。
				// 每一個元素的十六進制編碼恰好是 2 個字節,因此乘以 2 以後,
				// 恰好每一個元素多出 2 個字節的空間來存放 0x 或 0X
			}
			// 各個元素之間將被一個空格隔開
			width += length - 1
		} else if f.sharp {
			// 元素之間沒有空格,只須要在開頭添加一個 0x 或 0X 便可。
			width += 2
		}
	} else { // 元素爲空,則僅用空格填充寬度,好比:fmt.Printf("%8x", "")
		if f.widPresent {
			f.writePadding(f.wid)
		}
		return
	}

	// 二、處理「左」寬度填充

	if f.widPresent && f.wid > width && !f.minus {
		f.writePadding(f.wid - width)
	}

	// 三、開始編碼

	buf := *f.buf

	// 在第一個元素前面添加前導 0x 或 0X。
	if f.sharp {
		buf = append(buf, '0', digits[16])
	}
	// 遍歷各個元素(字節)
	var c byte
	for i := 0; i < length; i++ {
		if f.space && i > 0 {
			// 元素之間添加空格
			buf = append(buf, ' ')
			if f.sharp {
				// 每一個元素前面添加 0x 或 0X
				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

	// 四、處理「右」寬度填充

	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)
}

// 寫入雙引號字符串。
// 若是指定了 # 旗標,並且字符串不包含任何控制字符(除製表符),
// 則會寫入反引號原始字符串。
// 若是指定了 + 旗標,則字符串中的全部非 ASCII 字符都會被轉義處理。
func (f *fmt) fmt_q(s string) {

	// 一、處理精度信息

	// 將字符串截斷到指定精度
	s = f.truncate(s)

	// 二、開始編碼

	// 處理 # 號
	if f.sharp && strconv.CanBackquote(s) {
		f.padString("`" + s + "`")
		return
	}

	// 臨時緩衝區,提供給下面的轉換函數使用。
	buf := f.intbuf[:0]
	// 轉換結果不必定在這個 buf 中,由於轉換過程當中可能會擴容。
	// 轉換結果在轉換函數的返回值中,因此這個 buf 僅僅是一個參數。

	// 轉換並寫入
	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'
// 若是指定了 + 旗標,則非 ASCII 字符會被轉義處理。
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
func (f *fmt) fmt_float(v float64, size int, verb rune, prec int) {

	// 一、開始編碼

	// 「佔位符」中的精度將覆蓋參數中的默認精度
	if f.precPresent {
		prec = f.prec
	}
	// 格式化數值,結果寫入臨時緩衝區,爲 + 號預留一個空格
	// 若是 verb 是一個有效的動詞,則能夠在這裏將其轉換爲字節類型傳入。
	num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)

	// 二、處理符號

	// 若是轉換結果中有符號,則去掉預留的空格,不然將預留的空格轉換爲 + 號
	if num[1] == '-' || num[1] == '+' {
		num = num[1:]
	} else {
		num[0] = '+'
	}
	// 若是指定了 " " 旗標,而沒有指定 "+" 旗標,則將 + 號改成空格。
	if f.space && num[0] == '+' && !f.plus {
		num[0] = ' '
	}

	// 三、處理無窮大和非數字

	// 它看起來不像一個數字,因此不該該用 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
	}

	// 四、開始寫入

	// 指定了 "+" 旗標 或 結果不是以 + 開頭(以 - 或空格開頭)。
	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:])
}



相關文章
相關標籤/搜索