《Go語言四十二章經(jīng)》第十章 string

作者:李驍

10.1 字符串介紹

Go 語言中可以使用反引號(hào)或者雙引號(hào)來定義字符串。反引號(hào)表示原生的字符串,即不進(jìn)行轉(zhuǎn)義。

  1. 雙引號(hào):字符串使用雙引號(hào)括起來,其中的相關(guān)的轉(zhuǎn)義字符將被替換。例如:
str := "Hello World! \n Hello Gopher! \n"

輸出:
Hello World! 
Hello Gopher!
  1. 反引號(hào):字符串使用反引號(hào)括起來,其中的相關(guān)的轉(zhuǎn)義字符不會(huì)被替換。例如:
str :=  `Hello World! \n Hello Gopher! \n` 

輸出:
Hello World! \nHello Gopher! \n

雙引號(hào)中的轉(zhuǎn)義字符被替換,而反引號(hào)中原生字符串中的 \n 會(huì)被原樣輸出。

Go 語言中的string類型是一種值類型,存儲(chǔ)的字符串是不可變的,如果要修改string內(nèi)容需要將string轉(zhuǎn)換為[]byte或[]rune,并且修改后的string內(nèi)容是重新分配的。

那么byte和rune的區(qū)別是什么(下面寫法是type別名):

type byte = uint8
type rune = int32

從上面的定義中我們可清楚看到兩者的區(qū)別。

而string類型的零值是為長度為零的字符串,即空字符串 ""。

一般的比較運(yùn)算符(==、!=、<、<=、>=、>)通過在內(nèi)存中按字節(jié)比較來實(shí)現(xiàn)字符串的對(duì)比。你可以通過函數(shù) len() 來獲取字符串所占的字節(jié)長度,例如:len(str)。

字符串的內(nèi)容(純字節(jié))可以通過標(biāo)準(zhǔn)索引法來獲取,在中括號(hào) [] 內(nèi)寫入索引,索引從 0 開始計(jì)數(shù):

字符串 str 的第 1 個(gè)字節(jié):str[0]
第 i 個(gè)字節(jié):str[i - 1]
最后 1 個(gè)字節(jié):str[len(str)-1]

需要注意的是,在Go語言代碼使用 UTF-8 編碼,同時(shí)標(biāo)識(shí)符也支持 Unicode 字符。在標(biāo)準(zhǔn)庫 unicode 包中,提供了對(duì) Unicode 相關(guān)編碼、解碼的支持。而UTF8編碼由Go語言之父Ken Thompson和Rob Pike共同發(fā)明的,現(xiàn)在已經(jīng)是Unicode的標(biāo)準(zhǔn)。

Go語言默認(rèn)使用UTF-8編碼,對(duì)Unicode的支持非常好。但這也帶來一個(gè)問題,也就是很多資料中提到的“獲取字符串長度”的問題。內(nèi)置的len()函數(shù)獲取的是每個(gè)字符的UTF-8編碼的長度和,而不是直接的字符數(shù)量。

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {

    s := "其實(shí)就是rune"
    fmt.Println(len(s))                    // "16"
    fmt.Println(utf8.RuneCountInString(s)) // "8"
}

如字符串含有中文等字符,我們可以看到每個(gè)中文字符的索引值相差3。下面代碼同時(shí)說明了在for range循環(huán)處理字符時(shí),不是按照字節(jié)的方式來處理的。v其實(shí)際上是一個(gè)rune類型值。實(shí)際上,Go語言的range循環(huán)在處理字符串的時(shí)候,會(huì)自動(dòng)隱式解碼UTF8字符串。

package main

import (
    "fmt"
)

func main() {
    s := "Go語言四十二章經(jīng)"
    for k, v := range s {
        fmt.Printf("k:%d,v:%c == %d\n", k, v, v)
    }
}
程序輸出:
k:0,v:G == 71
k:1,v:o == 111
k:2,v:語 == 35821
k:5,v:言 == 35328
k:8,v:四 == 22235
k:11,v:十 == 21313
k:14,v:二 == 20108
k:17,v:章 == 31456
k:20,v:經(jīng) == 32463

注意事項(xiàng):

獲取字符串中某個(gè)字節(jié)的地址的行為是非法的,例如:&str[i]。

10.2 字符串拼接

可以通過以下方式來對(duì)代碼中多行的字符串進(jìn)行拼接。

  • 直接使用運(yùn)算符
str := "Beginning of the string " +
"second part of the string"

由于編譯器行尾自動(dòng)補(bǔ)全分號(hào)的緣故,加號(hào) + 必須放在第一行。
拼接的簡(jiǎn)寫形式 += 也可以用于字符串:

s := "hel" + "lo, "
s += "world!"
fmt.Println(s) // 輸出 “hello, world!”

里面的字符串都是不可變的,每次運(yùn)算都會(huì)產(chǎn)生一個(gè)新的字符串,所以會(huì)產(chǎn)生很多臨時(shí)的無用的字符串,不僅沒有用,還會(huì)給 gc 帶來額外的負(fù)擔(dān),所以性能比較差。

  • fmt.Sprintf()
fmt.Sprintf("%d:%s", 2018, "年")

內(nèi)部使用 []byte 實(shí)現(xiàn),不像直接運(yùn)算符這種會(huì)產(chǎn)生很多臨時(shí)的字符串,但是內(nèi)部的邏輯比較復(fù)雜,有很多額外的判斷,還用到了 interface,所以性能一般。

  • strings.Join()
strings.Join([]string{"hello", "world"}, ", ")

Join會(huì)先根據(jù)字符串?dāng)?shù)組的內(nèi)容,計(jì)算出一個(gè)拼接之后的長度,然后申請(qǐng)對(duì)應(yīng)大小的內(nèi)存,一個(gè)一個(gè)字符串填入,在已有一個(gè)數(shù)組的情況下,這種效率會(huì)很高,但是本來沒有,去構(gòu)造這個(gè)數(shù)據(jù)的代價(jià)也不小。

  • bytes.Buffer
var buffer bytes.Buffer
buffer.WriteString("hello")
buffer.WriteString(", ")
buffer.WriteString("world")

fmt.Print(buffer.String())

這個(gè)比較理想,可以當(dāng)成可變字符使用,對(duì)內(nèi)存的增長也有優(yōu)化,如果能預(yù)估字符串的長度,還可以用 buffer.Grow() 接口來設(shè)置 capacity。

  • strings.Builder
var b1 strings.Builder
b1.WriteString("ABC")
b1.WriteString("DEF")

fmt.Print(b1.String())

strings.Builder 內(nèi)部通過 slice 來保存和管理內(nèi)容。slice 內(nèi)部則是通過一個(gè)指針指向?qū)嶋H保存內(nèi)容的數(shù)組。strings.Builder 同樣也提供了 Grow() 來支持預(yù)定義容量。當(dāng)我們可以預(yù)定義我們需要使用的容量時(shí),strings.Builder 就能避免擴(kuò)容而創(chuàng)建新的 slice 了。strings.Builder是非線程安全,性能上和 bytes.Buffer 相差無幾。

10.3 有關(guān)string處理

標(biāo)準(zhǔn)庫中有四個(gè)包對(duì)字符串處理尤為重要:bytes、strings、strconv和unicode包。

strings包提供了許多如字符串的查詢、替換、比較、截?cái)唷⒉鸱趾秃喜⒌裙δ堋?/p>

bytes包也提供了很多類似功能的函數(shù),但是針對(duì)和字符串有著相同結(jié)構(gòu)的[]byte類型。因?yàn)樽址侵蛔x的,因此逐步構(gòu)建字符串會(huì)導(dǎo)致很多分配和復(fù)制。在這種情況下,使用bytes.Buffer類型將會(huì)更有效,稍后我們將展示。

strconv包提供了布爾型、整型數(shù)、浮點(diǎn)數(shù)和對(duì)應(yīng)字符串的相互轉(zhuǎn)換,還提供了雙引號(hào)轉(zhuǎn)義相關(guān)的轉(zhuǎn)換。

unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等類似功能,它們用于給字符分類。

strings 包提供了很多操作字符串的簡(jiǎn)單函數(shù),通常一般的字符串操作需求都可以在這個(gè)包中找到。下面簡(jiǎn)單舉幾個(gè)例子:

判斷是否以某字符串打頭/結(jié)尾
strings.HasPrefix(s, prefix string) bool
strings.HasSuffix(s, suffix string) bool

字符串分割
strings.Split(s, sep string) []string

返回子串索引
strings.Index(s, substr string) int
strings.LastIndex 最后一個(gè)匹配索引

字符串連接
strings.Join(a []string, sep string) string
另外可以直接使用“+”來連接兩個(gè)字符串

字符串替換
strings.Replace(s, old, new string, n int) string

字符串轉(zhuǎn)化為大小寫
strings.ToUpper(s string) string
strings.ToLower(s string) string

統(tǒng)計(jì)某個(gè)字符在字符串出現(xiàn)的次數(shù)
strings.Count(s, substr string) int

判斷字符串的包含關(guān)系
strings.Contains(s, substr string) bool

本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42

雖然本書中例子都經(jīng)過實(shí)際運(yùn)行,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請(qǐng)您指出;如有建議也歡迎交流。
聯(lián)系郵箱:roteman@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容