Golang 學習筆記三 字符串 映射字典

一、字符串

參考
【golang】淺析rune纠永,byte
golang string和[]byte的對比

在源碼中builtin.go中使用了類型別名(基礎知識可參考Golang關鍵字--type 類型定義

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

// 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

rune類型的底層類型是int32類型钢属,而byte類型的底層類型是int8類型,這決定了rune能比byte表達更多的數(shù)。

字符串是一系列8位字節(jié)的集合睡蟋,通常但不一定代表UTF-8編碼的文本。字符串可以為空枷颊,但不能為nil戳杀。而且字符串的值是不能改變的。不同的語言字符串有不同的實現(xiàn)夭苗,在go的源碼中src/runtime/string.go信卡,string的定義如下:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

可以看到str其實是個指針,指向某個數(shù)組的首地址题造,另一個字段是len長度傍菇。那到這個數(shù)組是什么呢? 在實例化這個stringStruct的時候:

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

哈哈界赔,其實就是byte數(shù)組丢习,而且要注意string其實就是個struct牵触。

在unicode中,一個中文占兩個字節(jié)咐低,utf-8中一個中文占三個字節(jié)揽思,golang默認的編碼是utf-8編碼,因此默認一個中文占三個字節(jié)见擦,但是golang中的字符串底層實際上是一個byte數(shù)組钉汗。因此可能會出現(xiàn)下面這種奇怪的情況

str := "hello 世界"
fmt.Println(len(str)) //12

我們期望得到的結果應該是8,原因是golang中的string底層是由一個byte數(shù)組實現(xiàn)的鲤屡,而golang默認的編碼是utf-8损痰,因此在這里一個中文字符占3個字節(jié),所以獲得的長度是12酒来,想要獲得我們想要的結果也很簡單卢未,golang中的unicode/utf8包提供了用utf-8獲取長度的方法

str := "hello 世界"
fmt.Println(utf8.RuneCountInString(str)) //8

上面說了byte類型實際上是一個int8類型,int8適合表達ascii編碼的字符堰汉,而int32可以表達更多的數(shù)尝丐,可以更容易的處理unicode字符,因此衡奥,我們可以通過rune類型來處理unicode字符

str := "hello 世界"
str2 := []rune(str)
fmt.Println(len(str2)) //8

這里會將申請一塊內存爹袁,然后將str的內容復制到這塊內存,實際上這塊內存是一個rune類型的切片矮固,而str2拿到的是一個rune類型的切片的引用失息,我們可以很容易的證明這是一個引用

str := "hello 世界"
str2 := []rune(str)
t := str2
t[0] = 'w'
fmt.Println(string(str2)) //“wello 世界”

通過把str2賦值給t,t上改變的數(shù)據(jù)档址,實際上是改變的是t指向的rune切片盹兢,因此,str也會跟著改變

對于字符串守伸,看一下如何遍歷吧绎秒,也許你會覺得遍歷輕而易舉,然而剛接觸golang的時候尼摹,如果這樣遍歷字符串见芹,那么將是非常糟糕的

str := "hello 世界"

for i := 0;i < len(str);i++ {
    fmt.Println(string(str[i]))
}

輸出:

h
e
l
l
o

?
?
?

如何解決這個問題呢?

第一個解決方法是用range循環(huán)

str := "hello 世界"

for _,v := range str {
    fmt.Println(string(v))
}

輸出

h
e
l
l
o

界

原因是range會隱式的unicode解碼

第二個方法是將str 轉換為rune類型的切片蠢涝,這個方法上面已經(jīng)說過了玄呛,這里就不再贅述了

當然還有很多方法,其本質都是將byte向rune上靠

結論:
byte 等同于int8和二,常用來處理ascii字符
rune 等同于int32,常用來處理unicode或utf-8字符

以下部分參考《快學 Go 語言》第 7 課 —— 字符串
go語言中是按utf8來存儲字符串的徘铝,從在線查看字符編碼中可以看到“嘻哈”的utf8編碼是e598bb e59388。

image.png

結構非常類似于切片,區(qū)別是頭部少了一個容量字段

1.通過下標來訪問內部字節(jié)數(shù)組具體位置上的字節(jié)

func main() {
    var s = "嘻哈china"
    for i:=0;i<len(s);i++ {
        fmt.Printf("%x ", s[i])
    }

}

-----------
e5 98 bb e5 93 88 63 68 69 6e 61

2.對字符串進行 range 遍歷惕它,每次迭代出兩個變量 codepoint 和 runeValue怕午。codepoint 表示字符起始位置,runeValue 表示對應的 unicode 編碼(類型是 rune)淹魄。

package main

import "fmt"

func main() {
    var s = "嘻哈china"
    for codepoint, runeValue := range s {
        fmt.Printf("%d %d ", codepoint, int32(runeValue))
    }
}

-----------
0 22075 3 21704 6 99 7 104 8 105 9 110 10 97

對字符串進行 range 遍歷郁惜,每次迭代出兩個變量 codepoint 和 runeValue。codepoint 表示字符起始位置揭北,runeValue 表示對應的 unicode 編碼(類型是 rune)。

3.字符串是只讀的
你可以使用下標來讀取字符串指定位置的字節(jié)吏颖,但是你無法修改這個位置上的字節(jié)內容搔体。如果你嘗試使用下標賦值,編譯器在語法上直接拒絕你半醉。

package main

func main() {
    var s = "hello"
    s[0] = 'H'
}
--------
./main.go:5:7: cannot assign to s[0]

字符串的值是不能改變的疚俱,這句話其實不完整,應該說字符串的值不能被更改缩多,但可以被替換呆奕。 還是以string的結構體來解釋吧,所有的string在底層都是這樣的一個結構體stringStruct{str: str_point, len: str_len}衬吆,string結構體的str指針指向的是一個字符常量的地址梁钾, 這個地址里面的內容是不可以被改變的,因為它是只讀的逊抡,但是這個指針可以指向不同的地址姆泻,我們來對比一下string、[]byte類型重新賦值的區(qū)別:

s := "A1" // 分配存儲"A1"的內存空間冒嫡,s結構體里的str指針指向這快內存
s = "A2"  // 重新給"A2"的分配內存空間拇勃,s結構體里的str指針指向這快內存

其實[]byte和string的差別是更改變量的時候array的內容可以被更改。

s := []byte{1} // 分配存儲1數(shù)組的內存空間孝凌,s結構體的array指針指向這個數(shù)組方咆。
s = []byte{2}  // 將array的內容改為2

因為string的指針指向的內容是不可以更改的,所以每更改一次字符串蟀架,就得重新分配一次內存瓣赂,之前分配空間的還得由gc回收,這是導致string操作低效的根本原因片拍。

4.字節(jié)切片和字符串的相互轉換
在使用 Go 語言進行網(wǎng)絡編程時钩述,經(jīng)常需要將來自網(wǎng)絡的字節(jié)流轉換成內存字符串,同時也需要將內存字符串轉換成網(wǎng)絡字節(jié)流穆碎。Go 語言直接內置了字節(jié)切片和字符串的相互轉換語法牙勘。

package main

import "fmt"

func main() {
    var s1 = "hello world"
    var b = []byte(s1)  // 字符串轉字節(jié)切片
    var s2 = string(b)  // 字節(jié)切片轉字符串
    fmt.Println(b)
    fmt.Println(s2)
}

--------
[104 101 108 108 111 32 119 111 114 108 100]
hello world

從節(jié)省內存的角度出發(fā),你可能會認為字節(jié)切片和字符串的底層字節(jié)數(shù)組是共享的。但是事實不是這樣的方面,底層字節(jié)數(shù)組會被拷貝放钦。如果內容很大,那么轉換操作是需要一定成本的恭金。

那為什么需要拷貝呢操禀?因為字節(jié)切片的底層數(shù)組內容是可以修改的,而字符串的底層字節(jié)數(shù)組是只讀的横腿,如果共享了颓屑,就會導致字符串的只讀屬性不再成立。

既然string就是一系列字節(jié)耿焊,而[]byte也可以表達一系列字節(jié)揪惦,那么實際運用中應當如何取舍?

  • string可以直接比較罗侯,而[]byte不可以器腋,所以[]byte不可以當map的key值。
  • 因為無法修改string中的某個字符钩杰,需要粒度小到操作一個字符時纫塌,用[]byte。
  • string值不可為nil讲弄,所以如果你想要通過返回nil表達額外的含義措左,就用[]byte。
  • []byte切片這么靈活避除,想要用切片的特性就用[]byte媳荒。
  • 需要大量字符串處理的時候用[]byte,性能好很多驹饺。

5.修改字符串
在 Go 語言中钳枕,字符串的內容是不能修改的,也就是說赏壹,你不能用 s[0] 這種方式修改字符串中的 UTF-8 編碼鱼炒,如果你一定要修改,那么你可以將字符串的內容復制到一個可寫的緩沖區(qū)中蝌借,然后再進行修改昔瞧。這樣的緩沖區(qū)一般是 []byte 或 []rune。如果要對字符串中的字節(jié)進行修改菩佑,則轉換為 []byte 格式自晰,如果要對字符串中的字符進行修改,則轉換為 []rune 格式稍坯,轉換過程會自動復制數(shù)據(jù)酬荞。

angel := "Heros never die"
angleBytes := []byte(angel)
for i := 5; i <= 10; i++ {
    angleBytes[i] = ' '
}
fmt.Println(string(angleBytes))

字符串不可變有很多好處搓劫,如天生線程安全,大家使用的都是只讀對象混巧,無須加鎖枪向;再者,方便內存共享咧党,而不必使用寫時復制(Copy On Write)等技術秘蛔;字符串 hash 值也只需要制作一份。

6.關于中文
(1)使用%q打印傍衡,使用utf8包

    for i, r := range "Hello, 世界" {
        fmt.Printf("%d\t%q\t%d\n", i, r, r)
    }
    
    ss := "Hello, 世界"
    fmt.Println(len(ss)) // "13"
    fmt.Println(utf8.RuneCountInString(ss)) // "9"

(2)golang截取中文字符串
在golang中可以通過切片截取一個數(shù)組或字符串深员,但是當截取的字符串是中文時,可能會出現(xiàn)的問題是:由于中文一個字不只是由一個字節(jié)組成蛙埂,所以直接通過切片可能會把一個中文字的編碼截成兩半倦畅,結果導致最后一個字符是亂碼。
例如: 想要截取前四個字

    name := "我是胡八一"
    fmt.Println("name[:4] = ",name[:4])

執(zhí)行后得到的結果會是這樣的:

name[:4] =  我?

解決方法: 先將其轉為[]rune箱残,再截取后滔迈,轉會string

    nameRune := []rune(name)
    fmt.Println("string(nameRune[:4]) = ",string(nameRune[:4]))

運行結果:

string(nameRune[:4]) =  我是胡八

7.string與int互轉

import "strconv"  //先導入strconv包

// string到int
int, err := strconv.Atoi(string)

// string到int64
int64, err := strconv.ParseInt(string, 10, 64)

// int到string
string := strconv.Itoa(int)

// int64到string
string := strconv.FormatInt(int64,10)

Go語言字符串高效拼接(一)
Go語言字符串高效拼接(二)
Go語言字符串高效拼接(三)

二止吁、字典

《快學 Go 語言》第 6 課 —— 字典
1.make

func main() {
    var m map[int]string = make(map[int]string)
    fmt.Println(m, len(m))
}

----------
map[] 0

如果你可以預知字典內部鍵值對的數(shù)量被辑,那么還可以給 make 函數(shù)傳遞一個整數(shù)值,通知運行時提前分配好相應的內存敬惦。這樣可以避免字典在長大的過程中要經(jīng)歷的多次擴容操作盼理。
var m = make(map[int]string, 16)

2.初始化

func main() {
    var m map[int]string = map[int]string{
        90: "優(yōu)秀",
        80: "良好",
        60: "及格",  // 注意這里逗號不可缺少,否則會報語法錯誤
    }
    fmt.Println(m, len(m))
}

---------------
map[90:優(yōu)秀 80:良好 60:及格] 3

3.讀寫

func main() {
    var fruits = map[string]int {
        "apple": 2,
        "banana": 5,
        "orange": 8,
    }
    // 讀取元素
    var score = fruits["banana"]
    fmt.Println(score)

    // 增加或修改元素
    fruits["pear"] = 3
    fmt.Println(fruits)

    // 刪除元素
    delete(fruits, "pear")
    fmt.Println(fruits)
}

-----------------------
5
map[apple:2 banana:5 orange:8 pear:3]
map[orange:8 apple:2 banana:5]

刪除操作時俄删,如果對應的 key 不存在宏怔,delete 函數(shù)會靜默處理。遺憾的是 delete 函數(shù)沒有返回值畴椰,你無法直接得到 delete 操作是否真的刪除了某個元素臊诊。這時候必須使用字典的特殊語法,如下

func main() {
    var fruits = map[string]int {
        "apple": 2,
        "banana": 5,
        "orange": 8,
    }

    var score, ok = fruits["durin"]
    if ok {
        fmt.Println(score)
    } else {
        fmt.Println("durin not exists")
    }

    fruits["durin"] = 0
    score, ok = fruits["durin"]
    if ok {
        fmt.Println(score)
    } else {
        fmt.Println("durin still not exists")
    }
}

-------------
durin not exists
0

4.遍歷
這個和數(shù)組一樣的

func main() {
    var fruits = map[string]int {
        "apple": 2,
        "banana": 5,
        "orange": 8,
    }

    for name, score := range fruits {
        fmt.Println(name, score)
    }

    for name := range fruits {
        fmt.Println(name)
    }
}

------------
orange 8
apple 2
banana 5
apple
banana
orange

奇怪的是斜脂,Go 語言的字典沒有提供諸于 keys() 和 values() 這樣的方法抓艳,意味著如果你要獲取 key 列表,就得自己循環(huán)一下帚戳,如下

func main() {
    var fruits = map[string]int {
        "apple": 2,
        "banana": 5,
        "orange": 8,
    }

    var names = make([]string, 0, len(fruits))
    var scores = make([]int, 0, len(fruits))

    for name, score := range fruits {
        names = append(names, name)
        scores = append(scores, score)
    }

    fmt.Println(names, scores)
}

----------
[apple banana orange] [2 5 8]

go語言中map每次遍歷的順序不同-問題分析

讀完上面的內容玷或,我的理解就是:如果沒有設置這個隨機數(shù),那么在大多數(shù)情況下片任,golang會表現(xiàn)出map的順序是固定的情況偏友。但是golang底層并沒有保證這一點,或許(現(xiàn)在/以后)會有特殊情況出現(xiàn)順序不固定的情況对供。擔心開發(fā)者們誤解這一點位他,golang就特意去打亂了這個順序,讓開發(fā)者們知道golang底層不保證map每次遍歷都是同一個順序。

5.映射的鍵可以是任何值棱诱。這個值的類型可以是內置類型,也可以是結構類型炬灭。只要可以使用==運算符做比較重归。切片厦凤、函數(shù)以及包含切片的結構類型,由于具有引用語義椎木,不能作為映射的鍵香椎,使用這些類型會造成編譯錯誤禽篱。(Go in Action)

6.Golang map使用注意事項
map中的元素并不是一個變量躺率,而是一個值悼吱。因此,我們不能對map的元素進行取址操作笨枯。

var m = map[int]int {
    0 : 0,
    1: 1,
}

func main() {
        fmt.Println(&m[0])
}

運行報錯:

cannot take the address of m[0]

因此猎醇,當 map 的元素為結構體類型的值硫嘶,那么無法直接修改結構體中的字段值梧税。考察如下示例:

package main

import (
        "fmt"
)

type person struct {
    name   string
    age    byte
    isDead bool
}

func whoIsDead(personMap map[string]person) {
    for name, _ := range personMap {
        if personMap[name].age < 50 {
            personMap[name].isDead = true
        }   
    }   
}

func main() {
    p1 := person{name: "zzy", age: 100}
    p2 := person{name: "dj", age: 99} 
    p3 := person{name: "px", age: 20} 
    personMap := map[string]person{
        p1.name: p1, 
        p2.name: p2, 
        p3.name: p3, 
    }   
    whoIsDead(personMap)
    
    for _, v :=range personMap {
        if v.isDead {
            fmt.Printf("%s is dead\n", v.name)
        }    
    }   
}

編譯報錯:

cannot assign to struct field personMap[name].isDead in map

原因是 map 元素是無法取址的刨秆,也就說可以得到 personMap[name]衡未,但是無法對其進行修改家凯。解決辦法有二绊诲,一是 map 的 value用 strct 的指針類型掂之,二是使用臨時變量,每次取出來后再設置回去动雹。

(1)將map中的元素改為struct的指針洽胶。

package main

import (
        "fmt"
)

type person struct {
    name   string
    age    byte
    isDead bool
}

func whoIsDead(people map[string]*person) {
    for name, _ := range people {
        if people[name].age < 50 {
            people[name].isDead = true
        }   
    }   
}

func main() {
    p1 := &person{name: "zzy", age: 100}
    p2 := &person{name: "dj", age: 99} 
    p3 := &person{name: "px", age: 20} 
    personMap := map[string]*person {
        p1.name: p1, 
        p2.name: p2, 
        p3.name: p3, 
    }   
    whoIsDead(personMap)
    
        for _, v :=range personMap {
                if v.isDead {
                        fmt.Printf("%s is dead\n", v.name)
                }    
        }   
}

輸出結果:

px is dead

(2)使用臨時變量覆蓋原來的元素。

package main

import (
        "fmt"
)

type person struct {
    name   string
    age    byte
    isDead bool
}

func whoIsDead(people map[string]person) {
    for name, _ := range people {
        if people[name].age < 50 {
            tmp := people[name]
            tmp.isDead = true
            people[name] = tmp 
        }   
    }   
}

func main() {
    p1 := person{name: "zzy", age: 100}
    p2 := person{name: "dj", age: 99} 
    p3 := person{name: "px", age: 20} 
    personMap := map[string]person {
        p1.name: p1, 
        p2.name: p2, 
        p3.name: p3, 
    }   
    whoIsDead(personMap)
    
        for _, v :=range personMap {
                if v.isDead {
                        fmt.Printf("%s is dead\n", v.name)
                }    
        }   
}

輸出結果:

px is dead

7.hash算法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末喷好,一起剝皮案震驚了整個濱河市梗搅,隨后出現(xiàn)的幾起案子无切,更是在濱河造成了極大的恐慌丐枉,老刑警劉巖瘦锹,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辱士,居然都是意外死亡,警方通過查閱死者的電腦和手機异赫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門祝辣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝙斜,“玉大人孕荠,你說我怎么就攤上這事攻谁∑莼拢” “怎么了受楼?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵艳汽,是天一觀的道長河狐。 經(jīng)常有香客問我,道長栅干,這世上最難降的妖魔是什么碱鳞? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任芙扎,我火速辦了婚禮填大,結果婚禮上允华,老公的妹妹穿的比我還像新娘。我一直安慰自己磷蜀,他們只是感情好褐隆,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布剖踊。 她就那樣靜靜地躺著德澈,像睡著了一般梆造。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镇辉,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天鸡捐,我揣著相機與錄音,去河邊找鬼煎源。 笑死,一個胖子當著我的面吹牛手销,可吹牛的內容都是我干的锋拖。 我是一名探鬼主播诈悍,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼兽埃!你這毒婦竟也來了侥钳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤柄错,失蹤者是張志新(化名)和其女友劉穎舷夺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體售貌,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡给猾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颂跨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恒削。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡斑粱,死狀恐怖矿微,靈堂內的尸體忽然破棺而出快骗,到底是詐尸還是另有隱情,我是刑警寧澤匕得,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布晋南,位于F島的核電站,受9級特大地震影響政溃,放射性物質發(fā)生泄漏申鱼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一猴鲫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦培慌、人聲如沸盒音。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哼审。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像翁逞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容

  • 1.安裝 https://studygolang.com/dl 2.使用vscode編輯器安裝go插件 3.go語...
    go含羞草閱讀 1,541評論 0 6
  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運用指針編程是C語言最主要的風格之一吩案。利用指針變量可以表示各種數(shù)據(jù)結構棚赔; ...
    朱森閱讀 3,430評論 3 44
  • 我的一天從午夜12點開始。 到凌晨12點半靠益,我看完了《無聲告白》這本書,本來準備在晚上11點就睡的...
    realwang95閱讀 329評論 0 0
  • 這兩天依晨爸爸在家!當我下班回來的時候因宇!作業(yè)都已經(jīng)完成了贺辰!感覺還是這樣的日子好稗嗲颉!
    楊依晨卓媽媽閱讀 107評論 0 0
  • 現(xiàn)在在我們家存在的主要 表面現(xiàn)象 是孩子的作業(yè) 不能 很好的完成歹鱼,爸爸沒有時間管 媽媽說呢 又不聽泣栈,孩子也知道 作...
    徐忠環(huán)閱讀 186評論 0 1