GO语言基础_slice

木鱼cavalry · · 603 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

数据切片的设计和注意事项

  1. 切片和数组的区别:
    • 数组是需要指定个数的,而切片则不需要。数组赋值也可是使用如下方式,忽略元素个数,使用“...”代替
    • slice和array的关系十分密切,通过两者的合理构建,既能实现动态灵活的线性结构,也能提供访问元素的高效性能。当然,这种结构也不是完美无暇,共用底层数组,在部分修改操作的时候,可能带来副作用,同时如果一个很大的数组,那怕只有一个元素被切片应用,那么剩下的数组都不会被垃圾回收,这往往也会带来额外的问题。
    • 使用make创建slice,此时golang会生成一个匿名的数组。
    • append操作超过了原始切片的容量,将会有一个新建底层数组的过程,那么此时再修改函数返回切片,应该不会再影响原始切片。
  2. nil 是 interface、function、pointer、map、slice 和 channel 类型变量的默认初始值。
  3. 直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array),会修改 slice 的底层 array,从而修改 slice
  4. 看起来 Go 支持多维的 array 和 slice,可以创建数组的数组、切片的切片,但其实并不是。对依赖动态计算多维数组值的应用来说,就性能和复杂度而言,用 Go 实现的效果并不理想。可以使用原始的一维数组、“独立“ 的切片、“共享底层数组”的切片来创建动态的多维数组。
    • 使用原始的一维数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配。
      • 使用“独立”的切片分两步:
        • 创建外部 slice
        • 对每个内部 slice 进行内存分配,注意内部的 slice 相互独立,使得任一内部 slice 增缩都不会影响到其他的 slice
      • 使用“共享底层数组”的切片
        • 创建一个存放原始数据的容器 slice
        • 创建其他的 slicefx
        • 切割原始 slice 来初始化其他的 slice
  5. string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可
    • 因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的。
    • 更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符
  6. 从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用。可以通过拷贝临时 slice 的数据,而不是重新切片来解决(特别是对于返回slice的函数,在函数里面的新slice不要引用越来的slice,而是新建一个slice的拷贝,返回这个拷贝)

测试代码

package main

import (
 "bytes"
 "fmt"
 "reflect"
)

func main() {
 makeSlice()
 apendSlice1()
 apendSlice2()
 fmt.Println("\n当往 newSlice 中新增元素的时候,由于其容量不够,newSlice 会拥有一个全新的底层数组,其容量是原来的两倍(Go 会自动完成这个操作,一旦元素个数超过 1000,增长因子会设为 1.25)")
 apendSlice3()
 apendSlice4()
 rangeSlice()
 fmt.Println("在使用 range 遍历 slice 的时候,range 会创建每个元素的副本,每次迭代的变量的地址是相同的,说明迭代过程复用了这个变量,也是一种防止内存浪费的做法。")
 equalSlice()
}

//makeSlice 数据切片的初始化
func makeSlice() {
 //声明数据切片
 var slice1 []int //可以直接使用,直接使用append方法

 //初始化数据切片,指定数据切片的长度
 slice2 := make([]int, 10)
 //初始化数据切片,指定数据切片的长度和切片的最大容量
 slice3 := make([]int, 10, 100)
 //声明数据切片并赋值数据切片,此时数据切片的长度和最大容量相等
 slice4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

 fmt.Printf("slice1 = %v\n len(slice1) == %d,\t cap(slice1) == %d\n\n", slice1, len(slice1), cap(slice1))
 fmt.Printf("slice2 = %v\n len(slice2) == %d,\t cap(slice2) == %d\n\n", slice2, len(slice2), cap(slice2))
 fmt.Printf("slice3 = %v\n len(slice3) == %d,\t cap(slice3) == %d\n\n", slice3, len(slice3), cap(slice3))
 fmt.Printf("slice4 = %v\n len(slice4) == %d,\t cap(slice4) == %d\n\n", slice4, len(slice4), cap(slice4))
}

//apendSlice1 数据切片的追加
func apendSlice1() {
 oldSlice := make([]int, 10, 100)
 fmt.Printf("oldSlice = %v\n len(oldSlice) == %d,\t cap(oldSlice) == %d\n\n", oldSlice, len(oldSlice), cap(oldSlice))

 n, m := 1, 99 //0 <= n <= m < cap(oldSlice)
 newSlice := oldSlice[n:m] //[n:m)
 fmt.Printf("newSlice = %v\n len(newSlice) == %d,\t cap(newSlice) == %d\n\n", newSlice, len(newSlice), cap(newSlice))
 fmt.Printf("len(newSlice) == m-n == %d, cap(newSlice) == cap(oldSlice)-n == %d\n", len(newSlice), cap(newSlice))
}

//apendSlice2 数据切片的追加
func apendSlice2() {
 //数据切片是一个引用,但是容量扩充之后引用就会发生变化
 fmt.Println("\n\n数据切片的复制\n")
 parent_arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
 fmt.Printf("parent_arr = %v\n", parent_arr)

 child1_slice := parent_arr[0:10]
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n\n", child1_slice, len(child1_slice), cap(child1_slice))

 child2_slice := child1_slice[3:5]
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 child2_slice = append(child2_slice, 100)
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n\n", child1_slice, len(child1_slice), cap(child1_slice))

 child3_slice := parent_arr[3:5]
 fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
 child3_slice = append(child3_slice, 1000)
 fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 fmt.Println(&child3_slice[1], &child2_slice[1], "\n")

 child4_slice := parent_arr[3:5:5]
 fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
 child4_slice = append(child4_slice, 10000)
 fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n\n", child3_slice, len(child3_slice), cap(child3_slice))

 child5_slice := parent_arr[3:5:6]
 fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
 child5_slice = append(child5_slice, 100000)
 fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
 fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n\n", child4_slice, len(child4_slice), cap(child4_slice))

 child5_slice = append(child5_slice, 100000)
 fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
 fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
 fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
 fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
 fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
}

//apendSlice3 数据切片的追加
func apendSlice3() {
 //数据切片声明的时候,直接可以通过append添加元素
 //验证数据切片的容量是按照倍数增长的
 var slice []int
 i := 0
 for i < 10 {
  fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
  slice = append(slice, i)
  i++
 }
 fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))

 //直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)
 //会修改 slice 的底层 array,从而修改 slice
 func(arr []int) {
  arr[0] = 7
  fmt.Println(slice)
 }(slice)

 fmt.Println(slice)
}

//apendSlice4 数据切片的追加
func apendSlice4() {
 slice := [][]int{{10}, {100, 200}}

 fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
 fmt.Printf("slice[0] = %v\n len(slice[0]) == %d,\t cap(slice[0]) == %d\n\n", slice[0], len(slice[0]), cap(slice[0]))
 fmt.Printf("slice[1] = %v\n len(slice[1]) == %d,\t cap(slice[1]) == %d\n\n", slice[1], len(slice[1]), cap(slice[1]))

 slice[0] = append(slice[0], 20)
 fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
 fmt.Printf("slice[0] = %v\n len(slice[0]) == %d,\t cap(slice[0]) == %d\n\n", slice[0], len(slice[0]), cap(slice[0]))
 fmt.Printf("slice[1] = %v\n len(slice[1]) == %d,\t cap(slice[1]) == %d\n\n", slice[1], len(slice[1]), cap(slice[1]))
}

//rangeSlice 数据切片的遍历
func rangeSlice() {
 slice := []int{10, 20, 30, 40,} //这个","不是必须的,但是如果换行之后这个","就是必须的

 // 迭代每个元素,并显示值和地址,这个值是原来元素值的一份拷贝,修改这个值并不会改变原来元素的值
 for index, value := range slice {
  fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
  value *=10
 }
 fmt.Println(slice)
 //需要使用数据切片的引用才能实现对源数据的修改
 for index, value := range slice {
  fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
  slice[index] *=10
 }
 fmt.Println(slice)
}

//equalSlice 数据切片的比较
func equalSlice(){
 var slice1 []byte
 slice2 := []byte{}
 fmt.Println("slice1 == slice1: ", bytes.Equal(slice1, slice2))
 fmt.Println("slice1 == slice2: ", reflect.DeepEqual(slice1, slice2))
}

测试代码结果

slice1 = []
 len(slice1) == 0,  cap(slice1) == 0

slice2 = [0 0 0 0 0 0 0 0 0 0]
 len(slice2) == 10, cap(slice2) == 10

slice3 = [0 0 0 0 0 0 0 0 0 0]
 len(slice3) == 10, cap(slice3) == 100

slice4 = [1 2 3 4 5 6 7 8 9 10]
 len(slice4) == 10, cap(slice4) == 10

oldSlice = [0 0 0 0 0 0 0 0 0 0]
 len(oldSlice) == 10,   cap(oldSlice) == 100

newSlice = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 len(newSlice) == 98,   cap(newSlice) == 99

len(newSlice) == m-n == 98, cap(newSlice) == cap(oldSlice)-n == 99


数据切片的复制

parent_arr = [1 2 3 4 5 6 7 8 9 10]
child1_slice = [1 2 3 4 5 6 7 8 9 10] len(child1_slice) == 10,  cap(child1_slice) == 10

child2_slice = [4 5] len(child2_slice) == 2,    cap(child2_slice) == 7
child2_slice = [4 5 100] len(child2_slice) == 3,    cap(child2_slice) == 7
child1_slice = [1 2 3 4 5 100 7 8 9 10] len(child1_slice) == 10,    cap(child1_slice) == 10

child3_slice = [4 5] len(child3_slice) == 2,    cap(child3_slice) == 7
child3_slice = [4 5 1000] len(child3_slice) == 3,   cap(child3_slice) == 7
child1_slice = [1 2 3 4 5 1000 7 8 9 10] len(child1_slice) == 10,   cap(child1_slice) == 10
child2_slice = [4 5 1000] len(child2_slice) == 3,   cap(child2_slice) == 7
0xc0000880c0 0xc0000880c0 

child4_slice = [4 5] len(child4_slice) == 2,    cap(child4_slice) == 2
child4_slice = [4 5 10000] len(child4_slice) == 3,  cap(child4_slice) == 4
child1_slice = [1 2 3 4 5 1000 7 8 9 10] len(child1_slice) == 10,   cap(child1_slice) == 10
child2_slice = [4 5 1000] len(child2_slice) == 3,   cap(child2_slice) == 7
child3_slice = [4 5 1000] len(child3_slice) == 3,   cap(child3_slice) == 7

child5_slice = [4 5] len(child5_slice) == 2,    cap(child5_slice) == 3
child5_slice = [4 5 100000] len(child5_slice) == 3, cap(child5_slice) == 3
child1_slice = [1 2 3 4 5 100000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 100000] len(child2_slice) == 3, cap(child2_slice) == 7
child3_slice = [4 5 100000] len(child3_slice) == 3, cap(child3_slice) == 7
child4_slice = [4 5 10000] len(child4_slice) == 3,  cap(child4_slice) == 4

child5_slice = [4 5 100000 100000] len(child5_slice) == 4,  cap(child5_slice) == 6
child1_slice = [1 2 3 4 5 100000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 100000] len(child2_slice) == 3, cap(child2_slice) == 7
child3_slice = [4 5 100000] len(child3_slice) == 3, cap(child3_slice) == 7
child4_slice = [4 5 10000] len(child4_slice) == 3,  cap(child4_slice) == 4

当往 newSlice 中新增元素的时候,由于其容量不够,newSlice 会拥有一个全新的底层数组,其容量是原来的两倍(Go 会自动完成这个操作,一旦元素个数超过 1000,增长因子会设为 1.25)
slice = []
 len(slice) == 0,   cap(slice) == 0

slice = [0]
 len(slice) == 1,   cap(slice) == 1

slice = [0 1]
 len(slice) == 2,   cap(slice) == 2

slice = [0 1 2]
 len(slice) == 3,   cap(slice) == 4

slice = [0 1 2 3]
 len(slice) == 4,   cap(slice) == 4

slice = [0 1 2 3 4]
 len(slice) == 5,   cap(slice) == 8

slice = [0 1 2 3 4 5]
 len(slice) == 6,   cap(slice) == 8

slice = [0 1 2 3 4 5 6]
 len(slice) == 7,   cap(slice) == 8

slice = [0 1 2 3 4 5 6 7]
 len(slice) == 8,   cap(slice) == 8

slice = [0 1 2 3 4 5 6 7 8]
 len(slice) == 9,   cap(slice) == 16

slice = [0 1 2 3 4 5 6 7 8 9]
 len(slice) == 10,  cap(slice) == 16

[7 1 2 3 4 5 6 7 8 9]
[7 1 2 3 4 5 6 7 8 9]
slice = [[10] [100 200]]
 len(slice) == 2,   cap(slice) == 2

slice[0] = [10]
 len(slice[0]) == 1,    cap(slice[0]) == 1

slice[1] = [100 200]
 len(slice[1]) == 2,    cap(slice[1]) == 2

slice = [[10 20] [100 200]]
 len(slice) == 2,   cap(slice) == 2

slice[0] = [10 20]
 len(slice[0]) == 2,    cap(slice[0]) == 2

slice[1] = [100 200]
 len(slice[1]) == 2,    cap(slice[1]) == 2

Value: 10 Value-Addr: C000074DC8 ElemAddr: C000090060
Value: 20 Value-Addr: C000074DC8 ElemAddr: C000090068
Value: 30 Value-Addr: C000074DC8 ElemAddr: C000090070
Value: 40 Value-Addr: C000074DC8 ElemAddr: C000090078
[10 20 30 40]
Value: 10 Value-Addr: C000074E10 ElemAddr: C000090060
Value: 20 Value-Addr: C000074E10 ElemAddr: C000090068
Value: 30 Value-Addr: C000074E10 ElemAddr: C000090070
Value: 40 Value-Addr: C000074E10 ElemAddr: C000090078
[100 200 300 400]
在使用 range 遍历 slice 的时候,range 会创建每个元素的副本,每次迭代的变量的地址是相同的,说明迭代过程复用了这个变量,也是一种防止内存浪费的做法。
slice1 == slice1: true
slice1 == slice2: false

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:木鱼cavalry

查看原文:GO语言基础_slice

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

603 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传