1. 字典:
go语言中的字典与C++的map类似,是key-value类型的数据结构。
1.1 数组切片 与 字典的比较:
数组切片让我们具备了可以操作一块连续内存的能力,它是对同质元素的统一管理。而字典则是对关联性数据的进行操作的数据结构。
1.2 字典的创建:
创建字典可以使用 make
函数,但这种方法创建的字典是空的,长度为零,内部没有任何元素。
如果需要给字典提供初始化的元素,则需要使用另一种创建字典的方式。
字典类型使用 map[key]value
表示。
package main
import "fmt"
func main() {
//method 1: make
var m1 map[int]string = make(map[int]string)
fmt.Println(m1, len(m1))
//method 2:
var m2 map[int]string = map[int]string{
90:"excellent",
80:"good",
60:"pass",
}
fmt.Println(m2, len(m2))
}
------
map[] 0
map[60:pass 80:good 90:excellent] 3
make函数可以传递一个整型值,在创建字典时提前分配好内存,避免字典在长大的过程中要经历的多次扩容操作:
func main() {
var m map[int]string = make(map[int]string, 16)
}
1.3 字典的读写:
同C++中的map一样,Go语言中的字典也可以使用中括号来读写内部元素,使用delete函数来删除元素。
package main
import "fmt"
func main() {
var fruits map[string]int = map[string]int {
"apple":2,
"banana":5,
"orange":8,
}
var price = fruits["apple"]
fmt.Println(price)
fruits["pear"] = 3 //增加元素
fmt.Println(fruits["pear"])
delete(fruits, "pear") //delete删除元素
fmt.Println(fruits)
}
------
2
3
map[apple:2 banana:5 orange:8]
当delete删除操作对应的key不存在时,delete函数会静默处理,delete没有返回值。因此无法直接判断delete操作是否真的删除了某个元素,只能通过判断字典的长度信息 或者提前尝试读取key对应的value。
当读操作时,如果key不存在,也不会抛出异常,它会返回value类型对应的零值。
key和value可能恰好就是零值,所以不能通过读操作的返回值直接判断key是否存在,而是要使用到字典的特殊语法:函数的“多态返回值”(一个函数的返回值可以有多个)。
package main
import "fmt"
func main() {
var fruits map[string]int = map[string]int {
"apple":2,
"banana":5,
"ornage":8,
}
var price, result = fruits["pear"];
if result {
fmt.Println(price)
} else {
fmt.Println("key is not exists")
}
var score, ok = fruits["apple"]
if ok {
fmt.Println(score)
} else {
fmt.Println("key is not exists")
}
}
------
key is not exists
2
1.4 字典的遍历:
字典的遍历提供两种方式,一种是可以同时获取key和value的值,一种是只获取key的值。
字典的遍历需要使用Go语言的 range
关键字。
package main
import "fmt"
func main() {
var fruits map[string]int = map[string]int {
"apple":2,
"banana":5,
"ornage":8,
}
//method 1:
//格式: for key value := range map {}
for name, score := range fruits {
fmt.Println(name, score)
}
//method 2:
//格式: for key := range map {}
for name := range fruits {
fmt.Println(name)
}
}
------
apple 2
banana 5
ornage 8
apple
banana
ornage
Go语言的字典没有提供 keys() 和 values() 这样的获取key或者value列表的方法,需要自行循环遍历获取:
package main
import "fmt"
func main() {
var fruits map[string]int = map[string]int {
"apple":2,
"banana":5,
"ornage":8,
}
var names []string = make([]string, 0, len(fruits))
var scores []int = make([]int, 0, len(fruits))
for name, score := range fruits {
names = append(names, name)
scores = append(scores, score)
}
fmt.Println(names)
fmt.Println(scores)
}
------
[apple banana ornage]
[2 5 8]
1.5 线程(协程)安全:
Go语言内置的字典不是线程安全的,如果需要线程安全,必须使用锁来控制。
1.6 字典变量中存放的值:
字典里存放的只是一个地址指针,这个指针指向字典的头部对象。
所以字典占用的空间是一个字,即一个指针的大小,在32位机器上是4个字节,在64位机器上是8个字节。
2. 字符串:
Go语言中的字符串的每个节点是 不定长的,其中 英文字符 占用 1个字节,非英文字符占用多个字节。
这意味着无法通过位置来快速定位出一个完整的字符来,而必须通过遍历的方式来逐个获取单个字符。
Go语言中的字符类型是 rune
,rune是一个衍生类型,在内存中使用 int32 类型的4个字节存储:
type rune int32
Go语言中的字符串并不是用 rune字符 来实现的,而是一种“字节串”,即按照节点元素的实际大小来分配内存:例如一个英文字符分配 1字节,一个中文字符分配 3字节。
如果使用 字符串 来表示字符串会造成空间浪费,因为所有的英文字符本来只需要一个字节来表示,用 rune 字符来表示的话那么剩余的3个字节都是零。但使用 rune来完全实现字符串有一个好处,那就是可以快速定位节点位置,因为所有节点占用的内存都是等长的。
图示 字节byte 与 字符rune 的区别:
其中,codepoint是每个字的真实偏移量,Go语言的字符采用 utf8 编码,其中中文汉字占用 3个字节,英文字符占用 1个字节。
len() 函数得到的是 字节的数量,通过下标来访问字符串得到的是 字节。
2.1 字符串的内存结构:
字符串的内存结构类似于切片的结构,编译器为其分配了头部字段来存储长度信息和 指向底层字节数组的指针,如下图所示:
当我们将一个字符串赋值给另一个字符串变量时,发生的是浅拷贝,底层的字节数组是共享的,只是浅拷贝了头部字段。
2.2 字符串是只读的:
编译器禁止使用字符串下标来直接赋值,只能通过下标读取字符串上指定位置的字节。
package main
import "fmt"
func main() {
var s = "hello"
fmt.Printf("%cn", s[0]) //正确!输出: h
s[0] = 'w' //错误!编译器:cannot assign to s[0] (strings are immutable)
}
2.3 字符串的切割:
字符串的实现上与切片类似,字符串也同样支持切割操作,切割后子串与母串共享底层的字节数组。
package main
import "fmt"
func main() {
var s1 string = "hello world";
var s2 string = s1[2:5]
fmt.Println(s1)
fmt.Println(s2)
}
------
hello world
llo
2.4 字节切片和字符串的相互转换:
在使用Go语言进行网络编程时,经常需要将来自网络的字节流转换成内存字符串,同时也需要将内存字符串转换成网络字节流。Go语言内置了字节切片和字符串的相互转换语法。
package main
import "fmt"
func main() {
var s1 string = "hello world"
var b []byte = []byte(s1) //字符串转成字节切片
var s2 string = string(b) //字节切片转成字符串
fmt.Println(b)
fmt.Println(s2)
}
------
[104 101 108 108 111 32 119 111 114 108 100]
hello world
当字节切片与字符串进行相互转换时,底层字节数组是不共享的,而是会进行拷贝,如果内容很大,那么转换操作需要一定的成本。
转换时需要进行拷贝的原因是:字节切片的底层数组内容是可以进行修改的,而字符串的底层字节数组是只读的,如果共享底层数组,就会导致字符串的只读属性不再成立。
参考内容:
https://zhuanlan.zhihu.com/p/50047198
https://zhuanlan.zhihu.com/p/50399072