Осуществляет ли преобразование доступ к элементам строки как байту?
В 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, а не его символы или rune
s.
Индексирование 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).