GO 中高效 int 轉換 string 的方法與源碼剖析

Go 語言 中,將整數(int)轉換爲字符串(string)是一項常見的操作。

本文將從逐步介紹幾種在 Go 中將 int 轉換爲 string 的常見方法,並重點剖析這幾種方法在性能上的特點。另外,還會重點介紹 FormatInt 高效的算法實現。

使用 strconv.Itoa

最直接且常用的方法是使用 strconv 包中的 Itoa 函數。Itoa 是 “Integer to ASCII” 的簡寫,它提供了一種快速且簡潔的方式實現整數到字符串之間的轉換。

示例代碼如下:

package main

import (
    "strconv"
    "fmt"
)

func main() {
    i := 123
    s := strconv.Itoa(i)
    fmt.Println(s)
}

strconv.Itoa 是通過直接將整數轉換爲其 ASCII 字符串表示形式。這個過程中儘量減少了額外的內存分配,沒有複雜邏輯。

使用 fmt.Sprintf

另一種方法是,使用 fmt 包的 Sprintf 函數。這個方法在功能上更爲強大和靈活,因爲它能處理各種類型並按照指定的格式輸出。

示例代碼如下:

package main

import (
    "fmt"
)

func main() {
    i := 123
    s := fmt.Sprintf("%d", i)
    fmt.Println(s)
}

雖然 fmt.Sprintf 在功能上非常強大,但它的性能通常不如 strconv.Itoa

爲什麼呢?

因爲 fmt.Sprintf 內部使用了反射(reflection)確定輸入值類型,並且在處理過程中涉及到更多的字符串拼接和內存分配。

使用 strconv.FormatInt

當需要更多控制或處理非 int 類型的整數(如 int64)時,可以使用 strconv 包的 FormatInt 函數。

package main

import (
    "strconv"
    "fmt"
)

func main() {
    var i int64 = 123
    s := strconv.FormatInt(i, 10)  // 10 表示十進制
    fmt.Println(s)
}

strconv.FormatInt 提供了對整數轉換過程的更細粒度控制,包括 base 的選擇(例如,十進制、十六進制等)。

與 strconv.Itoa 類似,FormatInt 在性能上也非常可觀,而且 FormatInt 提供了既靈活又高效的解決方案。

如果我們查看 strconv.Itoa 源碼,會發現 strconv.Itoa 其實是 strconv.FormatInt 的一個特殊情況。

// Itoa is shorthand for FormatInt(int64(i), 10).
func Itoa(i int) string {
    return FormatInt(int64(i), 10)
}

現在 int 轉 string 的高性能源碼剖析,就變成了重點剖析 FormatInt

FormatInt 深入剖析

基於 Go 1.21 版本的 itoa.go 源碼,我們可以深入理解 strconv 包中整數到字符串轉換函數的高效實現。

func FormatInt(i int64, base int) string {
    if fastSmalls && 0 <= i && i < nSmalls && base == 10 {
        return small(int(i)) // 100 以內的十進制小整數,使用 small 函數轉化
    }
      _, s := formatBits(nil, uint64(i), base, i < 0, false) // 其他情況使用 formatBits
    return s
}

以下是對其核心部分的詳細解讀,將會突出了其性能優化的關鍵方面,結合具體的源碼實現說明。

1. 快速路徑處理小整數

對於常見的小整數,strconv 包提供了一個快速路徑,small 函數,直接返回預先計算好的字符串,避免了運行時的計算開銷。

func small(i int) string {
    if i < 10 {
        return digits[i : i+1]
    }
    return smallsString[i*2 : i*2+2]
}

對於小於 100 的十進制整數,採用這個快速實現方案,或許這也是整數轉字符串的最常見使用場景吧。

small 函數通過索引到 smallsString 和 digits 獲取小整數的字符串表示,這個過程非常快速。 digits 和 smallsString 的值,如下所示:

const smallsString = "00010203040506070809" +
    "10111213141516171819" +
    "20212223242526272829" +
    "30313233343536373839" +
    "40414243444546474849" +
    "50515253545556575859" +
    "60616263646566676869" +
    "70717273747576777879" +
    "80818283848586878889" +
    "90919293949596979899"

const digits = "0123456789abcdefghijklmnopqrstuvwxyz"

它們也就是十進制 0-99 與對應字符串的映射。

2. formatBits 函數的高效實現

FormatInt 最複雜的部分是 formatBits 函數,它是整數到字符串轉換的核心,它針對不同的基數進行了優化。

10 進制轉換的優化

對於 10 進制轉換,formatBits 使用了基於除法和取餘的算法,並通過 smallsString 加速兩位數的字符串獲取。

if base == 10 {
    // ... (32位系統的優化)
    us := uint(u)
    for us >= 100 {
        is := us % 100 * 2
        us /= 100
        i -= 2
        a[i+1] = smallsString[is+1]
        a[i+0] = smallsString[is+0]
    }
    // ... (處理剩餘的數字)
}

2 的冪基數的優化

對於基數是 2 的冪的情況,formatBits 使用了位操作來優化轉換。

} else if isPowerOfTwo(base) {
    shift := uint(bits.TrailingZeros(uint(base))) & 7
    b := uint64(base)
    m := uint(base) - 1 // == 1<<shift - 1
    for u >= b {
        i--
        a[i] = digits[uint(u)&m]
        u >>= shift
    }
    // u < base
    i--
    a[i] = digits[uint(u)]
}

通用情況的處理

對於其他基數,formatBits 使用了通用的算法,但仍然儘量減少了除法和取餘操作的使用。

} else {
    // general case
    b := uint64(base)
    for u >= b {
        i--
        // Avoid using r = a%b in addition to q = a/b
        // since 64bit division and modulo operations
        // are calculated by runtime functions on 32bit machines.
        q := u / b
        a[i] = digits[uint(u-q*b)]
        u = q
}

我覺得最核心的算法就是利用移位和特殊路徑預置映射關係。另外,由於算法足夠優秀,還避免了一些不必要內存分配。

結論

將 int 轉化爲 string 是一個非常常見的需求。Go 語言的 strconv 包中的 int 到 string 的轉換函數展示了 Go 標準庫對性能的深刻理解和關注。

通過快速處理小整數、優化的 10 進制轉換算法、以及 2^n 基數的特別處理,這些函數能夠提供高效且穩定的性能。這些優化確保了即使在大量數據或在性能敏感的場景中,strconv 包的函數也能提供出色的性能

博文地址:GO 中高效 int 轉換 string 的方法與源碼剖析 [1]

引用鏈接

[1] GO 中高效 int 轉換 string 的方法與源碼剖析: https://www.poloxue.com/posts/2024-01-20-int-to-string-in-golang/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xwNZsIp3qMK532R9CVI31A