我们之前介绍了golang的基本数据类型,和它们对应的运算符操作。
我们继续深入golang的复合型的数据类型。
复合型类型的产生,起初来自于对需要处理的事物数量级变化的支持。从处理单个对象,到需要处理多个一样对象的列表,我们因此产生了数组(array)。
在golang中我们可以定义固定数组或者动态数组,但实际上后者使用的比较多:
var list1 = [4]int{1, 2, 3, 4} // 固定数组
var list []int // 动态数组
我们对一组对象的处理需求,从获取对象的个数,到添加对象,获取/查找对象,到删除对象,这些都是朴素的需求。
golang提供了一个强劲的数据类型:切片(slice),它是相应数组的一个动态视图。切片本身并不保存自己的数据,它只是引用了数组的一部分,因此它具备了方便与性能两方面的益处。
切片的定义包含了三个部分:
- 指针:指向数组的一个位置。
- 长度:切片中的元素个数。
- 容量:从切片的起始位置到数组末尾的元素个数。
slice1 := make([]int, 10, 20) // 使用make函数创建一个指定长度和容量的切片
slice2 := []int{1, 2, 3, 4, 5} // 通过字面量创建切片,指定了初始值
arr := [5]int{1, 2, 3, 4, 5}
slice3 := arr[1:4] // 从数组创建切片
切片的容量在超出当前容量时会自动扩展:
- 每次扩容,容量会翻倍,直到超出内存限制。
- 容量增长时会创建一个新的底层数组,并将数据复制过去。
slice := []int{1, 2, 3}
slice = append(slice, 4, 5, 6) // 扩展切片
golang中是通过切片的拼接来实现删除.
切片是引用类型,底层使用的是数组,不会拷贝底层数组,多个切片可以共享同一个数组。切片具有相对的灵活性和效率。实际上,大多数时候,你都是在用切片操作动态数组,甚至固定数组都只是在偶尔的场合中才使用。
我们回到数据类型:数组的性能上。对使用中性能的思考,注意是两方面的,时间上的,和空间上的。实际上都跟数组的长度或者说数量级有关。
时间上,指代码处理需要的时长。
空间上,指代码处理或者数据保存,需要的内存空间大小。
编程或者说程序的运行始终是要受到所处环境的限制,因此内存是一个硬条件限制。当一个对象数组数量上足够多时,它对我们系统的整体占用就必须被充分思考。
另一方面,对一个大数量的数组进行操作时,需要的处理时长更是决定程序性能指标的关键。
性能的这些方面的处理需求,产生了算法与数据结构。
在编程语言中,因此产生了相关的跟内存存储相对应的数据结构:
连续存储的数组:array
链表存储的数组:单向链表,双向链表,树(二叉树),图(有向图)
Set: 唯一性数组
栈,队列
。。。
每种数据结构,都有相应的处理算法来保证时空上的性能。
对不同数量级的数据,和具体的业务需求,需要有针对性的使用相应的数据结构。
golang中的字典(map), 它是一种无序的键值对集合。它主要解决的是快速的插入、删除和查找操作。
m1 := make(map[string]int)
m2 := map[string]int{"apple": 1, "banana": 2, "orange": 3}
m1["apple"] = 5
v1 := m1["apple"]
v2, ok := m1["pear"]
delete(m1, "banana")
for k, v := range m {
fmt.Printf("k=%s, v=%d
", k, v)
}
golang中的map是引用类型,它的键必须是可比较的类型,例如 int、float64、string、指针等。它不是并发安全的。
另外,需要注意:
map 的零值是 nil,一个 nil 的 map 不能存储键值对,必须进行初始化.
map 的遍历是无序的
map的底层实现是通过哈希函数将键映射到哈希函桶中,然后在桶中存储键值对。因此map类型具有足够的灵活性和性能。
总结一下,今天主要讨论了golang中一样对象的集合类型:数组/切片,和map. 对大数据量的一样对象集合进行处理时,算法与数据结构随之而来。我们对处理性能上的追求,是跟系统运行环境和需要处理的数据的数量级息息相关的,需要结合实际的业务需求和环境限制来考量。
总之,对集合类型变量的操作,需要小心在意。
我们明天继续聊不同对象类型组成的复合类型:结构,元组等。

















暂无评论内容