// 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:]) }