欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Golang中字符串的使用

 更新時(shí)間:2022年10月23日 14:10:22   作者:liuyuede123  
這篇文章主要為大家詳細(xì)介紹了Golang中字符串的使用,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,感興趣的小伙伴可以了解一下

1、字符串編碼

在go中rune是一個(gè)unicode編碼點(diǎn)。

我們都知道UTF-8將字符編碼為1-4個(gè)字節(jié),比如我們常用的漢字,UTF-8編碼為3個(gè)字節(jié)。所以rune也是int32的別名。

type rune = int32

當(dāng)我們打印一個(gè)英文字符hello的時(shí)候,我們可以得到s的長(zhǎng)度為5,因?yàn)橛⑽淖帜复?個(gè)字節(jié):

package main

import "fmt"

func main() {
	s := "hello"
	fmt.Println(len(s)) // 5
}

但是當(dāng)我們打印嗨的時(shí)候,會(huì)打印3個(gè)字節(jié)。因?yàn)槭褂肬TF-8,這個(gè)字符會(huì)被編碼成3個(gè)字節(jié):

package main

import "fmt"

func main() {
	s := "嗨"
	fmt.Println(len(s)) // 3
}

所以,我們使用len內(nèi)置函數(shù)輸出的并不是字符數(shù),而是字節(jié)數(shù)。

下面看一個(gè)有趣的例子,我們都知道漢字符使用3個(gè)字節(jié)編碼,分別是0xE5, 0x97, 0xA8。我們運(yùn)行下面代碼會(huì)得到漢字嗨:

package main

import "fmt"

func main() {
	s := string([]byte{0xE5, 0x97, 0xA8})
	fmt.Println(s) // 嗨
}

所以我們需要知道:

  • 字符集是一組字符,而編碼描述了如何將字符集轉(zhuǎn)換為二進(jìn)制
  • 在 Go 中,字符串引用任意字節(jié)的不可變切片
  • Go 源碼使用 UTF-8 編碼。 因此,所有字符串文字都是 UTF-8 字符串。 但是因?yàn)樽址梢园我庾止?jié),如果它是從其他地方(不是源碼)獲得的,則不能保證它是基于 UTF-8 編碼的
  • 使用 UTF-8,一個(gè) Unicode 字符可以編碼為 1 到 4 個(gè)字節(jié)
  • 在 Go 中對(duì)字符串使用 len 返回字節(jié)數(shù),而不是字符數(shù)

2、字符串遍歷

我們?cè)陂_發(fā)中經(jīng)常會(huì)用到對(duì)字符串進(jìn)行遍歷的場(chǎng)景。 也許我們想對(duì)字符串中的每個(gè) rune 執(zhí)行一個(gè)操作,或者實(shí)現(xiàn)一個(gè)自定義函數(shù)來(lái)搜索特定的子字符串。 在這兩種情況下,我們都必須遍歷字符串的不同字符。 但往往會(huì)得到讓我們意想不到的結(jié)果。

我們看下下面的例子,打印一個(gè)字符串中的不同字符和對(duì)應(yīng)的位置:

package main

import "fmt"

func main() {
	s := "h嗨llo"
	for i := range s {
		fmt.Printf("字符位置 %d: %c\n", i, s[i])
	}
	fmt.Printf("len=%d\n", len(s))
}

輸出結(jié)果:

go run 7.go
字符位置 0: h
字符位置 1: å
字符位置 4: l
字符位置 5: l
字符位置 6: o
len=7

我們想要的效果是通過(guò)遍歷字符串,打印出每個(gè)字符的索引。但是我們卻得到了一個(gè)特殊的字符å,其實(shí)我們想要的是嗨。

但是打印的字節(jié)數(shù)是符合我們的預(yù)期的,因?yàn)猷耸且粋€(gè)中文占用了3個(gè)字節(jié),所以len返回的是7。

3、字符串中的字符數(shù)

如果我們想要正確的獲取字符串的字符數(shù),可以使用go中的utf8包:

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "h嗨llo"

	for i := range s {
		fmt.Printf("字符位置 %d: %c\n", i, s[i])
	}
	fmt.Printf("len=%d\n", len(s))
	fmt.Printf(" rune len=%d\n", utf8.RuneCountInString(s)) // 獲取字符數(shù)
}

輸出結(jié)果: 

go run 7.go
字符位置 0: h
字符位置 1: å
字符位置 4: l
字符位置 5: l
字符位置 6: o
len=7
 rune len=5

在這個(gè)例子中,可以看到,我們確實(shí)遍歷了5次,也就是對(duì)應(yīng)字符串的5個(gè)字符。但是我們獲取到的索引其實(shí)是對(duì)應(yīng)每個(gè)字符的起始位置。像下面這樣

那我們?nèi)绾未蛴〕稣_的結(jié)果呢?我們稍微修改下代碼:

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "h嗨llo"

	for i, v := range s { // 此處改為獲取v,可以獲取到字符本身
		fmt.Printf("字符位置 %d: %c\n", i, v)
	}
	fmt.Printf("len=%d\n", len(s))
	fmt.Printf(" rune len=%d\n", utf8.RuneCountInString(s))
}

輸出結(jié)果:

go run 7.go
字符位置 0: h
字符位置 1: 嗨
字符位置 4: l
字符位置 5: l
字符位置 6: o
len=7
 rune len=5

另外一種方法就是把字符串轉(zhuǎn)換成rune切片,這樣也會(huì)正確打印結(jié)果:

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "h嗨llo"
	b := []rune(s)

	for i := range b {
		fmt.Printf("字符位置 %d: %c\n", i, b[i])
	}
	fmt.Printf("len=%d\n", len(s))
	fmt.Printf(" rune len=%d\n", utf8.RuneCountInString(s))
}

輸出結(jié)果:

go run 7.go
字符位置 0: h
字符位置 1: 嗨
字符位置 2: l
字符位置 3: l
字符位置 4: o
len=7
 rune len=5

下面是rune切片遍歷的過(guò)程(中間省略了將字節(jié)轉(zhuǎn)換為rune的過(guò)程,需要遍歷字節(jié),復(fù)雜度為O(n))

4、字符串trim

開發(fā)中我們經(jīng)常會(huì)遇到去除字符串頭部或者尾部字符的操作。比如我們現(xiàn)在有個(gè)字符串xohelloxo,現(xiàn)在我們想去除尾部的xo,可能我們會(huì)像下面這樣寫:

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "xohelloxo"
	s = strings.TrimRight(s, "xo")
	fmt.Println(s)
}

輸出結(jié)果:

go run 7.go
xohell

可以看到這不是我們期望的結(jié)果。我們可以看下TrimRight的工作原理:

  • 從右側(cè)取出第一個(gè)字符o,判斷是否在xo中,在就移除
  • 重復(fù)步驟1,知道不符合條件

所以就可以解釋通了。當(dāng)然和它相似的TrimLeft和Trim也是一樣的原理。

如果我們只想刪除最后xo可以使用TrimSuffix函數(shù):

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "xohelloxo"
	s = strings.TrimSuffix(s, "xo")
	fmt.Println(s)
}

輸出結(jié)果:

go run 7.go
xohello

當(dāng)然也有對(duì)應(yīng)的從前面刪除的函數(shù)TrimPrefix。

5、字符串連接

開發(fā)中我們經(jīng)常會(huì)用到連接字符串的操作,在go中我們一般有2種方式。

我們先看下+號(hào)連接的方式:

package main

import (
	"fmt"
	"strings"
)

func implode(values []string, operate string) string {
	s := ""
	for _, value := range values {
		s += operate
		s += value
	}
	s = strings.TrimPrefix(s, operate)
	return s
}

func main() {
	a := []string{"hello", "world"}
	s := implode(a, " ")
	fmt.Println(s)
}

輸出結(jié)果:

go run 7.go
 hello world

這種方式的缺點(diǎn)就是,由于字符串的不變性,每次+號(hào)賦值的時(shí)候s不會(huì)被更新,而是重新分配內(nèi)存,所以這種方式對(duì)性能有很大影響。

還有一種方式就是使用strings.Builder:

package main

import (
	"fmt"
	"strings"
)

func implode(values []string, operate string) string {
	sb := strings.Builder{}
	for _, value := range values {
		_, _ = sb.WriteString(operate)
		_, _ = sb.WriteString(value)
	}
	s := strings.TrimPrefix(sb.String(), operate)
	return s
}

func main() {
	a := []string{"hello", "world"}
	s := implode(a, " ")
	fmt.Println(s)
}

輸出結(jié)果:

go run 7.go
hello world

首先,我們創(chuàng)建了一個(gè) strings.Builder 結(jié)構(gòu)。 在每次遍歷中,我們通過(guò)調(diào)用 WriteString 方法構(gòu)造結(jié)果字符串,該方法將 value 的內(nèi)容附加到其內(nèi)部緩沖區(qū),從而最大限度地減少內(nèi)存復(fù)制。

WriteString 的第二個(gè)參數(shù)返回的是error,但是error的值會(huì)一直為nil。 之所以有第二個(gè)error參數(shù)是因?yàn)槲?strings.Builder 實(shí)現(xiàn)了 io.StringWriter 接口,它包含一個(gè)方法:WriteString(s string) (n int, err error)。

我們看下WriteString的內(nèi)部是什么樣的:

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

我們可以看到b.buf是一個(gè)字節(jié)切片,而里面的實(shí)現(xiàn)是使用了append方法。我們知道如果切片很大,使用append會(huì)讓底層數(shù)組不斷擴(kuò)容,影響代碼執(zhí)行效率。

我們知道解決這個(gè)問(wèn)題的方法是,如果事先知道切片的大小,我們可以在初始化的時(shí)候就分配好切片的容量。

所以上面的字符串連接還有一種優(yōu)化方案:

package main

import (
	"fmt"
	"strings"
)

func implode(values []string, operate string) string {
	total := 0
	for i := 0; i < len(values); i++ {
		total += len(values[i])
	}
	total += len(operate) * len(values)
	sb := strings.Builder{}
	sb.Grow(total) // 這里會(huì)重新分配b.buf的長(zhǎng)度和容量
	for _, value := range values {
		_, _ = sb.WriteString(operate)
		_, _ = sb.WriteString(value)
	}
	s := strings.TrimPrefix(sb.String(), operate)
	return s
}

func main() {
	a := []string{"hello", "world"}
	s := implode(a, " ")
	fmt.Println(s)
}

輸出結(jié)果:

go run 7.go
hello world

6、字節(jié)切片轉(zhuǎn)字符串

需要明確的是,字節(jié)切片轉(zhuǎn)換成字符串,需要復(fù)制一份副本出來(lái)。可以通過(guò)下面的代碼做驗(yàn)證:

b := []byte{'a', 'b', 'c'}
s := string(b)
b[1] = 'x'
fmt.Println(s)

事實(shí)上,上面將會(huì)輸出abc而不是axc。所以字節(jié)切片到字符串的轉(zhuǎn)換是有開銷的。

但是我們開發(fā)中經(jīng)常用到的包iio.Read之類的,入?yún)⒒蛘叻祷亟?jīng)常是字節(jié)切片類型。而我們調(diào)用這些函數(shù)時(shí)經(jīng)常是以字符串的形式,導(dǎo)致我們不得不做一些字節(jié)切片刀字符串的轉(zhuǎn)換。

所以結(jié)論是,當(dāng)我們需要使用字符串作為入?yún)⒒蛘叻祷貢r(shí),我們首先要考慮的是能用字節(jié)切片的就用字節(jié)切片。

到此這篇關(guān)于詳解Golang中字符串的使用的文章就介紹到這了,更多相關(guān)Golang字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go疑難雜癥講解之為什么nil不等于nil

    Go疑難雜癥講解之為什么nil不等于nil

    在日常開發(fā)中,可能一不小心就會(huì)掉進(jìn)?Go?語(yǔ)言的某些陷阱里,而本文要介紹的?nil?≠?nil?問(wèn)題,感興趣的小伙伴可以跟隨小編一起了解一下
    2022-10-10
  • Go語(yǔ)言每天必學(xué)之switch語(yǔ)句

    Go語(yǔ)言每天必學(xué)之switch語(yǔ)句

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言每天必學(xué)之switch語(yǔ)句的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Golang實(shí)現(xiàn)定時(shí)任務(wù)的幾種方法小結(jié)

    Golang實(shí)現(xiàn)定時(shí)任務(wù)的幾種方法小結(jié)

    在 Golang 開發(fā)中,定時(shí)任務(wù)是常見的需求,本文將介紹幾種在 Golang 中實(shí)現(xiàn)定時(shí)任務(wù)的方法,包括 time 包的定時(shí)器、ticker,以及第三方庫(kù) cron,并通過(guò)示例代碼展示它們的使用方式,需要的朋友可以參考下
    2024-01-01
  • Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例

    Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例

    本篇文章主要介紹了Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • go?colly?爬蟲實(shí)現(xiàn)示例

    go?colly?爬蟲實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了go?colly?爬蟲實(shí)現(xiàn)示例,效果是根據(jù)輸入的瀏覽器cookie及excel必要行列號(hào),從excel中讀取公司名稱,查詢公司法人及電話號(hào)碼。并寫回到excel中指定行
    2022-09-09
  • Go項(xiàng)目與Docker結(jié)合實(shí)現(xiàn)高效部署深入探究

    Go項(xiàng)目與Docker結(jié)合實(shí)現(xiàn)高效部署深入探究

    在現(xiàn)代軟件開發(fā)中,使用Docker部署應(yīng)用程序已經(jīng)成為一種標(biāo)準(zhǔn)實(shí)踐,本文將深入探討如何將Go項(xiàng)目與Docker結(jié)合,實(shí)現(xiàn)高效、可靠的部署過(guò)程,通過(guò)詳細(xì)的步驟和豐富的示例,你將能夠迅速掌握這一流程
    2023-12-12
  • go依賴注入管理工具wire的使用方法

    go依賴注入管理工具wire的使用方法

    本文主要介紹了如何使用go wire管理依賴,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-09-09
  • Golang拾遺之實(shí)現(xiàn)一個(gè)不可復(fù)制類型詳解

    Golang拾遺之實(shí)現(xiàn)一個(gè)不可復(fù)制類型詳解

    在這篇文章中我們將實(shí)現(xiàn)一個(gè)無(wú)法被復(fù)制的類型,順便加深對(duì)引用類型、值傳遞以及指針的理解。文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2023-02-02
  • Golang比較兩個(gè)slice是否相等的問(wèn)題

    Golang比較兩個(gè)slice是否相等的問(wèn)題

    本文主要介紹了Golang比較兩個(gè)slice是否相等的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Golang使用gorm實(shí)現(xiàn)分頁(yè)功能的示例代碼

    Golang使用gorm實(shí)現(xiàn)分頁(yè)功能的示例代碼

    在提供列表接口時(shí)一般要用到分頁(yè),對(duì)于存儲(chǔ)在某些數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行分頁(yè)起來(lái)非常的方便,下文給出一個(gè)通過(guò)gorm進(jìn)行分頁(yè)并通過(guò)http返回?cái)?shù)據(jù)的例子,感興趣的小伙幫跟著小編一起來(lái)看看吧
    2024-10-10

最新評(píng)論