【问题标题】:Overhead of converting from []byte to string and vice-versa从 []byte 转换为 string 的开销,反之亦然
【发布时间】:2014-09-14 06:14:22
【问题描述】:

我似乎总是一遍又一遍地将字符串转换为 []byte 到字符串。这有很多开销吗?有没有更好的办法?

例如,这里有一个函数,它接受一个 UTF8 字符串,对其进行规范化,去除重音符号,然后将特殊字符转换为 ASCII 等效字符:

var transliterations = map[rune]string{'Æ':"AE",'Ð':"D",'Ł':"L",'Ø':"OE",'Þ':"Th",'ß':"ss",'æ':"ae",'ð':"d",'ł':"l",'ø':"oe",'þ':"th",'Œ':"OE",'œ':"oe"}
func RemoveAccents(s string) string {
    b := make([]byte, len(s))
    t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
    _, _, e := t.Transform(b, []byte(s), true)
    if e != nil { panic(e) }
    r := string(b)

    var f bytes.Buffer
    for _, c := range r {
        temp := rune(c)
        if val, ok := transliterations[temp]; ok {
            f.WriteString(val)
        } else {
            f.WriteRune(temp)
        }
    }
    return f.String()
}

所以我从一个字符串开始,因为这就是我得到的,然后我将它转换为一个字节数组,然后再转换回一个字符串,然后再转换为一个字节数组,然后再转换回一个字符串。当然这是不必要的,但我不知道如何不这样做..?它是否真的有很多开销,或者我不必担心过度转换会减慢速度?

(另外,如果有人有时间我还没有弄清楚bytes.Buffer实际上是如何工作的,那么初始化一个2倍于字符串大小的缓冲区会不会更好,这是返回的最大输出大小价值?)

【问题讨论】:

    标签: string go


    【解决方案1】:

    在 Go 中,strings 是不可变的,因此任何更改都会创建一个新字符串。作为一般规则,从string 转换为byterune 切片一次,然后再转换回string 一次。为避免重新分配,对于小型和临时分配,如果您不知道确切的数量,请过度分配以提供安全边际。

    例如,

    package main
    
    import (
        "bytes"
        "fmt"
        "unicode"
        "unicode/utf8"
    
        "code.google.com/p/go.text/transform"
        "code.google.com/p/go.text/unicode/norm"
    )
    
    var isMn = func(r rune) bool {
        return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
    }
    
    var transliterations = map[rune]string{
        'Æ': "AE", 'Ð': "D", 'Ł': "L", 'Ø': "OE", 'Þ': "Th",
        'ß': "ss", 'æ': "ae", 'ð': "d", 'ł': "l", 'ø': "oe",
        'þ': "th", 'Œ': "OE", 'œ': "oe",
    }
    
    func RemoveAccents(b []byte) ([]byte, error) {
        mnBuf := make([]byte, len(b)*125/100)
        t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
        n, _, err := t.Transform(mnBuf, b, true)
        if err != nil {
            return nil, err
        }
        mnBuf = mnBuf[:n]
        tlBuf := bytes.NewBuffer(make([]byte, 0, len(mnBuf)*125/100))
        for i, w := 0, 0; i < len(mnBuf); i += w {
            r, width := utf8.DecodeRune(mnBuf[i:])
            if s, ok := transliterations[r]; ok {
                tlBuf.WriteString(s)
            } else {
                tlBuf.WriteRune(r)
            }
            w = width
        }
        return tlBuf.Bytes(), nil
    }
    
    func main() {
        in := "test stringß"
        fmt.Println(in)
        inBytes := []byte(in)
        outBytes, err := RemoveAccents(inBytes)
        if err != nil {
            fmt.Println(err)
        }
        out := string(outBytes)
        fmt.Println(out)
    }
    

    输出:

    test stringß
    test stringss
    

    【讨论】:

    【解决方案2】:

    这个问题没有答案。如果这些转换是您应用程序中的性能瓶颈,您应该修复它们。如果不是:不是。

    您是否在实际负载下分析了您的应用程序并且RemoveAccents 是瓶颈?不?那何必呢?

    真的:我认为一个人可以做得更好(在更少垃圾、更少迭代和更少转换的意义上),例如通过链接一些“TransliterationTransformer”。但我怀疑这会带来麻烦。

    【讨论】:

    • 嗯...我明白你在说什么,但我喜欢在脑海中对我的编码风格的效率有一个想法。进入而不是分析和优化特定应用程序是一个习惯问题。
    【解决方案3】:

    将字符串转换为字节切片(不是数组,即different type)会产生少量开销。即为字节片分配空间。

    字符串是它自己的类型,是对字节序列的解释。但并非每个字节序列都是有用的字符串。字符串也是immutable。如果您查看strings package,您会发现字符串很多都是sliced

    在您的示例中,您可以省略第二次转换回字符串。您还可以在一个字节切片上进行范围。

    对于每个关于性能的问题:您可能需要衡量。字节片的分配真的是你的瓶颈吗?

    你可以像这样初始化你的bytes.Buffer

    f := bytes.NewBuffer(make([]byte, 0, len(s)*2))
    

    您的字符串大小为 0,容量为字符串大小的 2 倍。如果您可以估计缓冲区的大小,那么这样做可能会很好。它将为您节省一些底层字节片的重新分配。

    【讨论】:

    • 感谢有关初始化缓冲区的建议。对于字符串的范围,我认为它确实需要是一个字符串,因为符文的字符串范围是我在这种情况下所需要的。它可能可以通过字节数组上的扫描仪来完成,但我还没有学会如何做到这一点。
    • @Alasdair,你可以从[]bytebufio.Reader.ReadRune 获得符文,或者更直接地使用utf8.DecodeRune
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-14
    • 1970-01-01
    • 1970-01-01
    • 2013-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多