切片

slice的实现

1567076849767

切片的结构定义
type SliceHeader struct{
  Data uintptr
  Len int
  Cap int
}
//由上述代码可以看出切片的开头部分和Go字符串一样,但是切片多了一个Cap成员来表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)
切片的定义方式
var (
    a []int
  b = []int{}
  c = []int{1,2,3} //int也是个结构体,所以可以这样赋值
  d = c[:2]
  e = c[0:2:cap(c)] //有两个元素的切片,长度为2,cap为3
  f = c[:0]
  g = make([]int,3)
  h = make([]int,2,3)
  i = make([]int,0,3)
)
切片的类型

切片和数组不同,切片的类型和长度信息无关,只要是相同类型元素构成的切片均对赢相同的切片类型

package main

import "fmt"

func main() {
    a := make([]int, 3, 4)
    b := make([]int, 4, 5)
    b = a //这里a切片可以直接赋值给b切片的,如果是数组编译直接报错,因为切片数据类型与长度无关,数组则有关
  fmt.Print(b) //[0,0,0]
}
添加切片元素
var a[]int
a = append(a,1) //追加一个元素
a = append(a,1,2,3) //追加多个元素,手动解包方式
a = append(a, []int{1, 2, 3}...) //追加一个切片切片需要解包
从开头添加切片元素
var a = []int{1,2,3}
a = append([]int{0}, a...) //在开头插入一个切片
a = append([]int{-3,-2,-1}, a...) //在开头添加一个切片,全部是拓展相当于python extend,是吧切片元素append到原始切片中,不是切片这个对象
//在开头添加元素一般都会导致内存的重新分配,而且会导致已有的元素全部复制一次,因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多
append支持链式操作,可以将多个append组合起来使用
var a []int
a = append(a[:i],append([]int{x},a[i:]...)...) //在第i个位置插入x
a = append(a[:i],append([]int{1,2,3},a[i:]...)...) //在第i个位置插入切片
copy和append的组合可以避免创建中间的临时切片,同样是完成添加元素的操作
a = append(a,0) //切片扩展一个空间
copy(a[i+1:], a[i:]) //a[i:]向后移动一个位置
a[i] = x //设置新添加的元素
//这个方法虽然冗长了一点,但是相比前面的方法,可以减少中间创建的临时切片

切片的性能问题

在容量不足的情况下,append操作会导致重新分配内存,可能导致巨大的内存分配和复制数据的代价,即使容量足够,依然需要用append函数的返回值来更新切片本身,因为新切片的长度已经发生了变化

//将切片 b 的元素追加到切片 a 之后:
a = append(a, b...)
//复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a))
copy(b, a)
//删除位于索引 i 的元素:
a = append(a[:i], a[i+1:]...)
//切除切片 a 中从索引 i 至 j 位置的元素:
a = append(a[:i], a[j:]...)
//为切片 a 扩展 j 个元素长度:
a = append(a, make([]T, j)...)
//在索引 i 的位置插入元素 x:
a = append(a[:i], append([]T{x}, a[i:]...)...)
//在索引 i 的位置插入长度为 j 的新切片:
a = append(a[:i], append(make([]T, j), a[i:]...)...)
//在索引 i 的位置插入切片 b 的所有元素:
a = append(a[:i], append(b, a[i:]...)...)
//取出位于切片 a 最末尾的元素 x:
x, a = a[len(a)-1], a[:len(a)-1]
//将元素 x 追加到切片a:
a = append(a, x)
slice在做append操作时容量扩充规则

如果新的 slice 大小是当前大小2倍以上,则大小增长为新大小

否则循环以下操作:如果当前slice大小小于1024,按每次 2 倍增长,否则每次按当前大小 1/4 增长,直到增长的大小超过或等于新大小。

append 的实现只是简单的在内存中将旧 slice 复制给新 slice

package main

import "fmt"

func main() {

    aSlice := make([]int, 3, 5)
    bSlice := append(aSlice, 1, 2, 3, 4)
    fmt.Printf("a %v , cap = %d, len = %d\n", aSlice, cap(aSlice), len(aSlice))
    fmt.Printf("b %v , cap = %d, len = %d\n", bSlice, cap(bSlice), len(bSlice))
    aSlice[0] = 6
    fmt.Printf("a %v , cap = %d, len = %d\n", aSlice, cap(aSlice), len(aSlice))
    fmt.Printf("b %v , cap = %d, len = %d", bSlice, cap(bSlice), len(bSlice))
        // 这里aslice的变化不会影响到b,b在创建的时候超出了a的容量,所以内存重新拷贝了,不指向同一块底层数据
}

slice的内存分配和cap扩充规则

package main

import "fmt"

func main() {

    Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
    Slice_a := Array_a[2:5] //切片的容量是 3号位到10,从起点开始算,因为切片指向的是一块连续的内存,所以这里容量为8
    Slice_b := Slice_a[6:7] //所以这里读取6号到7号不会报错,以为读取的是原数组的数据
    fmt.Printf("Slice_a %v , cap = %d, len = %d\n", string(Slice_a), cap(Slice_a), len(Slice_a))
    fmt.Printf("Slice_b %v , cap = %d, len = %d\n", string(Slice_b), cap(Slice_b), len(Slice_b))
      Slice_b[0] = 10 //这里的改变会影响到原数组,指向同一块内存,没有扩容,所以没有产生内存复制
}

这里我们会发现 Slice_b 对 Slice_a 进行重新切片后,并没有报错,而是还有输出,这是因为 Slice_a 的 cap 是 8 ,并不是我们想象的 3,slice 指向的是一块连续的内存,所以 Slice_a 的容量其实是从索引为2的元素一直到 Array_a 的最后的。所以这里 Array_b 对 Array_a 进行切片后会得到值

删除切片数据

删除开头的元素可以直接移动数据指针
a = []int{1,2,3}
a = a[1:] //删除开头一个元素
a = a[N:] //删除开头N个元素
删除开的元素也可以不移动数据指针
a = []int{1,2,3}
a = append(a[:0], a[1:]...) //删除开头1个元素
a = append(a[:0], a[N:]...) //删除开头N个元素




posted @ 2019-12-19 19:40  离地最远的星  阅读(209)  评论(0编辑  收藏  举报