数组与Slice
数据容器
数组与切片
数组和切片有何异同
切片本质上是对数组的封装,他描述一个数组的片段。
数组是一片连续的地址空间,切片是一个结构体,包含长度、容量、底层数组。
// src/runtime/slice.go
type slice struct {
array unsafe.Pointer //元素指针
len int // 长度
cap int // 容量
}
注意:底层数组可以被多个切片同时指向
切片如何被截取
上文说过,底层数组是可以被共用的,
那么 切片被截取的完成后,需要考虑 ——新旧slice 的底层数组是不是共用的。
一般情况下,只有slice 继续append 扩充长度,才会导致底层数组不可用
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := slice[2:5]
s2 := slice[2:6:7]
s2 = append(s2, 100)
s2 = append(s2, 200)
s1[2] = 20
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(slice)
}
[2 3 20]
[2 3 4 5 100 200]
[0 1 2 3 20 5 100 7 8 9]
从上面的例子可以看出,s1 是与slice 公用底层数组,而s2 没有共用,是因为底层数组长度不够,进行了扩充。
切片的容量是怎样增长的
切片容量增长调用的 append 函数
备注: 这里详细去看slice 的文档
切片作为函数参数会被改变吗
前文说到,slice 是作为一个结构体存在的,但是他的数组是以指针的形式。
若直接传slice ,在调用者看来,实参slice 并不会被函数对形参的操作改变,实参是形参的一个复制;
若传的是slice指针,则会影响实参。
但是因为结构体里面是数组以指针的形式存在。
那么在上面不论是直接传slice 还是 slice 指针,都是共用的一个底层数组。!!!
另外提一句: go语言中的函数传参,只有值传递,没有引用传递。
内建函数 make 和new 的区别是什么
首先make 和new 都是用来分配内存的函数。但是适用的类型不同:
make 适用于 slice ,map,channel 等引用函数。
new 适用于 int 型、数组、结构体等值类型。
散列表map
map 的底层实现原理是什么
Go 语言采用的是哈希查找表,并且使用链表法解决哈希冲突
- map 内存模型
// src/runtime/map.go
type hmap struct {
//元素个数 ,调用 len(map) 直接返回值
count int
flags uint8
//buckets 的对数 log_2
B uint8
// overflow 的bucket 近似数
noverflow uint16
// 计算key的哈希的时候会传入哈希函数
hash0 unt32
// 指向buckets 数组 ,大小为 2^B
// 如果元素个数为0,就为nil
buckets unsafe.Pointer
// 扩容的时候,buckets 长度会是oldbuckets 的两倍
oldbuckets unsafe.Pointer
// 指针扩容进度,小于此地址的buckets 完成迁移
nevacuate uintptr
extra *mapextra
}
map 中的key 为什么是无序的
随着map 中的key 数量的增多,原有的空间无法保证高效的进行增删改查的操作时就会发生扩容。而map 扩容,会发生key 的搬迁。原来在同一个bucket 中的key ,搬迁后,有些key会发生变动,bucket 序号加上2^B
上面描述了,假定,每次遍历bucket 是依次遍历的。map 是在有增删 的环境下,遍历是无序。
那么为什么真实情况,map 还是无序的?
在Go 的视线中,当遍历map 时,并不是固定从bucket 从0 开始遍历。而是每次都随机一个bucket 开始。
所以 map 是无序的。
float 类型可以作为map 的key 吗
从语法来看,是可以的,Go 语言 只要是可比较类型都可以作为key ,除了 slice ,map,functions 这几种类型,其他类型都可以作为map 的key。 这些类型的共同特征是支持 == 和 != 操作符。如果是结构体,只有hash 后的值和字面值相当,才认同为相同的可以。
如何比较两个map 是否相等
在两个map 深度相等的条件如下:
- 都为nil
- 非空、长度相等,指向同一个map 实体对象
- 响应的key 指向value “深度相等”
三个条件只要满足一条都认为是相等。(直接用== 是错误,相当于 判nil)