// 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:] // 1、判斷容量是否夠用 // 如果沒有指定精度,那么容量肯定夠用,因為即便使用 %#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) // 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、處理寬度信息(用空格填充,不允許用 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) { // 1、修正參數 // 如果參數 u 中存入的是負值,則將其轉變為正值,負號由 negative 保存。 negative := isSigned && int64(u) < 0 if negative { u = -u // 相當於 -int64(u),類型轉換不會改變值內容,所以減哪個都一樣,-u 省一步轉換操作 } // 臨時緩沖區,容量為 68 字節,如果容量不夠用,可以進行進行擴充。 buf := f.intbuf[0:] // 2、確保容量夠用 if f.widPresent || f.precPresent { width := 3 + f.wid + f.prec // 需要額外的 3 個字節來存放帶符號的 "-0x"。 // 這里為了提高效率,直接將寬度和精度相加(實際上寬度和精度是重疊的), // 因為在大多數情況下,相加的結果都不會太大,結果大於 len(buf) 的情況很 // 少出現,很少需要重新分配內存,所以這里只是為了確保安全而已。相反,如 // 果用判斷語句計算准確的 width 值,反而會降低效率。 if width > len(buf) { buf = make([]byte, width) } } // 3、確定精度信息 // 注:有兩種方式為整數添加前導零:%.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) // 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、處理寬度信息(由於指定了精度,所以不能用 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) { // 1、計算內容長度 // 獲取字符串或切片的長度 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 } // 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++ { 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 // 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) } // 寫入雙引號字符串。 // 如果指定了 # 旗標,而且字符串不包含任何控制字符(除制表符), // 則會寫入反引號原始字符串。 // 如果指定了 + 旗標,則字符串中的所有非 ASCII 字符都會被轉義處理。 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] // 轉換結果不一定在這個 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) { // 1、開始編碼 // “占位符”中的精度將覆蓋參數中的默認精度 if f.precPresent { prec = f.prec } // 格式化數值,結果寫入臨時緩沖區,為 + 號預留一個空格 // 如果 verb 是一個有效的動詞,則可以在這里將其轉換為字節類型傳入。 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、開始寫入 // 指定了 "+" 旗標 或 結果不是以 + 開頭(以 - 或空格開頭)。 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:]) }