关于golang:Go数据结构系列之-Array-and-Alice

40次阅读

共计 3735 个字符,预计需要花费 10 分钟才能阅读完成。

概述

在应用 Go 开发的时候,数组和切片常常被应用到,这篇文章来简略聊聊吧。

数组 array

在 Go 中,有两种形式能够初始化数组


func main() {userId := [3]int{1, 2, 3}
  userName := [...]string{"wqq", "curry", "joke"}
}
 

一种是显式的定义数组的大小,另一种通过 […] 申明数组,Go 会在编译期间推导出数组的大小。

既然应用了数组,少不了遍历,在 Go 中遍历数组个别也就两种形式。


func main() {userIds := [3]int{1, 2, 3}
  names := [...]string{"wqq", "curry", "joke"}

  for i := 0; i < len(names); i++ {fmt.Printf("user id is:%v,user name is:%v\n", userIds[i], names[i])
  }

  for index, item := range names {fmt.Printf("user id is:%v,user name is:%v\n", userIds[index], item)
  }
}

第一种就是你所认知的 for 循环。第二种能够应用 for/range 表达式,该表达式返回两个值,第一个值是索引,第二个值对应此索引的元素值。range 不单单能遍历数组,还能遍历 slice、map、channel 等汇合构造。当然这些不在这篇文章的探讨范畴内。

切片 slice

切片实质上是动静数组,它的底层蕴含了对数组的援用。切片的长度是动静的,能够随便的对其进行 append 操作,在应用的过程中,如果容量有余,会主动进行扩容操作。咱们能够从源码看看 slice 的构造。源码位于 src/runtime/slice.go,更多底层常识能够自行查看源码。


type slice struct {
  array unsafe.Pointer // 底层数组的指针地位
  len   int // 切片以后长度
  cap   int // 容量,当容量不够时,会触发动静扩容的机制
}
 

同理,初始化 slice 的形式也是多样的。

  • 应用 make 关键字
  • 和数组一样,应用字面量初始化
  • 通过下标的形式获取数组或者切片的一部分,生成 slice
func main() {
  // 字面量初始化
  userIds1 := []int{1, 2, 3}

  // make 初始化 slice 的长度为 5,容量为 10
  userIds2 := make([]int, 5,10)

  // 通过下标的形式获取数组的一部分作为 alice
  userArray := [5]string{"curry", "wqq", "lisa", "tony", "james"}

  // 获取从索引下标 0 开始,到下标 3(不包含 3)
  user := userArray[0:3]
  fmt.Printf("userIds1:%v,userIds2:%v,userSlice:%v\n", userIds1, userIds2, user)
}
 

这里就拿 make 初始化切片进行阐明。

      注: 图片起源《Go 编程专家》

 这段初始化操作示意 slice 的长度是 5,容量是 10,array 字段存储的是援用数组的指针地位。因为长度是 5,咱们能够应用下标 0-4 来操作此 slice。同时容量是 10,所以后续向 slice 增加新数据临时不须要重新分配新内存。

那数组和切片有什么关联呢?

咱们看看通过下标的形式获取数组数据,初始化切片的一种模式。

func main() {userArray := [4]string{"curry", "wqq", "lisa", "tony"}

  // 获取从索引下标 0 开始,到下标 3(不包含 3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
}
 

咱们用数组创立了 userSlice 的切片,此时 userSlice 将和 userArray 共用一部分内存。因而在批改 userSlice 索引 0 处的值时,操作的是同一块数组内存地址,从后果中能够看出失效了。

而后咱们开始往 userSlice 切片增加元素。

func main() {userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标 0 开始,到下标 3(不包含 3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  
  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  
}

查看输入后果:

能够看到,再向 userSlice 减少一个元素后,打印后果,数组和切片值一样,操作之后 userSlice 的 len 是 4,数组的长度也是 4。操作 append 后 userSlice 底层数组和 userArray 指向的还是同一个内存地址,并不需要产生扩容。

这时候,userSlice 所援用的底层数组曾经满了 (底层数组的长度是 4),咱们持续向 userSlice 减少元素。

func main() {userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标 0 开始,到下标 3(不包含 3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)

  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)

  userSlice = append(userSlice, "test2")
  fmt.Printf("userArray:%v,user:%v\n", userArray, userSlice)
 }

查看输入后果:

能够看到,userArray 的元素未变,因为这时候 userSlice 切片的长度曾经大于原指向的数组的长度了,userSlice 产生了扩容。

咱们能够做个试验测试一下,咱们批改数组 userArray 范畴内的 userSlice 元素的值,查看数组的数据是否会跟着扭转。

func main() {userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标 0 开始,到下标 3(不包含 3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)

  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)

  userSlice = append(userSlice, "test2")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
// 扭转索引 0 处的值
  userSlice[0] = "only one"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
}

最初一行曾经阐明了所有。此时的 userSlice 产生了扩容,不再和 userArray 共用原数组空间了。因而对 userSlice 的改变不会影响到 userArray。

对于扩容

后面提到在向切片增加新元素时如果此时切片的容量有余,会主动产生扩容。所谓扩容,也就是为以后切片生成新的一块内存空间,而后依据肯定规定,将原切片的元素全副拷贝到新的地址。扩容的规定在 src/runtime/slice.go 里的 growslice 办法。

这里截取了此办法中对于扩容规定的代码。

  • 如果冀望的新容量 (cap) 大于以后容量的两倍,那么就间接应用冀望的容量
  • 如果以后切片的长度 (len) 小于 1024,那么把以后容量翻倍
  • 如果以后切片的长度 (len) 大于等于 1024,那么每次把以后容量减少 1/4,直到新容量值大于冀望的的容量。

其实要写下去还有很多货色,比方,sliceCopy、底层编译逻辑 ……,有些货色我也没看过,学习的最好形式还是本人入手而后输入。

参考资料:

  • https://draveness.me/golang/
  • https://www.bookstack.cn/read…
  • https://medium.com/@hackintos…

正文完
 0