三. go 高性能编程之 切片性能问题

一. 基础

  1. Go 中数组变量属于值类型(value type),当一个数组变量被赋值或者传递时,实际上会对数组底层的内存进行拷贝,为了避免这个问题,可以传递指向数组的指针
  2. 数组固定长度,缺少灵活性,所以大部分场景下会选择使用基于数组构建的切片类型
languages := []string{"Go", "Python", "C"}

//也可以用内部提供的make函数初始化切片
//T 即元素类型,
//第二个参数是长度 len,即初始化的切片拥有多少个元素,
//第三个参数是容量 cap,容量是可选参数,默认等于长度
func make([]T, len, cap) []T

二. 操作切片与性能

copy

Append 与容量

  1. 切片底层有ptr指针,len长度,cap容量三个属性,在执行append追加 时有两种场景
  1. 当 append 之后的长度小于 cap,将会直接利用原底层数组剩余的空间。
  2. 如果新添加元素之后,slice 的长度大于等于底层数组的容量,会触发扩容

如果扩容前的容量小于 1024,那么新的容量将会扩大为原来的两倍
如果扩容前的容量大于等于 1024,那么新的容量将会扩大为原来的 1.25 倍

  1. 因此,为了避免内存发生拷贝,能够预知切片的最终大小时,可以预先设置 cap 的值

Delete

  1. 切片的底层是数组,因此删除意味着后面的元素需要逐个向前移位。每次删除的复杂度为 O(N),因此切片不合适大量随机删除的场景,这种场景下适合使用链表
  2. 优化的做法
//1.使用内置函数 copy 进行删除
func deleteElement(s []int, index int) []int {
    //copy 可以将一个较长的 slice 拷贝到一个较短的 slice 中
    copy(s[index:], s[index+1:])
    //返回元素个数
    return s[:len(s)-1]
}

//2.遍历切片进行删除
func deleteElement(s []int, index int) []int {
    for i := index; i < len(s) - 1; i++ {
        s[i] = s[i+1]
    }
    return s[:len(s)-1]
}

//3.将要删除的元素和最后一个元素交换位置,然后再将末尾元素截掉
func deleteElement(s []int, index int) []int {
    s[index], s[len(s)-1] = s[len(s)-1], s[index]
    return s[:len(s)-1]
}
  1. 并且手动将删除位置置空,有助于垃圾收集
    在这里插入图片描述

Insert

insert 和 append 类似。即在某个位置添加一个元素后,将该位置后面的元素再 append 回去。复杂度为 O(N)。因此,不适合大量随机插入的场景
在这里插入图片描述

Filter

在过滤获取元素时,如果原切片不会再被使用,就地 filter 方式是比较推荐,可以节省内存

func filterSlice(s []int) []int {
    i := 0
    for _, v := range s {
        if v >= 5 {
            s[i] = v
            i++
        }
    }
    return s[:i]
}

Push

  1. 在末尾追加元素,不考虑内存拷贝的情况,复杂度为 O(1)
    在这里插入图片描述
  2. 在头部追加元素,时间和空间复杂度均为 O(N),不推荐
    在这里插入图片描述

Pop

  1. 尾部删除元素,复杂度 O(1)
    在这里插入图片描述
  2. 头部删除元素,如果使用切片方式,复杂度为 O(1)。但是需要注意的是,底层数组没有发生改变,第 0 个位置的内存仍旧没有释放。如果有大量这样的操作,头部的内存会一直被占用
    在这里插入图片描述

二. 切片的性能陷阱

  1. 在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。比较推荐的做法,使用 copy 替代 re-slice,如下取 origin 切片的最后 2 个元素
func lastNumsBySlice(origin []int) []int {
	return origin[len(origin)-2:]
}

func lastNumsByCopy(origin []int) []int {
	result := make([]int, 2)
	copy(result, origin[len(origin)-2:])
	return result
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值