Осуществляет ли преобразование доступ к элементам строки как байту?

В Go, чтобы получить доступ к элементам stringмы можем написать:

str := "text"
for i, c := range str {
  // str[i] is of type byte
  // c is of type rune
}

При доступе str[i] Go выполняет преобразование из rune в byte? Я предполагаю, что ответ - да, но я не уверен. Если так, то какой из следующих методов лучше с точки зрения производительности? Является ли один предпочтительным по сравнению с другим (например, с точки зрения наилучшей практики)?

str := "large text"
for i := range str {
  // use str[i]
}

или же

str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}

2 ответа

Решение

Какой из следующих методов лучше с точки зрения производительности?

Определенно не это.

str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}

Строки неизменны. []byte изменчиво Это означает []byte(str) делает копию. Таким образом, выше будет скопировать всю строку. Я обнаружил, что не знаю, когда строки копируются, чтобы быть основным источником проблем с производительностью для больших строк.

Если str2 никогда не изменяется, компилятор может оптимизировать копию. По этой причине лучше написать выше, чтобы гарантировать, что байтовый массив никогда не будет изменен.

str := "large text"
for _, s := range []byte(str) {
  // use s
}

Таким образом, нет str2 возможно, будет изменен позже и испортит оптимизацию.

Но это плохая идея, потому что она испортит любые многобайтовые символы. Увидеть ниже.


Что касается преобразования байтов / рун, производительность не учитывается, поскольку они не эквивалентны. c будет руна, и str[i] будет байт. Если ваша строка содержит многобайтовые символы, вы должны использовать руны.

Например...

package main

import(
    "fmt"
)

func main() {
    str := "snow ☃ man"
    for i, c := range str {
        fmt.Printf("c:%c str[i]:%c\n", c, str[i])
    }
}

$ go run ~/tmp/test.go
c:s str[i]:s
c:n str[i]:n
c:o str[i]:o
c:w str[i]:w
c:  str[i]: 
c:☃ str[i]:â
c:  str[i]: 
c:m str[i]:m
c:a str[i]:a
c:n str[i]:n

Обратите внимание, что с помощью str[i] портит многобайтовый снеговик Unicode, он содержит только первый байт многобайтового символа.

Там нет разницы в производительности, так как range str уже должен делать работу, чтобы идти символ за символом, а не побайтово.

string значения в Go хранят байты текста в кодировке UTF-8, а не его символы или runes.

Индексирование string индексирует свои байты: str[i] имеет тип byte (или же uint8его псевдоним). Также string на самом деле это кусочек байтов только для чтения (с некоторым синтаксическим сахаром). Индексирование string не требует преобразования его в срез.

Когда вы используете for ... range на stringперебирает runeс string, а не его байты!

Так что если вы хотите перебрать runes (символы), вы должны использовать for ... range но без преобразования в []byte, так как первая форма не будет работать с string значения, содержащие многобайтовые (UTF-8)-байтовые символы. Спецификация позволяет вам for ... range на string значение, а 1-е значение итерации будет байтовым индексом текущего символа, 2-е значение будет текущим значением символа типа rune (который является псевдонимом int32):

Для строкового значения предложение "range" выполняет итерации по кодовым точкам Unicode в строке, начиная с байтового индекса 0. В последовательных итерациях значение индекса будет индексом первого байта последовательных кодированных точек UTF-8 в строка и второе значение типа rune будут значением соответствующей кодовой точки. Если итерация встречает недопустимую последовательность UTF-8, второе значение будет 0xFFFD, символ замены Unicode, и следующая итерация будет продвигать один байт в строке.

Простой пример:

s := "Hi 世界"
for i, c := range s {
    fmt.Printf("Char pos: %d, Char: %c\n", i, c)
}

Вывод (попробуйте на Go Playground):

Char pos: 0, Char: H
Char pos: 1, Char: i
Char pos: 2, Char:  
Char pos: 3, Char: 世
Char pos: 6, Char: 界

Должен прочитать сообщение в блоге для вас:

Блог Go: строки, байты, руны и символы в Go


Примечание: если вам нужно перебрать байты string (а не его персонажи), используя for ... range с переделанным string как ваш второй пример не делает копию, он оптимизирован. Для получения дополнительной информации см. Golang: [] byte (string) vs [] byte (* string).

Другие вопросы по тегам