体系课-数据可视化入门到精通-打造前端差异化竞争力(已完结)

#1

download:体系课-数据可视化入门到精通-打造前端差异化竞争力(已完结)


Slice是Go中的一个基本数据结构,可以用来管理数据集。切片的设计思想来源于动态数组的概念,使得一个数据结构可以自动增减,更加方便开发者。但是切片本身不是动态数据或数组指针。常见的切片操作包括重新切片、追加和复制。同时,切片具有可索引和迭代的优良特性。
切片数据结构
切片本身不是动态数组或数组指针。其内部数据结构通过指针引用底层数组,并设置相关属性将数据读写操作限制在指定区域。Slice本身是一个只读对象,它的工作机制类似于数组指针的封装。
Slice是对数组连续段的引用,所以是引用类型(所以更类似于C/C++中的数组类型,或者Python中的列表类型)。这个片段可以是整个数组,也可以是由起始和结束索引标识的某些项的子集。请注意,由终止索引标识的项目不包括在切片中。Slice提供了一个带有数组的动态窗口。
给定项的切片索引可能小于相关数组中相同元素的索引。与数组不同,切片的长度可以在运行时修改,从0到相关数组的长度:切片是长度可变的数组。
切片的数据结构定义如下:
类型切片结构{
数组不安全。指针
len int
cap int
}
复制代码

切片结构由三部分组成,指针是指向数组的指针,len代表当前切片的长度,cap是当前切片的容量。Cap总是大于或等于len。

第二,创建切片
make函数允许在运行时动态指定数组长度,绕过数组类型必须使用编译时常量的约束。
创建切片有两种形式,make创建切片和空切片。
制作切片文字
func makeslice(et *_type,len,cap int)切片{
//根据切片的数据类型获取切片的最大容量。
max elements:= maxSliceCap(et . size)
//比较切片的长度。长度范围应该在[0,maxElements]之间
if len < 0 | | uintptr(len)> max elements {
panic(errorString(" make slice:len超出范围"))
}
//比较切片的容量。容量范围应该在[len,maxElements]之间
if cap < len | | uintptr(cap)> max elements {
panic(errorString(" make slice:cap超出范围"))
}
//根据切片的容量申请内存
p:= mallocgc(et . size * uintptr(cap),et,true)
//返回已应用内存的片的第一个地址。
返回切片{p,len,cap}
}
复制代码
还有一个版本的int64:
func makeslice64(et *_type,len64,cap64 int64)切片{
len := int(len64)
if int64(len)!= len64
panic(errorString(" make slice:len超出范围"))
}

cap := int(cap64)
如果int64(cap)!= cap64 {
panic(errorString(" make slice:cap超出范围"))
}

返回makeslice(et,len,cap)
}
复制代码
实现原理同上,只是增加了将int64转换为int的步骤。

上图显示了由make函数创建的len = 4和cap = 6的切片。适用于6种int内存大小的内存。因为len = 4,后两个暂时不能访问,但是容量还在。此时,数组中的每个变量都是0。
零和空切片
零段和空段也是常用的。
var slice []int
复制代码

Nil slice用于许多标准库和内置函数中。当描述一个不存在的切片时,有必要使用nil slice。例如,当异常发生时,函数返回的切片是nil slice。nil片的指针指向nil。
空切片通常用于表示空集。例如,如果数据库查询找不到任何结果,则可以返回一个空切片。
silce := make( []int,0)
slice := []int{ }
复制代码
空片和nil片的区别在于,空片指向的地址不是nil,而是内存地址,但不分配任何内存空间,即底层元素包含0个元素。
最后,有一点需要说明。无论使用nil slice还是empty slice,调用内置函数append、len和cap都会产生相同的效果。
三片展开
当一个存储片的容量已满时,需要对其进行扩展。如何扩张,策略是什么?
func growslice(et _type,old slice,cap int)切片{
如果raceenabled {
callerpc := getcallerpc(不安全。指针(&et))
racereadrangepc(old.array,uintptr(old.len
int(et.size)),callerpc,funcPC(growslice))
}
如果msanenabled {
msanread(old.array,uintptr(old.len*int(et.size)))
}

如果et.size == 0 {
//如果要扩容的新容量比原来的容量还要小,意味着容量会减少,可以直接报慌。
如果cap < old.cap {
panic(errorString(" grow slice:cap超出范围"))
}

//如果当前切片的大小为0,调用扩展方法,则生成并返回新容量的新切片。
返回切片{unsafe。指针(&zerobase),old.len,cap}
}

//这里是扩张策略。
newcap := old.cap
双重上限:=新上限+新上限
如果cap > doublecap {
newcap = cap
}否则{
如果old.len < 1024 {
newcap = doublecap
}否则{
对于newcap < cap {
newcap += newcap / 4
}
}
}

//计算新切片的容量和长度。
var lenmem,newlenmem,capmem uintptr
const ptrSize =不安全。sizeof((字节)(无))
开关组件尺寸{
案例1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
cap mem = round upsize(uintptr(new cap))
newcap = int(capmem)
案例大小:
len mem = uintptr(old . len)
ptr size
newlenmem = uintptr(cap) * ptrSize
cap mem = round upsize(uintptr(new cap)* ptr size)
newcap = int(capmem / ptrSize)
默认值:
len mem = uintptr(old . len)* et . size
newlenmem = uintptr(cap) * et.size
cap mem = round upsize(uintptr(new cap)* et . size)
newcap = int(capmem / et.size)
}

//确定非法值,确保容量在增加,且不超过最大容量。
if cap < old . cap | | uintptr(new cap)> maxSliceCap(et . size){
panic(errorString(" grow slice:cap超出范围"))
}

var p不安全。指针
if et.kind&kindNoPointers!= 0 {
//继续扩展旧切片后面的容量
p = mallocgc(capmem,nil,false)
//将lenmem,若干字节,从old.array地址复制到p的地址。
memmove(p,old.array,lenmem)
//先把P地址加到新的容量上得到新的片容量的地址,然后初始化新的片容量的地址后面的内存capmem-newlenmem字节。然后为append()操作腾出空间。
memclrnoheiutors(add(p,newlenmem),capmem-newlenmem)
}否则{
//为新切片重新应用新数组
//重新申请capmen的大内存地址,初始化为0值。
p = mallocgc(capmem,et,true)
如果!writeBarrier.enabled {
//如果还不能打开写锁,只能从old.array复制lenmem大小的字节到p的地址。
memmove(p,old.array,lenmem)
}否则{
//循环复制旧切片的值
for I:= uintptr(0);i < lenmemi += et.size
typedmemmove(et,add(p,I),add(old.array,I))
}
}
}
//返回最终的新切片,容量更新为最近一次扩展后的容量。
返回切片{p,old.len,newcap}
}
复制代码
就是以上拓展的实现。需要注意的主要有两点,一是扩展时的策略,二是扩展是否会生成一个全新的内存地址或者追加到原地址之后。
扩张战略
func main() {
slice := []int{10,20,30,40}
新闻切片:=追加(切片,50)
fmt。Printf("切片前= %v,指针= %p,长度= %d,帽= %d\n ",切片,&切片,长度(切片),帽(切片))
fmt。Printf("在新闻片= %v之前,指针= %p,len = %d,cap = %d\n ",新闻片,&新闻片,len(新闻片),cap(新闻片))
新闻片[1] += 10
fmt。Printf("后切片= %v,指针= %p,长度= %d,帽= %d\n ",切片,&切片,长度(切片),帽(切片))
fmt。Printf("在新闻片= %v,指针= %p,len = %d,cap = %d\n "之后,新闻片,&新闻片,len(新闻片),cap(新闻片))
}
复制代码
输出结果:
切片= [10 20 30 40]之前,指针= 0xc4200b0140,len = 4,cap = 4
在newSlice = [10 20 30 40 50]之前,指针= 0xc4200b0180,len = 5,cap = 8
slice = [10 20 30 40]之后,指针= 0xc4200b0140,len = 4,cap = 4
在newSlice = [10 30 30 40 50]之后,指针= 0xc4200b0180,len = 5,cap = 8
复制代码

从图中我们很容易看出,新切片与之前的切片不同,因为新切片改变了一个值,而这个值并不影响原来的数组。新切片指向的数组是一个全新的数组。而且上限容量也变了。他们之间发生了什么?
Go中切片扩展的策略如下:
如果切片的容量小于1024个元素,则在扩展时容量会加倍。上面的例子也验证了这个情况,总容量从原来的4个翻倍到现在的8个。
一旦元素数量超过1024,增长因子就变成1.25,即每次增加原来容量的四分之一。
注意:扩展的容量是针对原始容量,而不是原始数组的长度。