GO学习之切片操作

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
21、GO学习之 条件变量 sync.Cond
22、GO学习之 单例模式 sync.Once
23、GO 面试题总结一【面试官这样问】
24、GO 面试题进阶篇【面试官这样问】

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
前面学了一些 go 的基础知识,比如说 go 语言的基础,go 的数据类型和一些基本操作,if else switch for 等语法,当然除了简单的一些变量定义还有稍微复杂的,就是数据和切片了,那就针对切片专门进行学习一下,毕竟在开发中 切片 也是使用比较多的。

一、什么是切片(和数组有什么关系)

什么是切片呢?切片(Slice)是 Go 语言中的一种动态长度的数据结构,它是对数组的一个连续片段的引用,并且提供了一种方便且灵活的方式来操作和管理一组相同类型的元素。

1.1 切片的定义

切片的定义:

  1. 切片由三部分组成:指针长度容量
  2. 指针指向数组的第一个元素,切片的长度表示当前存储元素的个数,容量表示切片底层数组的长度

面试题: 切片和数组有什么关系呢?

  1. 切片是数组的一个连续片段的引用,提供了一种动态大小的、方便的方式来操作数组。
  2. 切片和数组的底层数据结构是相同的,都是连续的内存块,但是切片是一个动态长度的视图,而数组的长度是固定的。
  3. 对切片的修改会影响底层数组,因为切片是对数组的引用。同样的,对底层数组的修改也会影响切片。

总结起来,切片是一种方便且灵活的动态长度的数据结构,它是对数组的引用。通过切片,我们可以动态操作数组中的一部分元素,同时也提供了方便的切片操作和动态调整大小的能力。
切片和数组密切相关,切片提供了对底层数组的引用和操作,可以说切片是数组的一种便捷封装。

1.2 深拷贝和浅拷贝

关于深拷贝和浅拷贝,它们是指在复制数据结构时所涉及的深度和影响范围。

  • 浅拷贝:浅拷贝只复制了数据结构的最外层,而不会复制内部的引用类型(如切片、字典、指针等)所指向的数据。也就是说,浅拷贝只是复制了引用,而不是实际的数据。
  • 深拷贝:而深拷贝则是复制了所有,就是复制了引用和原始数据,复制后数据完全独立,互不影响。

1.3 扩容机制

切片扩容是指在切片容量不足以容纳更多的元素时,系统会自动增加切片的容量,以确保能容纳更多的元素进来。

  • 当切片的长度(即元素个数)超过容量时,切片会自动扩容
  • 切片的库容策略是按照一定的增长因子扩容的,一般情况下是容量的 2 倍,意味着每次扩容后新的容量是原来的 2 倍。
  • 但如果切片容量 小于1024 个元素时,每次扩容后增加原容量的 1/4,若容量 超过 1024(含1024) 时,每次扩容将会增加原容量的 1/2
  • 切片扩容过程中,会创建一个新的底层数组,并将原始数据复制到新数组中。
  • 在进行扩容操作时,Go语言会尽量减少内存的分配和拷贝次数,以提高性能。

1.4 是否线程安全

在 Go 中,切片本身 并不是线程安全的 。当多个 goroutine 同时对一个切片进行读写操作是,可能会导致竞争,从而引发数据错误或者运行时程序错误和其他不确定的行为。

  • 并发写操作: 当多个 goroutine 同时向一个切片进行写操作时,此时切片内部底层数组可能会被其他重新分配或者修改,导致不同的 goroutine 之间相互干扰,导致数据混乱或丢失。
  • 扩容操作: 在切片进行扩容时,会涉及到底层数组的重新分配和数据复制过程,这可能会导致在扩容过程中某些goroutine对切片的读写操作发生冲突,进而引发竞争条件和数据竞争的问题。

二、切片基本操作

2.1 切片定义

切片声明的方式有好几种, 可以直接 var arr []int,也可以通过 make 来初始化,或者直接可以简写为 s5 := []int{1, 2, 3},也可以直接为数组的一部分,具体看代码:

package main
import "fmt"
func main() {
	// 声明切片
	var s1 []int
	s2 := []int{}
	// 通过 make 来创建分片
	var s3 []int = make([]int, 10)
	var s4 []int = make([]int, 0, 5)
	// 简写方式
	s5 := []int{1, 2, 3}
	arr := [5]int{1, 2, 3, 4, 5}
	var s6 []int
	// 声明切片为数组的部分
	s6 = arr[1:4]
	if s1 == nil {
		fmt.Println("s1 is nil")
	}
	fmt.Println("s1: ", s1)
	fmt.Println("s2: ", s2)
	fmt.Println("s3: ", s3)
	fmt.Println("s4: ", s4)
	fmt.Println("s5: ", s5)
	fmt.Println("s6: ", s6)
}

执行结果:

s1 is nil
s1:  []
s2:  []
s3:  [0 0 0 0 0 0 0 0 0 0]
s4:  []
s5:  [1 2 3]
s6:  [2 3 4]

2.2 添加元素

使用 append 函数来对切片进行添加元素操作。

package main
import "fmt"
func main() {
	var list []int
	for i := 0; i < 5; i++ {
		// 添加元素
		list = append(list, i)
	}
	list = append(list, 100)
	fmt.Println("list: ", list)
}

执行结果:

list:  [0 1 2 3 4 100]

2.3 删除元素

需要注意的是,go 语言中对切片的元素删除不像 JAVA 中对 List 中元素的删除,直接用 remove() 方法,go 语言中切片没有函数来删除元素,这是因为在 go 语言中,切片的删除操作可以通过切片的切片操作来重新赋值实现,不需要专门的函数。
下面的实例是移除第二个元素。

package main
import "fmt"
func main() {
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	// 删除元素
	data3 := append(data[:2], data[3:]...)
	fmt.Println("被删除后的数组为:", data3)
}

执行结果:

被删除后的数组为: [0 1 3 4 5 6 7 8 9]

2.4 遍历

切片遍历可以直接用 for range 来实现,下面示例中,index 是下标,value 是值;
有时候我们只需要遍历 切片,但是不需要知道下标,可以用下划线 “_” 来代替,就是忽略下标的意思。

package main
import "fmt"
func main() {
	// 切片遍历
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice := data[:]
	for index, value := range slice {
		fmt.Printf("index %v 的值为 %v \n", index, value)
	}
	// 需要 下标(index) 的写法
	for _, value := range slice {
		fmt.Println(value)
	}
}

执行结果:

index 0 的值为 0
index 1 的值为 1
index 2 的值为 2
index 3 的值为 3
index 4 的值为 4
index 5 的值为 5
index 6 的值为 6
index 7 的值为 7
index 8 的值为 8
index 9 的值为 9

2.5 自定义contains函数

如果你写 JAVA 就会知道,我们判断一个元素是否在 List 中,可以直接调用 contains() 方法,但是在 go 语言中,却没有这个函数。
在上面的示例中,我们定义了一个名为 contains 的函数,它接受一个切片和一个要查找的元素作为参数。函数内部使用循环遍历切片,并逐个比较元素的值与目标元素是否相等。如果找到相等的元素,则返回 true 表示元素存在于切片中;

package main
import "fmt"
func main() {
	//定义数组
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	//切片
	slice := data[:]
	element := 5
	if contains(slice, element) {
		fmt.Println(element, " 在切片slice2中...")
	} else {
		fmt.Println(element, " 不在切片slice中...")
	}
}

func contains(data []int, element int) bool {
	for _, value := range data {
		if value == element {
			return true
		}
	}
	return false
}

三、总结

看到这里,go 语言中的切片就基本有了了解,也能写一写简单的功能了,不过相对于JAVA来说,go 语言中好多地方还是大不相同的,就对切片来说,表面上有点类似于 JAVA 的 ArrayList,但其实大相径庭,也需要更加去使用和了解才能正在的懂什么是切片。
那为什么 JAVA 中对 ArrayList 操作由 remove、contains等方法,但是 go 中却没有这样的函数呢?

是因为 go 语言的设计哲学强调简洁性和效率,保持语言的简介和清晰,尽量避免引入过多的内置函数和语法糖,让程序员能够简洁的方式表达意图,相对于大量的内置函数,go 语言更倾向于提供基础的数据结构和少了的核心函数,以便程序员构建自己的函数和工具。
不过,go 语言社区拥有丰富的第三方包和库,包含了各种函数和工具,我们可以寻找合适的第三方包,集成到我们自己的项目中使用。

切片就到这里吧!
感谢阅读!!!如有不妥之处欢迎纠正


微信公众号同步更新,欢迎━(`∀´)ノ亻!来聊!!!
Yphen聊码

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值