Go:从[]字节转换为字符串的开销,反之亦然

时间:2014-07-23 07:18:01

标签: string go

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

例如,这是一个接受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倍的缓冲区是不是更好,这是最大输出大小返回值?)

3 个答案:

答案 0 :(得分:4)

在Go中,string是不可变的,因此任何更改都会创建一个新字符串。作为一般规则,请将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

答案 1 :(得分:2)

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

您是否在实际负载下分析了应用程序,RemoveAccents是瓶颈?没有?那么为什么要这么麻烦呢?

真的:我认为一个人可以做得更好(在垃圾更少,迭代次数更少,转换次数更少的情况下),例如通过链接一些&#34; TransliterationTransformer&#34;。但我怀疑这将是麻烦的麻烦。

答案 2 :(得分:1)

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

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

在您的示例中,您可以省略第二次转换回字符串。您还可以使用一个字节切片。

与关于绩效的每个问题一样:您可能需要衡量。字节切片的分配真的是你的瓶颈吗?

您可以像这样初始化bytes.Buffer

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

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