最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言如何高效的進行字符串拼接(6種方式對比分析)

 更新時間:2022年08月22日 16:45:51   作者:Golang夢工廠  
本文主要介紹了Go語言如何高效的進行字符串拼接(6種方式對比分析),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

日常業(yè)務開發(fā)中離不開字符串的拼接操作,不同語言的字符串實現(xiàn)方式都不同,在Go語言中就提供了6種方式進行字符串拼接,那這幾種拼接方式該如何選擇呢?使用那個更高效呢?本文我們就一起來分析一下。

本文使用Go語言版本:1.17.1

string類型

我們首先來了解一下Go語言中string類型的結(jié)構(gòu)定義,先來看一下官方定義:

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

string是一個8位字節(jié)的集合,通常但不一定代表UTF-8編碼的文本。string可以為空,但是不能為nil。string的值是不能改變的。

string類型本質(zhì)也是一個結(jié)構(gòu)體,定義如下:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

stringStructslice還是很相似的,str指針指向的是某個數(shù)組的首地址,len代表的就是數(shù)組長度。怎么和slice這么相似,底層指向的也是數(shù)組,是什么數(shù)組呢?我們看看他在實例化時調(diào)用的方法:

//go:nosplit
func gostringnocopy(str *byte) string {
	ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
	s := *(*string)(unsafe.Pointer(&ss))
	return s
}

入?yún)⑹且粋€byte類型的指針,從這我們可以看出string類型底層是一個byte類型的數(shù)組,所以我們可以畫出這樣一個圖片:

string類型本質(zhì)上就是一個byte類型的數(shù)組,在Go語言中string類型被設(shè)計為不可變的,不僅是在Go語言,其他語言中string類型也是被設(shè)計為不可變的,這樣的好處就是:在并發(fā)場景下,我們可以在不加鎖的控制下,多次使用同一字符串,在保證高效共享的情況下而不用擔心安全問題。

string類型雖然是不能更改的,但是可以被替換,因為stringStruct中的str指針是可以改變的,只是指針指向的內(nèi)容是不可以改變的,也就說每一個更改字符串,就需要重新分配一次內(nèi)存,之前分配的空間會被gc回收。

關(guān)于string類型的知識點就描述這么多,方便我們后面分析字符串拼接。

字符串拼接的6種方式及原理

原生拼接方式"+"

Go語言原生支持使用+操作符直接對兩個字符串進行拼接,使用例子如下:

var s string
s += "asong"
s += "真帥"

這種方式使用起來最簡單,基本所有語言都有提供這種方式,使用+操作符進行拼接時,會對字符串進行遍歷,計算并開辟一個新的空間來存儲原來的兩個字符串。

字符串格式化函數(shù)fmt.Sprintf

Go語言中默認使用函數(shù)fmt.Sprintf進行字符串格式化,所以也可使用這種方式進行字符串拼接:

str := "asong"
str = fmt.Sprintf("%s%s", str, str)

fmt.Sprintf實現(xiàn)原理主要是使用到了反射,具體源碼分析因為篇幅的原因就不在這里詳細分析了,看到反射,就會產(chǎn)生性能的損耗,你們懂得!?。?/p>

Strings.builder

Go語言提供了一個專門操作字符串的庫strings,使用strings.Builder可以進行字符串拼接,提供了writeString方法拼接字符串,使用方式如下:

var builder strings.Builder
builder.WriteString("asong")
builder.String()

strings.builder的實現(xiàn)原理很簡單,結(jié)構(gòu)如下:

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte // 1
}

addr字段主要是做copycheckbuf字段是一個byte類型的切片,這個就是用來存放字符串內(nèi)容的,提供的writeString()方法就是像切片buf中追加數(shù)據(jù):

func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, s...)
	return len(s), nil
}

提供的String方法就是將[]]byte轉(zhuǎn)換為string類型,這里為了避免內(nèi)存拷貝的問題,使用了強制轉(zhuǎn)換來避免內(nèi)存拷貝:

func (b *Builder) String() string {
	return *(*string)(unsafe.Pointer(&b.buf))
}

bytes.Buffer

因為string類型底層就是一個byte數(shù)組,所以我們就可以Go語言的bytes.Buffer進行字符串拼接。bytes.Buffer是一個一個緩沖byte類型的緩沖器,這個緩沖器里存放著都是byte。使用方式如下:

buf := new(bytes.Buffer)
buf.WriteString("asong")
buf.String()

bytes.buffer底層也是一個[]byte切片,結(jié)構(gòu)體如下:

type Buffer struct {
	buf      []byte // contents are the bytes buf[off : len(buf)]
	off      int    // read at &buf[off], write at &buf[len(buf)]
	lastRead readOp // last read operation, so that Unread* can work correctly.
}

因為bytes.Buffer可以持續(xù)向Buffer尾部寫入數(shù)據(jù),從Buffer頭部讀取數(shù)據(jù),所以off字段用來記錄讀取位置,再利用切片的cap特性來知道寫入位置,這個不是本次的重點,重點看一下WriteString方法是如何拼接字符串的:

func (b *Buffer) WriteString(s string) (n int, err error) {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(len(s))
	if !ok {
		m = b.grow(len(s))
	}
	return copy(b.buf[m:], s), nil
}

切片在創(chuàng)建時并不會申請內(nèi)存塊,只有在往里寫數(shù)據(jù)時才會申請,首次申請的大小即為寫入數(shù)據(jù)的大小。如果寫入的數(shù)據(jù)小于64字節(jié),則按64字節(jié)申請。采用動態(tài)擴展slice的機制,字符串追加采用copy的方式將追加的部分拷貝到尾部,copy是內(nèi)置的拷貝函數(shù),可以減少內(nèi)存分配。

但是在將[]byte轉(zhuǎn)換為string類型依舊使用了標準類型,所以會發(fā)生內(nèi)存分配:

func (b *Buffer) String() string {
	if b == nil {
		// Special case, useful in debugging.
		return "<nil>"
	}
	return string(b.buf[b.off:])
}

strings.join

Strings.join方法可以將一個string類型的切片拼接成一個字符串,可以定義連接操作符,使用如下:

baseSlice := []string{"asong", "真帥"}
strings.Join(baseSlice, "")

strings.join也是基于strings.builder來實現(xiàn)的,代碼如下:

func Join(elems []string, sep string) string {
	switch len(elems) {
	case 0:
		return ""
	case 1:
		return elems[0]
	}
	n := len(sep) * (len(elems) - 1)
	for i := 0; i < len(elems); i++ {
		n += len(elems[i])
	}

	var b Builder
	b.Grow(n)
	b.WriteString(elems[0])
	for _, s := range elems[1:] {
		b.WriteString(sep)
		b.WriteString(s)
	}
	return b.String()
}

唯一不同在于在join方法內(nèi)調(diào)用了b.Grow(n)方法,這個是進行初步的容量分配,而前面計算的n的長度就是我們要拼接的slice的長度,因為我們傳入切片長度固定,所以提前進行容量分配可以減少內(nèi)存分配,很高效。

切片append

因為string類型底層也是byte類型數(shù)組,所以我們可以重新聲明一個切片,使用append進行字符串拼接,使用方式如下:

buf := make([]byte, 0)
base = "asong"
buf = append(buf, base...)
string(base)

如果想減少內(nèi)存分配,在將[]byte轉(zhuǎn)換為string類型時可以考慮使用強制轉(zhuǎn)換。

Benchmark對比

上面我們總共提供了6種方法,原理我們基本知道了,那么我們就使用Go語言中的Benchmark來分析一下到底哪種字符串拼接方式更高效。我們主要分兩種情況進行分析:

  • 少量字符串拼接
  • 大量字符串拼接

因為代碼量有點多,下面只貼出分析結(jié)果,詳細代碼已經(jīng)上傳githubhttps://github.com/asong2020/Golang_Dream/tree/master/code_demo/string_join

我們先定義一個基礎(chǔ)字符串:

var base  = "123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASFGHJKLZXCVBNM"

少量字符串拼接的測試我們就采用拼接一次的方式驗證,base拼接base,因此得出benckmark結(jié)果:

goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/string_join/once
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSumString-16           21338802                49.19 ns/op          128 B/op          1 allocs/op
BenchmarkSprintfString-16        7887808               140.5 ns/op           160 B/op          3 allocs/op
BenchmarkBuilderString-16       27084855                41.39 ns/op          128 B/op          1 allocs/op
BenchmarkBytesBuffString-16      9546277               126.0 ns/op           384 B/op          3 allocs/op
BenchmarkJoinstring-16          24617538                48.21 ns/op          128 B/op          1 allocs/op
BenchmarkByteSliceString-16     10347416               112.7 ns/op           320 B/op          3 allocs/op
PASS
ok      asong.cloud/Golang_Dream/code_demo/string_join/once     8.412s

大量字符串拼接的測試我們先構(gòu)建一個長度為200的字符串切片:

var baseSlice []string
for i := 0; i < 200; i++ {
		baseSlice = append(baseSlice, base)
}

然后遍歷這個切片不斷的進行拼接,因為可以得出benchmark:

goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/string_join/muliti
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSumString-16                       7396            163612 ns/op         1277713 B/op        199 allocs/op
BenchmarkSprintfString-16                   5946            202230 ns/op         1288552 B/op        600 allocs/op
BenchmarkBuilderString-16                 262525              4638 ns/op           40960 B/op          1 allocs/op
BenchmarkBytesBufferString-16             183492              6568 ns/op           44736 B/op          9 allocs/op
BenchmarkJoinstring-16                    398923              3035 ns/op           12288 B/op          1 allocs/op
BenchmarkByteSliceString-16               144554              8205 ns/op           60736 B/op         15 allocs/op
PASS
ok      asong.cloud/Golang_Dream/code_demo/string_join/muliti   10.699s

結(jié)論

通過兩次benchmark對比,我們可以看到當進行少量字符串拼接時,直接使用+操作符進行拼接字符串,效率還是挺高的,但是當要拼接的字符串數(shù)量上來時,+操作符的性能就比較低了;函數(shù)fmt.Sprintf還是不適合進行字符串拼接,無論拼接字符串數(shù)量多少,性能損耗都很大,還是老老實實做他的字符串格式化就好了;strings.Builder無論是少量字符串的拼接還是大量的字符串拼接,性能一直都能穩(wěn)定,這也是為什么Go語言官方推薦使用strings.builder進行字符串拼接的原因,在使用strings.builder時最好使用Grow方法進行初步的容量分配,觀察strings.join方法的benchmark就可以發(fā)現(xiàn),因為使用了grow方法,提前分配好內(nèi)存,在字符串拼接的過程中,不需要進行字符串的拷貝,也不需要分配新的內(nèi)存,這樣使用strings.builder性能最好,且內(nèi)存消耗最小。bytes.Buffer方法性能是低于strings.builder的,bytes.Buffer 轉(zhuǎn)化為字符串時重新申請了一塊空間,存放生成的字符串變量,不像strings.buidler這樣直接將底層的 []byte 轉(zhuǎn)換成了字符串類型返回,這就占用了更多的空間。

同步最后分析的結(jié)論:

無論什么情況下使用strings.builder進行字符串拼接都是最高效的,不過要主要使用方法,記得調(diào)用grow進行容量分配,才會高效。strings.join的性能約等于strings.builder,在已經(jīng)字符串slice的時候可以使用,未知時不建議使用,構(gòu)造切片也是有性能損耗的;如果進行少量的字符串拼接時,直接使用+操作符是最方便也是性能最高的,可以放棄strings.builder的使用。

綜合對比性能排序:

strings.joinstrings.builder > bytes.buffer > []byte轉(zhuǎn)換string > "+" > fmt.sprintf

總結(jié)

本文我們針對6種字符串的拼接方式進行介紹,并通過benckmark對比了效率,無論什么時候使用strings.builder都不會錯,但是在少量字符串拼接時,直接+也就是更優(yōu)的方式,具體業(yè)務場景具體分析,不要一概而論。

文中代碼已上傳githubhttps://github.com/asong2020/Golang_Dream/tree/master/code_demo/string_join

到此這篇關(guān)于Go語言如何高效的進行字符串拼接(6種方式對比分析)的文章就介紹到這了,更多相關(guān)Go 字符串拼接內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang中slice刪除元素的性能對比

    Golang中slice刪除元素的性能對比

    go沒有對刪除切片元素提供專用的語法或者接口,需要使用切片本身的特性來刪除元素,下面這篇文章主要給大家介紹了關(guān)于Golang中slice刪除元素的性能對比,需要的朋友可以參考下
    2022-06-06
  • Go?1.21.0?新增結(jié)構(gòu)化日志記錄標準庫log/slog使用詳解

    Go?1.21.0?新增結(jié)構(gòu)化日志記錄標準庫log/slog使用詳解

    這篇文章主要為大家介紹了Go?1.21.0?新增結(jié)構(gòu)化日志記錄標準庫log/slog使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • 淺談Go數(shù)組比切片好在哪

    淺談Go數(shù)組比切片好在哪

    Go1.17 會正式支持切片轉(zhuǎn)換到數(shù)據(jù),不再需要用以前那種騷辦法了,本文就談談Go數(shù)組比切片好在哪,感興趣的可以了解一下
    2021-09-09
  • Golang中空的切片轉(zhuǎn)化成 JSON 后變?yōu)?nbsp;null 問題的解決方案

    Golang中空的切片轉(zhuǎn)化成 JSON 后變?yōu)?nbsp;null 問題的解決方案

    在 Golang 中,經(jīng)常需要將其他類型(例如 slice、map、struct 等類型)的數(shù)據(jù)轉(zhuǎn)化為 JSON 格式,有時候轉(zhuǎn)化的結(jié)果并不是預期中的,例如將一個空的切片轉(zhuǎn)化為 JSON 時,會變成"null",所以本文將給大家介紹一下解決方法,需要的朋友可以參考下
    2023-09-09
  • golang敏感詞過濾的實現(xiàn)

    golang敏感詞過濾的實現(xiàn)

    本文主要介紹了golang敏感詞過濾的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • Golang語言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實例

    Golang語言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實例

    這篇文章主要為大家介紹了Golang語言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言切片常考的面試真題解析

    Go語言切片??嫉拿嬖囌骖}解析

    了解最新的Go語言面試題型,讓面試不再是難事,下面這篇文章主要給大家介紹了關(guān)于Go語言切片面試常考的一些問題,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-02-02
  • Go語言中Slice常見陷阱與避免方法詳解

    Go語言中Slice常見陷阱與避免方法詳解

    這篇文章主要為大家詳細介紹的是 Go 語言中的 Slice 的常見陷阱以及如何避免這些錯誤,文中的示例代碼講解詳細,感興趣的小伙伴可以學習一下
    2023-02-02
  • 詳解Golang如何在編譯時注入版本信息

    詳解Golang如何在編譯時注入版本信息

    這篇文章主要為大家詳細介紹了Golang如何在編譯時實現(xiàn)注入版本信息,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的可以了解一下
    2023-06-06
  • 使用go語言實現(xiàn)Redis持久化的示例代碼

    使用go語言實現(xiàn)Redis持久化的示例代碼

    redis 是一個內(nèi)存數(shù)據(jù)庫,如果你把進程殺掉,那么里面存儲的數(shù)據(jù)都會消失,那么這篇文章就是來解決 redis 持久化的問題,本文給大家介紹了使用go語言實現(xiàn)Redis持久化,需要的朋友可以參考下
    2024-07-07

最新評論

习水县| 洛南县| 通化县| 商河县| 招远市| 鲁山县| 大英县| 营口市| 石狮市| 德昌县| 德昌县| 滨海县| 陆丰市| 太白县| 卓资县| 凤阳县| 韶关市| 永泰县| 康马县| 保定市| 兴城市| 安吉县| 宜章县| 汉阴县| 汉川市| 勃利县| 宜兰县| 六枝特区| 斗六市| 延长县| 小金县| 宁阳县| 凌海市| 鹤山市| 隆尧县| 九龙坡区| 肥东县| 荔波县| 舞阳县| 吉隆县| 神池县|