<Go语言学习笔记>【数组与切片】
*本文为极客时间《Go语言核心36讲》的学习笔记,梳理了索引相关的知识点。
Go 语言的切片类型属于引用类型,同属引用类型的还有字典类型、通道类型、函数类型等;而 Go 语言的数组类型则属于值类型,同属值类型的有基础数据类型以及结构体类型。
两者区别
简单的说,数组类型的长度是固定的,而切片类型是可变长的。数组的容量永远等于其长度,都是不可变的。
切片
切片的结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的,可以参考下面这段源码。
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
// NOTE: Produce a 'len out of range' error instead of a
// 'cap out of range' error when someone does make([]T, bignumber).
// 'cap out of range' is true too, but since the cap is only being
// supplied implicitly, saying len is clearer.
// See golang.org/issue/4085.
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)
}
切片的扩容和缩容
切片的扩容
假设原有切片的容量是A,当切片需要扩容的时候:
第一,会先判定需要扩容的容量X,如果X大于两倍的A,那么直接扩容超过X。也就是说新的切片的容量将会是大于等于X。
第二,如果X小等于两倍的A,此时需要进行再一次的判断,旧容量A如果小于1024,直接扩容两倍的A,此时新的切片容量是2A(理论上)。
第三,如果X小等于两倍的A,且旧容量A大于1024,会循环扩容1.25倍,直到大于X为止。此时的新切片的容量是(n 1.25 A)
以上的计算是理论上的值,实际上的值可能会稍微大一些。
具体代码:/runtime/slice.go 中的 growslice方法。
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4 //等价于 newcap = 1.25*newcap
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
切片的缩容
Go 本身没有切片缩容后,底层数组不会被释放掉。缩容次数多了,会占用很多内存。
可以用copy的方法,创建新的切片和底层数组。并把原来的切片置nil。
切片的底层数组什么时候会替换
准确的说,一个切片不存在底层数组被替换的情况。当一个切片容量不够时,会给他创建一个新的切片,这个切片有自己的底层数组,自己的结构,自己的内存地址。
我们看到某个切片变量被扩容了,实际上是这个变量内容发生了变化。具体而言,该变量的内存地址不变,但是地址里的东西发生了变化。
举一个形象的例子:假设第一次拜访黄河南路1号别墅的时候,这里住了一家三口人。后来发生了一些变化(对应扩容了),我们再一次去拜访这个地址,发现地址没有变化,房子也没有变化,但是变成一家五口人(其中三口人你认识,另外两人你不认识)但这一家人已经不是以前那一家人了。(某些科幻片的设定)
真正会导致底层数据发生变化的只有扩容的时候。因为数组不能被扩容这个缘故,需要重新创建一个新的底层数组,并创建一个新的切片信息。缩容并不会。
a := []int{1,2,3,4,5,6,}
println(a)
//a = a[:3] //裁剪三个
//println(a) //地址不会变化,容量不会变化,长度会变化
a = append(a,make([]int, 5)...) //增加5个元素
println(a) //地址 容量,长度 都会变
一些拓展问题
关于append的之后,切片的具体变化情况。
如果append时,引发了切片扩容,那么新的切片内容会发生变化,包括底层数组,长度。如果没有触发扩容,那么只有长度会发生变化。具体可以看代码:
s6 := make([]int, 0)
//s6 := make([]int, 1,10)
println(s6)
fmt.Printf("The capacity of s6: %dn", cap(s6))
for i := 1; i <= 5; i++ {
s6 = append(s6, i)
println(s6) //一旦触发扩容,地址信息会变
fmt.Printf("s6(%d): len: %d, cap: %dn", i, len(s6), cap(s6)) //长度在稳定增加,但是容量会跳着增加
}
fmt.Println()
range 循环时切片的具体变化
切片在range 循环时,value的赋值一样是值传递,本身的地址不变,内容会变。并且循环内对value 的修改,不会影响原来的切片内容。
var b = [6]int{1,2,3,4,5,6,}
for key, value := range b {
fmt.Printf("value的值:%d,value的地址:%x,切片该元素的地址:%xn", value, &value, &b[key])
//Value的值不会变,value 的地址不变
//println(reflect.TypeOf(value))
value += 1 //验证下会不会改变原
}
fmt.Printf("value的值:%d",b) //值没有加1
空切片 与nil切片
var a []int //nil切片,只定义了类型,slice.array内容指向nil。
println(a) //[0/0]0x0
b := make( []int , 0 ) //空切片,有类型,有地址
b := []int{ }
println(b) // [0/0]0xc00003e778
- nil切片被用在很多标准库和内置函数中,返回一个不存在的切片。
- 空切片是一个定义好的,分配好内存的切片,只是切片里面没有任何元素。
make 和 new 的区别
Make 是专门用来创建 slice、map、channel 的值的。它返回的是被创建的值,并且立即可用。
New 是申请一小块内存并标记它是用来存放某个值的。它返回的是指向这块内存的指针,而且这块内存并不会被初始化。或者说,对于一个引用类型的值,那块内存虽然已经有了,但还没法用(因为里面没有针对那个值的数据结构)。
所以,对于引用类型的值,不要用 new,能用 make 就用 make,不能用 make 就用复合字面量来创建。
- 在Python机器学习中如何索引、切片和重塑NumPy数组
- HelloWorld,我的第一趟旅程出发点
- Android OpenGL开发实践 - GLSurfaceView对摄像头数据的再处理
- 走进科学:对七夕“超级病毒”XX神器的逆向分析
- 机器学习 - 朴素贝叶斯分类器的意见和文本挖掘
- 认知指纹:颠覆性的身份认证技术
- 跟我学姿势:极客教你如何科学的看电影
- Discuz 5.x/6.x/7.x投票SQL注入分析
- 论如何高效的挖掘漏洞
- Rxjava + retrofit + dagger2 + mvp搭建Android框架
- 走进科学:如何正确的隐藏自己的行踪
- 比特儿(Bter.com) 比特币交易平台被盗事件全解析
- BitTorrent Bleep:无法被监控的聊天软件
- QQ蠕虫的行为检测方法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- String、StringBuffer、StringBuiler深入分析,看这一篇就够了
- Windows安装pip方法
- 通过jvm字节码研究Synchronized
- 学synchronized锁升级过程,吊打面试官
- jvm启动加载类的全过程,全网最全一篇,告诉你什么是双亲委派机制
- sonar+Jenkins 构建代码质量自动化分析平台
- 几百行代码,实现了微信群聊,神奇!
- 深入解析==与equals()区别
- 工作中常用的十款idea插件
- Linux系统rsync数据同步服务介绍
- 清空表与删除表mysql
- Java虚拟机-JVM组成结构详解
- 解决Chunkize warning while installing gensim问题
- numpy的random模块
- MySQL如何快速生成千万数据量?