亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Golang中字符串的使用

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

1、字符串編碼

在go中rune是一個unicode編碼點。

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

type rune = int32

當我們打印一個英文字符hello的時候,我們可以得到s的長度為5,因為英文字母代表1個字節(jié):

package main

import "fmt"

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

但是當我們打印嗨的時候,會打印3個字節(jié)。因為使用UTF-8,這個字符會被編碼成3個字節(jié):

package main

import "fmt"

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

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

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

package main

import "fmt"

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

所以我們需要知道:

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

2、字符串遍歷

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

我們看下下面的例子,打印一個字符串中的不同字符和對應(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

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

但是打印的字節(jié)數(shù)是符合我們的預(yù)期的,因為嗨是一個中文占用了3個字節(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

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

那我們?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切片,這樣也會正確打印結(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切片遍歷的過程(中間省略了將字節(jié)轉(zhuǎn)換為rune的過程,需要遍歷字節(jié),復(fù)雜度為O(n))

4、字符串trim

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

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è)取出第一個字符o,判斷是否在xo中,在就移除
  • 重復(fù)步驟1,知道不符合條件

所以就可以解釋通了。當然和它相似的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

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

5、字符串連接

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

我們先看下+號連接的方式:

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

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

還有一種方式就是使用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)建了一個 strings.Builder 結(jié)構(gòu)。 在每次遍歷中,我們通過調(diào)用 WriteString 方法構(gòu)造結(jié)果字符串,該方法將 value 的內(nèi)容附加到其內(nèi)部緩沖區(qū),從而最大限度地減少內(nèi)存復(fù)制。

WriteString 的第二個參數(shù)返回的是error,但是error的值會一直為nil。 之所以有第二個error參數(shù)是因為我 strings.Builder 實現(xiàn)了 io.StringWriter 接口,它包含一個方法: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是一個字節(jié)切片,而里面的實現(xiàn)是使用了append方法。我們知道如果切片很大,使用append會讓底層數(shù)組不斷擴容,影響代碼執(zhí)行效率。

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

所以上面的字符串連接還有一種優(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) // 這里會重新分配b.buf的長度和容量
	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ù)制一份副本出來??梢酝ㄟ^下面的代碼做驗證:

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

    Go如何實現(xiàn)HTTP請求限流示例

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

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

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

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

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

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

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

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

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

    Golang比較兩個slice是否相等的問題

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

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

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

最新評論