这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天。
1. 字符串
- Go 的字符串是字节数组组成的字符序列,使用 UTF-8 编码来表示 Unicode 文本。
- Go 的字符串是不可变的。
- 双引号中的字符串是普通字符串,可以使用转义字符;反引号中的字符串保持原样。
var s1 string = "hello\n"
var s2 string = `hello\n
hello
你好!
`
fmt.Println(s1, s2)
+用于拼接字符串,若要换行,+需要留在上一行
{
var s1 = "hello"
var s2 = "world"
var s3 = s1 + s2
fmt.Println(s3)
}
{
var s = "asasasasasasa" + "sdsdsdsd" +
"sdsdsdsd" + "sdsdsdsd"
fmt.Println(s)
}
1.1. 字符串遍历
s := "h2世界o"
fmt.Println(len(s)) // 9
fmt.Println(utf8.RuneCountInString(s)) // 5
for i, ch := range s {
fmt.Println(i, ch)
}
fmt.Println("---")
// 0 104
// 1 50
// 2 19990
// 5 30028
// 8 111
// ---
for i, ch := range []byte(s) {
fmt.Println(i, ch)
}
fmt.Println("---")
// 0 104
// 1 50
// 2 228
// 3 184
// 4 150
// 5 231
// 6 149
// 7 140
// 8 111
// ---
for i, ch := range []rune(s) {
fmt.Println(i, ch)
}
// 0 104
// 1 50
// 2 19990
// 3 30028
// 4 111
func RuneCountInString(s string) (n int)获取字符串中字符的个数- rune 是 int32 的别名,表示一个 Unicode 码点。
- 字符串底层存储结构是一个 byte 数组,每个字符的 Unicode 码点将被 UTF-8 编码后存储到数组中,当遍历字符串时会将 UTF-8 值解码为 Unicode 码点赋值到 rune 类型变量中。
// 字符 '世' 的 Unicode 码点是 19990
// 字符 '世' 的 UTF-8 编码后是三字节 [228, 184, 150]
bytes := []byte{228, 184, 150}
fmt.Printf("%X\n", bytes) // E4B896
r, size := utf8.DecodeRune(bytes)
fmt.Println(r, size) // 19990 3
fmt.Printf("%c\n", r) // 世
// 19990 是 UTF-8 解码得到的 Unicode 码点, 转为 16 进制是 4E 16
// E4B896 是 UTF-8 编码后的值, 转为 10 进制是 14,989,462
1.2. 字符串的底层结构
字符串是由一个指向底层 byte 数组的指针和记录 byte 数组长度的 int 组成的结构体。
// runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
len(s)得到的是底层字节数组的长度,而不是字符数- 对字符串直接使用下标访问,得到的是该处的字节
for-range遍历是以字符为单位遍历的,一到多个字节被解码为 rune
2. 数组
2.1. 数组定义
// var 数组变量名 [数组大小]数据类型
var a [10]int
// [0 0 0 0 0 0 0 0 0 0]
数组定义并初始化:
var 数组变量名 [数组大小]数据类型 = [数组大小]数据类型{初始值列表}
var a [10]int = [10]int{1, 2, 3}
// [1 2 3 0 0 0 0 0 0 0]
// 当然赋值符号前的类型可以省略
var a = [10]int{1, 2, 3}
// [1 2 3 0 0 0 0 0 0 0]
// var 数组变量名 = [...]数据类型{初始值列表}
var b = [...]int{1, 2, 3}
var b [3]int = [...]int{1, 2, 3}
// [...] 表示由编译器取推断数组大小
// 按下标初始化几个元素
var c = [6]int{1: 1, 4: 4}
// [0 1 0 0 4 0]
var d = [...]int{1: 1, 4: 4}
// [0 1 0 0 4]
var 也可改为 :=
a := [10]int{1, 2, 3}
b := [...]int{1, 2, 3}
c := [6]int{1: 1, 4: 4}
d := [...]int{1: 1, 4: 4}
2.2. 数组的传递
GO 的数组是值类型,参数传递时会拷贝整个数组
func main() {
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 2
// 0xc00000e398
changeArray(arr)
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 2
// 0xc00000e398
}
func changeArray(arr [5]int) {
arr[1] = 10
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 10
// 0xc00000e3c8
}
Go 的数组长度不同是不同的数据类型,函数 changeArray 只接受 5 个元素的数组
使用数组指针来模拟按引用传递:
func main() {
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 2
// 0xc00000e3f8
changeArrayByPointer(&arr)
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 10
// 0xc00000e3f8
}
func changeArrayByPointer(arr *[5]int) {
arr[1] = 10
fmt.Println(arr[1])
fmt.Println(&arr[1])
// 10
// 0xc00000e3c8
}
2.3. 数组遍历
-
for-i
arr := [...]int{1, 2, 3, 4, 5} for i := 0; i < len(arr); i++ { fmt.Println(arr[i]) } // 1 // 2 // 3 // 4 // 5 -
for-range
arr := [...]int{1, 2, 3, 4, 5} for i, a := range arr { fmt.Println(i, arr[i], a) } // 0 1 1 // 1 2 2 // 2 3 3 // 3 4 4 // 4 5 5// 只要下标 for i := range arr { fmt.Println(arr[i]) } // 只要元素 for _, a := range arr { fmt.Println(a) }
3. Slice 切片
- 切片是数组的一个引用,切片的引用类型,传递时按引用传递
- 切片的取值和遍历与数组一样
- 切片的长度可动态的变化
切片的定义:
var 切片名 []数据类型
切片的创建方式一:在一个已定义的数组上建立视图
arr := [...]int{0, 1, 2, 3, 4, 5, 6}
var slice []int = arr[2:5]
fmt.Print(slice) // [2 3 4]
arr[2:5]表示对数组切片,中括号内的两个数字是切片的数组下标区间,左开右闭- 省略其中一个表示切片到底
fmt.Print(arr[:5]) // [0 1 2 3 4]
fmt.Print(arr[2:]) // [2 3 4 5 6]
fmt.Print(arr[:]) // [0 1 2 3 4 5 6]
fmt.Print(arr[0:7]) // [0 1 2 3 4 5 6]
fmt.Print(arr[0:8]) // invalid argument: index 8 out of bounds [0:8]
- 一个数组上可以创建多个切片
- 切片上面还可以创建切片,切片可以向后扩展,切片区间只要不超过数组即可
arr := [...]int{0, 1, 2, 3, 4, 5, 6}
var s1 []int = arr[2:6]
var s2 []int = arr[1:3]
var ss1 []int = s1[3:5]
fmt.Print(s1, s2, ss1) // [2 3 4 5] [1 2] [5 6]
切片的创建方式二:make
var 切片名 []数据类型 = make([]type, len, [cap])
var slice []int = make([]int, 5)
fmt.Print(slice) // [0 0 0 0 0]
- make 可以指定切片大小
len和容量cap,要求cap >= len - make 创建的切片底层会维护一个数组,对外不可见
var slice1 []int = make([]int, 5, 10)
fmt.Println(slice1, len(slice1), cap(slice1)) // [0 0 0 0 0] 5 10
var slice2 []int = slice1[0:10]
fmt.Println(slice2, len(slice2), cap(slice2)) // [0 0 0 0 0 0 0 0 0 0] 10 10
var slice3 []int = slice1[5:7]
fmt.Println(slice3, len(slice3), cap(slice3)) // [0 0] 2 5
切片的创建方式三:make 的语法糖
var slice []int = []int{0, 1, 2, 3, 4, 5, 6}
fmt.Println(slice, len(slice), cap(slice)) // [0 1 2 3 4 5 6] 7 7
3.1. Slice 的底层结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
3.2. append 动态追加
func append(slice []Type, elems ...Type) []Type
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(s1, s2, arr)
// [2 3 4 5] [5 6] [0 1 2 3 4 5 6 7]
s3 := append(s2, 10)
fmt.Println(s1, s2, s3, arr)
// [2 3 4 5] [5 6] [5 6 10] [0 1 2 3 4 5 6 10]
s4 := append(s3, 11)
fmt.Println(s1, s2, s3, s4, arr)
// [2 3 4 5] [5 6] [5 6 10] [5 6 10 11] [0 1 2 3 4 5 6 10]
s5 := append(s4, 12)
fmt.Println(s1, s2, s3, s4, s5, arr)
// [2 3 4 5] [5 6] [5 6 10] [5 6 10 11] [5 6 10 11 12] [0 1 2 3 4 5 6 10]
s4, s5再次追加元素时,arr的容量不够,会重新创建一个更大的数组,将元素拷贝过去。
var s []int
oldCap := cap(s)
for i := 1; i < 2018; i++ {
s = append(s, i)
if newCap := cap(s); newCap != oldCap {
oldCap = newCap
fmt.Printf("%d %p %p\n", newCap, s, &s)
}
}
// cap s 指向的底层数组 变量 s 的地址
// 1 0xc00001c480 0xc000008300
// 2 0xc00001c490 0xc000008300
// 4 0xc000014200 0xc000008300
// 8 0xc000010580 0xc000008300
// 16 0xc00008a100 0xc000008300
// 32 0xc000006500 0xc000008300
// 64 0xc00008e000 0xc000008300
// 128 0xc000090000 0xc000008300
// 256 0xc000092000 0xc000008300
// 512 0xc000094000 0xc000008300
// 848 0xc000096000 0xc000008300
// 1280 0xc0000a0000 0xc000008300
// 1792 0xc0000aa000 0xc000008300
// 2560 0xc0000b8000 0xc000008300
var s []int
fmt.Println(cap(s)) // 0
s = append(s, 1)
fmt.Println(cap(s)) // 1
s = append(s, 1, 1, 1, 1, 1)
fmt.Println(cap(s)) // 6
s = append(s, 1, 1, 1, 1, 1)
fmt.Println(cap(s)) // 12
slice是其指向的底层数组的地址,&slice是变量slice自身的地址- 当 slice 容量较小时,扩容一倍;当其容量较大时扩容大小小于一倍。(参加函数
growslice) - 当一次 append 多个元素时,若期望容量大于上一条的容量,则直接扩容到期望容量
- 扩容时是并发不安全的
3.3. copy 拷贝切片
func copy(dst, src []Type) int
var s1 []int = []int{1, 2, 3, 4}
var s2 []int = make([]int, 10)
var s3 []int = make([]int, 2)
copy(s2, s1)
copy(s3, s1)
fmt.Println(s1, s2, s3)
// [1 2 3 4] [1 2 3 4 0 0 0 0 0 0] [1 2]
s2[0] = 9
fmt.Println(s1, s2, s3)
// [1 2 3 4] [9 2 3 4 0 0 0 0 0 0] [1 2]
4. Map
Map 的定义:
var 变量名 map[key类型]value类型
Map 的创建:make
变量名 = make(t Type, size)
var m map[string]string
m = make(map[string]string, 16)
make 的语法糖
var m map[string]string = map[string]string{
"name": "A",
"age": "1",
}
- map 在使用前一定要 make
- map 的键值对是无序的
- 当 key 不存在时,获取的是 value 类型的初始值
- map 是引用类型
4.1. map 的操作
var m map[string]string = map[string]string{
"name": "A",
"age": "1",
}
fmt.Println(m) // map[age:1 name:A]
// 更新
fmt.Println(m["name"]) // A
m["name"] = "B"
fmt.Println(m["name"]) // B
// 增加
fmt.Println(m["other"]) //
m["other"] = "O"
fmt.Println(m["other"]) // O
// 删除
fmt.Println(m["age"]) // 1
delete(m, "age")
fmt.Println(m["age"]) //
// 查找
v1, v1Exist := m["name"]
v2, v2Exist := m["age"]
fmt.Println(v1Exist, v1) // true B
fmt.Println(v2Exist, v2) // false
4.2. map 遍历
m := map[string]string{
"a": "A",
"b": "B",
"c": "C",
}
for k, v := range m {
fmt.Println(k, v)
}
// a A
// b B
// c C
4.3. k v 的类型
可以作为 key 的类型:
- 必须可以比较相等
- 除了 slice, map, function 其他内建类型都可作为 key
- Struct 类型不包含 slice, map, function 字段,也可作为 key
4.4. map 例题:最长不含重复字符的子字符串
// 设以 s[i] 结尾的 s 的子串中, 无重复字符且最长的字符串的长度为 f[i]
//
// f[0] = 1
// f[i] = f[i-1] + 1, if (s[i] not in s[0:i-1])
// f[i] = i - r, if (s[i] in s[0:i-1]), r 是最近的一个重复的 s[i] 的下标
// res = max(f)
func lengthOfLongestSubstring(s string) int {
var f = 0
var ans = 0
// m 中记录了 s[i] 之前区间出现的所有的 <字符, 下标>
// 若某个字符出现了两次, m 中总是记录离 s[i] 最近的下标
m := make(map[rune]int)
for i, ch := range []rune(s) {
// 如果存在重复字符, 且重复的字符在当前 l 的区间内
if r, exist := m[ch]; exist && f >= i - r {
f = i - r
} else {
f = f + 1
}
// 添加或更新当前字符位置
m[ch] = i
if ans < f {
ans = f
}
}
return ans
}
4.5. Map 的底层结构
// runtime/map.go
// A header for a Go map.
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
// A bucket for a Go map.
type bmap struct {
// tophash generally contains the top byte of the hash value
// for each key in this bucket. If tophash[0] < minTopHash,
// tophash[0] is a bucket evacuation state instead.
tophash [bucketCnt]uint8
// Followed by bucketCnt keys and then bucketCnt elems.
// NOTE: packing all the keys together and then all the elems together makes the
// code a bit more complicated than alternating key/elem/key/elem/... but it allows
// us to eliminate padding which would be needed for, e.g., map[int64]int8.
// Followed by an overflow pointer.
}
const (
// Maximum number of key/elem pairs a bucket can hold.
bucketCntBits = 3
bucketCnt = 1 << bucketCntBits // 即 8
)
buckets指向的是一个长度为2^B的bmap数组bmap.tophash是一个长度为8的uint8数组,存放了 K 的哈希值高 8 位。
其后还有 8 个 key 和 8 个 value,即一个bmap能存放 8 个键值对
以及一个 overflow pointer(溢出指针),当键值对超过 8 个时,该指针指向下一个bmap。
由于无法确定 K V 的类型,就无法确定大小,因此这些字段将在编译时才添加。- map 的读写是有并发问题的
sync.Map 是并发安全的
- 使用了两个 map,分离了扩容
- 不会引发扩容的操作,如查、改,使用 read map
- 可能引发扩容的操作,如新增,使用 dirty map