Go教程:14-array-slice-list区别和场景

Go教程:14-array-slice-list区别和场景

array 和 slice 看似相似,却有着极大的不同,但他们之间还有着千次万缕的联系 slice 是引用类型,是 array 的引用,相当于动态数组, 这些都是 slice 的特性,但是 slice 底层如何表现,内存中是如何分配的,特别是在程序中大量使用 slice 的情况下,怎样可以高效使用 slice? 今天借助 Go 的 unsafe 包来探索 array 和 slice 的各种奥妙

1. Array数组概念

数组是具有相同 唯一类型 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构); 这种类型可以是任意的原始类型例如整型,字符串或者自定义类型. 数组长度必须是一个常量表达式,并且必须是一个非负整数. 数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的.数组的编译时值初始化是按照数组顺序完成的.

Array数组要点:

  • 一组已编号且长度固定的数据项序列
  • 类型可以是任意的原始类型例如整型,字符串或者自定义类型
  • 数组长度必须是一个常量表达式,并且必须是一个非负整数
  • 数组长度也是数组类型的一部分
  • 使用 make 来创建

之前的疑问,为什么数组不能用 make 创建? 上面分析了解到数组操作是在编译时转换成对应指令的, 而 make 是在运行时处理(特殊状态下会做编译器优化,make可以被优化,下面 slice 分析时来讲

2. Slice切片

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的), 所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型).这个片段可以是整个数组, 或者是由起始和终止索引标识的一些项的子集. 需要注意的是,终止索引标识的项不包括在切片内.切片提供了一个相关数组的动态窗口.

3. Array 和 Slice之间的关系

切片(slice)是对数组(slice)一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的)

优点 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用.

4. Array 和 slice 的区别

声明数组时,方括号内写明了数组的长度或者…,声明slice时候,方括号内为空 作为函数参数时,数组传递的是数组的副本,而slice传递的是指针.

5. container/list Go标准库链表

Go的标准包container中包含了常用的容器类型,包括conatiner/list,container/heap,container/ring.本篇介绍conatiner/list.

conatiner/list实现了一个双向链表.使用起来与其他语言的动态列表非常相似

package main

import (
    "container/list"
    "fmt"
)

func main() {
    nums := list.New()
    nums.PushBack(1)
    nums.PushBack(2)
    nums.PushBack(3)
    for e := nums.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
}

6. 为何不用动态链表实现slice

首先拷贝一断连续的内存是很快的,假如不想发生拷贝,也就是用动态链表,那您就没有连续内存. 此时随机访问开销会是:链表 O(N), 2倍增长块链 O(LogN),二级表一个常数很大的O(1). 问题不仅是算法上开销,还有内存位置分散而对缓存高度不友好,这些问题i在连续内存方案里都是不存在的.除非您的应用是狂append然后只顺序读一次,否则优化写而牺牲读都完全不 make sense. 而就算您的应用是严格顺序读,缓存命中率也通常会让您的综合效率比拷贝换连续内存低.

对小 slice 来说,连续 append 的开销更多的不是在 memmove, 而是在分配一块新空间的 memory allocator 和之后的 gc 压力(这方面对链表更是不利).所以,当您能大致知道所需的最大空间(在大部分时候都是的)时, 在make的时候预留相应的 cap 就好.如果所需的最大空间很大而每次使用的空间量分布不确定,那您就要在浪费内存和耗 CPU 在 allocator + gc 上做权衡.

Go 在 append 和 copy 方面的开销是可预知+可控的,应用上简单的调优有很好的效果. 这个世界上没有免费的动态增长内存,各种实现方案都有设计权衡

6. Array Slice 链表使用场景

  • 固定长度或者已知长度使用Array,性能更优
  • 不定长度需要append…大部分情况下使用slice
  • 当程序要求slice的容量超大并且需要频繁的更改slice的内容时,就不应该用slice,改用list更合适.
目录