Оригинальная ссылка:blog.thinker idea.com/201910/go/ о…
Недавно я былGo Forumнайти в[SOLVED] String size of 20 characterЭта проблема,"hollowaykeanho" дал соответствующий ответ, и я обнаружил, что схема перехвата строк не самый идеальный метод, поэтому я провел серию экспериментов и получил эффективный метод перехвата строк. Эта статья шаг за шагом объяснит процесс моей практики .
Перехват байтового среза
Это точно "hollowaykeanhoПервое решение, данное ", я думаю, что это также первое решение, о котором думают многие, использует встроенный синтаксис среза go для перехвата строк:
s := "abcdef"
fmt.Println(s[1:4])
Мы быстро поняли, что это побайтовое усечение, при обработкеASCII
Нет лучшего решения, чем перехват однобайтовой строки, китайский часто занимает несколько байтов.utf8
В кодировке 3 байта, мы получим искаженные данные в следующей программе:
s := "Go 语言"
fmt.Println(s[1:4])
Убийца - преобразование типа []руна
"hollowaykeanhoВторое приведенное решение — преобразовать строку в[]rune
, а затем перехватить в соответствии с синтаксисом среза, а затем преобразовать результат в строку.
s := "Go 语言"
rs := []rune(s)
fmt.Println(strings(rs[1:4]))
Сначала мы получили правильный результат, что является самым большим улучшением. Тем не менее, я всегда осторожно относился к преобразованию типов и беспокоился о его производительности, поэтому я пытался найти ответ в поисковых системах и на крупных форумах, но самое большее, что я нашел, это это решение, которое кажется единственным решением.
Я пытаюсь написать тест производительности, чтобы оценить его производительность:
package benchmark
import (
"testing"
)
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRunes(s string, length int) string {
if utf8.RuneCountInString(s) > length {
rs := []rune(s)
return string(rs[:length])
}
return s
}
func BenchmarkSubStrRunes(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRunes(benchmarkSubString, benchmarkSubStringLength)
}
}
Я получил результаты, которые меня немного удивили:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRunes-8 872253 1363 ns/op 336 B/op 2 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 2.120s
Для усечения первых 20 символов строки из 69 строк требуется около 1,3 микросекунды, что намного превышает мои ожидания.Я обнаружил, что из-за выделения памяти, вызванного преобразованием типа, создается новая строка, а преобразование типа требует много вычислений.
Спасатель — utf8.DecodeRuneInString
Я хочу улучшить дополнительные операции и выделение памяти, вызванные преобразованием типов, я тщательно прочесал этоstrings
package и обнаружил, что нет связанного инструмента, тогда я подумал об этомutf8
пакет, который предоставляет инструменты, связанные с многобайтовыми вычислениями. Честно говоря, я не знаком с ним или активно (непосредственно) не использовал его. Я проверил всю его документацию и обнаружил, чтоutf8.DecodeRuneInString
Функция может преобразовать одиночный символ и выдать количество байтов, занимаемых символом.Я пробовал следующие эксперименты:
package benchmark
import (
"testing"
"unicode/utf8"
)
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrDecodeRuneInString(s string, length int) string {
var size, n int
for i := 0; i < length && n < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[n:])
n += size
}
return s[:n]
}
func BenchmarkSubStrDecodeRuneInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrDecodeRuneInString(benchmarkSubString, benchmarkSubStringLength)
}
}
После запуска я получил к своему удивлению:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrDecodeRuneInString-8 10774401 105 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.250s
Сравнивать[]rune
Улучшена эффективность преобразования типов13 раз, устраняет выделение памяти, это действительно захватывающе и захватывающе, мне не терпится ответить"hollowaykeanho«Скажите ему, что я нашел лучший способ, и предоставьте соответствующие тесты производительности.
Я был немного взволнован, и взволнованно просматривал всевозможные интересные вопросы на форуме.Просматривая справку по вопросу (забыл, какой вопрос -_-||), я был удивлен, обнаружив другую идею.
Хорошее лекарство не должно быть горьким — итерация строки диапазона
Многие видимо забылиrange
Итерации по символам, а не по байтам. использоватьrange
При переборе строки он возвращает начальный индекс символа и соответствующий символ.Я сразу попытался использовать эту функцию, чтобы написать следующий вариант использования:
package benchmark
import (
"testing"
)
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRange(s string, length int) string {
var n, i int
for i = range s {
if n == length {
break
}
n++
}
return s[:i]
}
func BenchmarkSubStrRange(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRange(benchmarkSubString, benchmarkSubStringLength)
}
}
Я попробовал запустить его, и это казалось бесконечным волшебством, и оно меня не разочаровало.
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRange-8 12354991 91.3 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.233s
Улучшение всего на 13%, но оно настолько простое и понятное, что похоже на лекарство, которое я искал.
Если вы думали, что это конец, то нет, для меня это только начало исследования.
Окончательный момент - создайте свои собственные колеса
напитокrange
Эта миска сладкого и жирного лекарства, кажется, успокаивает меня, мне нужно построить колесо, оно должно быть проще в использовании и более эффективным.
Итак, я внимательно наблюдал за двумя схемами оптимизации, все они, похоже, заключаются в том, чтобы найти позицию индекса перехватываемых символов заданной длины, если я могу предоставить такой метод, могу ли я предоставить пользователям простую реализацию перехватаs[:strIndex(20)]
, я не мог избавиться от этой идеи, как только она зародилась, и я два дня бился над тем, как обеспечить простой в использовании интерфейс.
После этого я создалexutf8.RuneIndexInStringа такжеexutf8.RuneIndexметод, используемый для вычисления позиции конечного индекса указанного количества символов в строке и байтовом срезе соответственно.
я используюexutf8.RuneIndexInStringРеализован тест на перехват строк:
package benchmark
import (
"testing"
"unicode/utf8"
"github.com/thinkeridea/go-extend/exunicode/exutf8"
)
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRuneIndexInString(s string, length int) string {
n, _ := exutf8.RuneIndexInString(s, length)
return s[:n]
}
func BenchmarkSubStrRuneIndexInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneIndexInString(benchmarkSubString, benchmarkSubStringLength)
}
}
Попробуйте запустить его, и я очень доволен результатами:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneIndexInString-8 13546849 82.4 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.213s
лучшая производительностьrange
Повышение на 10%, и я с облегчением снова получил новое усиление, доказав, что оно работает.
Он достаточно эффективен, но не прост в использовании. Мне нужно две строки кода для перехвата строк. Если я хочу перехватывать символы от 10 до 20, мне нужно 4 строки кода. Это не простой в использовании интерфейс для пользователей. Я имею в виду другие лингвистическиеsub_string
метода, я думаю, что мне также следует разработать такой интерфейс для пользователя.
exutf8.RuneSubStringа такжеexutf8.RuneSubЭто метод, который я написал после тщательного обдумывания:
func RuneSubString(s string, start, length int) string
Он имеет три параметра:
-
s
: строка ввода -
start
: Позиция начала перехвата. Если start неотрицательное число, возвращаемая строка будет начинаться с начальной позиции строки и начинать отсчет с 0. Например, в строке «abcdef» символ в позиции 0 — это «a», символ в позиции 2 — «c» и так далее. Если значение start отрицательно, возвращаемая строка будет начинаться с начального символа с конца строки. Если длина строки меньше start, будет возвращена пустая строка. -
length
: Длина, которую нужно урезать. Если указана положительная длина, возвращаемая строка будет начинаться с начала и включать символы до длины (в зависимости от длины строки). Если указана отрицательная длина, то символы длины в конце строки будут опущены (если начало отрицательное, то считается с конца строки). Если start не находится в этом тексте, будет возвращена пустая строка. Если указана длина 0, возвращаемая подстрока будет начинаться с начальной позиции до конца строки.
Я предоставил им псевдонимы, и каждый более склонен использовать их в соответствии со своими привычками использования.strings
пакет, ищущий решение такого рода проблемы, я создалexstrings.SubStringа такжеexbytes.SubКак более легко извлекаемый метод псевдонима.
Наконец, мне нужно сделать еще один тест производительности, чтобы убедиться в его производительности:
package benchmark
import (
"testing"
"github.com/thinkeridea/go-extend/exunicode/exutf8"
)
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRuneSubString(s string, length int) string {
return exutf8.RuneSubString(s, 0, length)
}
func BenchmarkSubStrRuneSubString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneSubString(benchmarkSubString, benchmarkSubStringLength)
}
}
Запустите его, он меня не подводит:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneSubString-8 13309082 83.9 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.215s
Хотя по сравнению сexutf8.RuneIndexInStringОт него отказались, но он предоставляет интерфейс, с которым легко взаимодействовать и использовать. Я думаю, что это должно быть наиболее практичным решением. Его все еще можно использовать, если вы стремитесь к конечному результату.exutf8.RuneIndexInString, это по-прежнему самое быстрое решение.
Суммировать
Когда вы видите код, о котором идет речь, даже если он очень простой, его все равно стоит заглянуть и изучить, это не скучно и скучно, но это будет очень полезно.
с самого начала[]rune
Преобразование типа, чтобы, наконец, построить свои собственные колеса, не только получить16 разулучшение производительности, я также узналutf8
Упаковать и углубитьrange
Обход свойств строки и дляgo-extendСклад содержит ряд практичных и эффективных решений, позволяющих болееgo-extendпользователи получают результаты.
go-extendЭто репозиторий, содержащий практические и эффективные методы.Если у читателей есть хорошие функции и общие и эффективные решения, я надеюсь, что вы без колебаний пришлете их мне.Pull request
, вы также можете использовать этот репозиторий для ускорения реализации функций и повышения производительности.
Перепечатано:
Автор этой статьи: Ци Инь (thinkeridea)
Ссылка на эту статью:blog.thinker idea.com/201910/go/ о…
Заявление об авторских правах: все статьи в этом блоге используются, если не указано иное.Соглашение CC BY 4.0 CNсоглашение. Пожалуйста, укажите источник!