Объединение строк в golang

Одной из частых операций может оказаться объединения(concatenation) строк, есть много библиотек для решения задач, мы рассмотрим несколько самых распространенных примеров.

В стандартной библиотеке fmt есть метод Sprintf(format string, a ...interface{}) string в качестве первого аргумента он принимает формат, в нашем случае это просто %s %s для двух строк. Также есть метод strings.Join, тут все просто slice строк и разделитель. Еще можно использовать тип bytes.Buffer с его методами WriteString. Ну и не забываем про string+" "+ string".

Запустим для всего этого benchmark для 100 строк, результат для него будет такой

BenchmarkSfmt-4       	  100000     12207 ns/op    4705 B/op     102 allocs/op
BenchmarkBuff-4        	  500000      2516 ns/op    6928 B/op       7 allocs/op
BenchmarkJoin-4        	 1000000      1041 ns/op    1792 B/op       1 allocs/op
BenchmarkConc100-4    	 1000000      1367 ns/op    1792 B/op       1 allocs/op

Сразу видно что fmt.Sprintf меньше всего для этого подходит, он скорее предназначен когда нам надо больше чем просто объединить строки, с его возможностями можете ознакомится в документации. У него есть также аналоги как например log.Printf,fmt.Errorf и тп. Buffer почти в двое проигрывает специализированным решениям, там прямая зависимость от строк , к примеру пустой срез будет иметь такой результат

BenchmarkBuff-4       	 3000000       405 ns/op       0 B/op       0 allocs/op
BenchmarkJoin-4       	 2000000       787 ns/op       0 B/op       0 allocs/op
BenchmarkConc100-4    	10000000       134 ns/op       0 B/op       0 allocs/op

а при длине строки в 322 символа

BenchmarkBuff-4   	  100000     15442 ns/op  125088 B/op       9 allocs/op
BenchmarkJoin-4   	  300000      4451 ns/op   32768 B/op       1 allocs/op
BenchmarkConc100-4	  300000      4497 ns/op   32768 B/op       1 allocs/op

Если вам нужно объединить строки то используйте + или strings.Join. Не используете сложение строк в foreach иначе получите результат сравнимый с Sprintf

BenchmarkFor100-4     	  100000     14175 ns/op   84848 B/op      99 allocs/op
BenchmarkConc100-4    	 1000000      1328 ns/op    1792 B/op       1 allocs/op

А что если нам нужно сложить не только строки? Для этого надо будет преобразовать типы в строку и точна также сложить, есть пакет strconv мы будем использовать методы FormatFloat и Itoa

BenchmarkIfmt-4       	  100000     12045 ns/op    4289 B/op     102 allocs/op
BenchmarkFfmt-4       	  100000     23098 ns/op    4289 B/op     102 allocs/op
BenchmarkJoinInt-4    	  200000      5824 ns/op    5248 B/op     101 allocs/op
BenchmarkJoinFloat-4  	  100000     19269 ns/op    8448 B/op     201 allocs/op
BenchmarkConc100Int-4 	  200000      6017 ns/op    5248 B/op     101 allocs/op
BenchmarkConc100Float-4   100000     18470 ns/op    8448 B/op     201 allocs/op

Тут думаю понятно что выигрыша мы на типе float вообще не получаем, но учитывайте что используются аргументы 'g', -1, 64.

Выводы

Надеюсь вы перестанете везде использовать методы fmt.Sprintf("err: %s, message: %s", err,message)" да возможно они более читабельные, или если вас не устраивает использование strings.Join([]string{"err:",err.Error(),"message:",message}, "") можно сделать в проекте func join(in ...string) string. По поводу вывода строковой информации о других типов тут уже все зависит от задачи, если будет float или смешанный вариант то я наверное предпочту читабельный код. Если вы для логов делаете вывод то возможно стоит обратить внимание на пакет go.uber.org/zap там к примеру есть удобные методы SugaredLogger.Warnw которые позволяют использовать основные типы, и как бонус искать потом по этим полям если вы используете json. Данная статья не последняя инстанция, если у вас есть свои выводы подкрепленные Benchmark, оставляйте ссылку на gist, уверен будет интересно всем. Все основные результаты можно посмотреть на github.