由leetcode引发的fmt.Sprintf函数思考

刷leetcode时,发现直接使用"+“拼接字符串与使用fmt.Sprintf函数进行拼接,用例执行时间相差28ms(针对发现问题的题目)。所以需要打开fmt.Sprintf函数流程,观察其到底进行了哪些操作。

问题背景

使用fmt.Sprintf进行字符串拼接,代码如下:

func getFolderNames(names []string) []string {
    nameMap := make(map[string]int64)
    for i := range names {
        if v, ok := nameMap[names[i]]; ok {
            count := v
            var tmp string
            for ; ok; count++ {
                tmp = names[i] + fmt.Sprintf("(%d)", count)
                _, ok = nameMap[tmp]
            }
            nameMap[names[i]] = count
            nameMap[tmp] = 1
            names[i] = tmp
            continue
        }

        nameMap[names[i]] = 1
    }

    return names
}

对应耗时如下:

img.png

问题分析

与耗时最小答案对比

通过分析,解题思路是最优的,在方法上不会有耗时的提高。分析当前耗时最小代码,发现与自己提交代码的结构相同,最大的区别为当前最优代码中字符串拼接使用了+而非fmt.Sprintf函数。当前最优代码如下:

func getFolderNames(names []string) []string {
    res := []string{}
    mp := map[string]int{}

    for _, name := range names {
        str := name
        for mp[str] > 0 {
            str = name + "(" + strconv.Itoa(mp[name]) +")"
            mp[name]++
        }

        mp[str]++
        res = append(res, str)
    }

    return res
}

使用+提交

使用+直接拼接字符串代码:

func getFolderNames(names []string) []string {
    nameMap := make(map[string]int64)
    for i := range names {
        if v, ok := nameMap[names[i]]; ok {
            count := v
            var tmp string
            for ; ok; count++ {
                tmp = names[i] + "(" + strconv.Itoa(int(count)) + ")"
                _, ok = nameMap[tmp]
            }
            nameMap[names[i]] = count
            nameMap[tmp] = 1
            names[i] = tmp
            continue
        }

        nameMap[names[i]] = 1
    }

    return names
}

对应耗时:

img.png

使用+连接后,耗时减少28ms,且超越了当前最小耗时,可见fmt.Sprintf确实为问题关键。

fmt.Sprintf流程分析

img.png

fmt.Sprintf流程中,涉及到字符的copy操作(将string拷贝到buf),且需要逐个对传入的string byte解析,判断%动词。

直接使用+拼接,仅涉及字符拷贝,没有动词的解析流程,当拼接字符串比较简单时,可使用直接拼接代替fmt.Sprintf。

参考链接

leetcode题目