web-dev-qa-db-fra.com

Comment encoder [] rune en [] octet en utilisant utf8 dans golang?

Il est donc très facile de décoder un []byte dans une []rune (simplement cast en string, puis cast en []rune fonctionne très bien, je suppose qu'il utilise par défaut utf8 et avec des octets de remplissage pour les invalides). Ma question est - comment êtes-vous supposé décoder ce []rune retour à []byte sous forme utf8?

Suis-je en train de manquer quelque chose ou dois-je appeler manuellement EncodeRune pour chaque rune de mon []rune? Il y a sûrement un encodeur auquel je peux simplement passer un Writer.

17
Herp Derpington

Vous pouvez simplement convertir une tranche de rune ([]rune) En string que vous pouvez reconvertir en []byte.

Exemple:

rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := []byte(string(rs))

fmt.Printf("%s\n", bs)
fmt.Println(string(bs))

Sortie (essayez-le sur le Go Playground ):

Hello 世界
Hello 世界

Spécification Go: Conversions mentionne ce cas explicitement: Conversions vers et depuis un type de chaîne , point # 3:

La conversion d'une tranche de runes en un type de chaîne produit une chaîne qui est la concaténation des valeurs de runes individuelles converties en chaînes.

Notez que la solution ci-dessus - bien qu'elle puisse être la plus simple - peut ne pas être la plus efficace. Et la raison en est qu'il crée d'abord une valeur string qui contiendra une "copie" des runes sous forme codée UTF-8, puis il copie la tranche de support de la chaîne dans la tranche d'octets de résultat (une copie doit être faite car les valeurs de string sont immuables, et si la tranche de résultat partageait des données avec le string, nous serions en mesure de modifier le contenu du string; pour détails, voir golang: [] octet (chaîne) vs [] octet (* chaîne) et Chaîne immuable et adresse du pointeur ).

Notez qu'un compilateur intelligent pourrait détecter que la valeur intermédiaire string ne peut pas être référencée et ainsi éliminer l'une des copies.

Nous pouvons obtenir de meilleures performances en allouant une tranche d'un seul octet et en codant les runes une par une. Et nous avons terminé. Pour le faire facilement, nous pouvons appeler le package unicode/utf8 à notre aide:

rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := make([]byte, len(rs)*utf8.UTFMax)

count := 0
for _, r := range rs {
    count += utf8.EncodeRune(bs[count:], r)
}
bs = bs[:count]

fmt.Printf("%s\n", bs)
fmt.Println(string(bs))

La sortie de ce qui précède est la même. Essayez-le sur le Go Playground .

Notez que pour créer la tranche de résultat, nous devions deviner la taille de la tranche de résultat. Nous avons utilisé une estimation maximale, qui est le nombre de runes multiplié par le nombre maximal d'octets dans lequel une rune peut être codée (utf8.UTFMax). Dans la plupart des cas, ce sera plus grand que nécessaire.

Nous pouvons créer une troisième version où nous calculons d'abord la taille exacte nécessaire. Pour cela, nous pouvons utiliser la fonction utf8.RuneLen() . Le gain sera que nous ne "gaspillerons" pas la mémoire, et nous n'aurons pas à faire un découpage final (bs = bs[:count]).

Comparons les performances. Les 3 fonctions (3 versions) à comparer:

func runesToUTF8(rs []rune) []byte {
    return []byte(string(rs))
}

func runesToUTF8Manual(rs []rune) []byte {
    bs := make([]byte, len(rs)*utf8.UTFMax)

    count := 0
    for _, r := range rs {
        count += utf8.EncodeRune(bs[count:], r)
    }

    return bs[:count]
}

func runesToUTF8Manual2(rs []rune) []byte {
    size := 0
    for _, r := range rs {
        size += utf8.RuneLen(r)
    }

    bs := make([]byte, size)

    count := 0
    for _, r := range rs {
        count += utf8.EncodeRune(bs[count:], r)
    }

    return bs
}

Et le code de référence:

var rs = []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}

func BenchmarkFirst(b *testing.B) {
    for i := 0; i < b.N; i++ {
        runesToUTF8(rs)
    }
}

func BenchmarkSecond(b *testing.B) {
    for i := 0; i < b.N; i++ {
        runesToUTF8Manual(rs)
    }
}

func BenchmarkThird(b *testing.B) {
    for i := 0; i < b.N; i++ {
        runesToUTF8Manual2(rs)
    }
}

Et les résultats:

BenchmarkFirst-4        20000000                95.8 ns/op
BenchmarkSecond-4       20000000                84.4 ns/op
BenchmarkThird-4        20000000                81.2 ns/op

Comme suspect, la deuxième version est plus rapide et la troisième version est la plus rapide, bien que le gain de performances ne soit pas énorme. En général, la première solution la plus simple est préférée, mais si elle se trouve dans une partie critique de votre application (et est exécutée plusieurs fois), la troisième version peut valoir la peine d'être utilisée.

36
icza