golang 数据二 (切片)

在项目开发过程中,更多的场景是需要一个长度可以动态更新的数据存储结构,切片本身并非是动态数组或数组指针,他内部通过指针引用底层数组,并设定相关属性将数据读写操作限定在指定区域内。比如:

/runtime/slice.go

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

切片初始化

切片有两种基本初始化方式:

切片可以通过内置的make函数来初始化,初始化时len=cap,一般使用时省略cap参数,默认和len参数相同,在追加元素时,如果容量cap不足时,将按len的2倍动态扩容。

通过数组来初始化切片,以开始和结束索引位置来确定最终所引用的数组片段。

//make([]T, len, cap) //T是切片的数据的类型,len表示length,cap表示capacity
{
    s := make([]int,5)      //len: 5  cap: 5
    s := make([]int,5,10)    //len: 5  cap: 10
    s := []int{1,2,3}
} 

{
    arr := [...]int{0,1,2,3,4,5,6,7,8,9}
    s1 := arr[:]
    s2 := arr[2:5]
    s3 := arr[2:5:7]
    s4 := arr[4:]
    s5 := arr[:4]
    s6 := arr[:4:6]

    fmt.Println("s1: ",s1, len(s1),cap(s1))
    fmt.Println("s2: ",s2, len(s2),cap(s2))
    fmt.Println("s3: ",s3, len(s3),cap(s3))
    fmt.Println("s4: ",s4, len(s4),cap(s4))
    fmt.Println("s5: ",s5, len(s5),cap(s5))
    fmt.Println("s6: ",s6, len(s6),cap(s6))
}
输出:
s1:  [0 1 2 3 4 5 6 7 8 9] 10 10
s2:  [2 3 4] 3 8
s3:  [2 3 4] 3 5
s4:  [4 5 6 7 8 9] 6 6
s5:  [0 1 2 3] 4 10
s6:  [0 1 2 3] 4 6

通过上例说明cap 是表示切片所引用数组片段的真实长度,len是表示已经赋过值的最大下标(索引)值加1.

注意下面两种初始化方式的区别:

{
    var a  []int
    b := []int{}
    fmt.Println(a==nil,b==nil)

    fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))
    fmt.Printf("b: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&b)))
    fmt.Printf("a size %d\n", unsafe.Sizeof(a))
    fmt.Printf("b size %d\n", unsafe.Sizeof(b))
}
输出:
true false
a: &reflect.SliceHeader{Data:0x0, Len:0, Cap:0}
b: &reflect.SliceHeader{Data:0x5168b0, Len:0, Cap:0}
a size 24
b size 24
说明:
1. 变量b的内部指针被赋值,即使该指针指向了runtime.zerobase,但它依然完成了初始化操作
2. 变量a表示一个未初始化的切片对象,切片本身依然会分配所需的内存

切片之间不支持逻辑运算符,仅能判断是否为nil,比如:

{
    var a  []int
    b := []int{}
    fmt.Println(a==b) //invalid operation: a == b (slice can only be compared to nil)
}

reslice

在原slice的基础上进行新建slice,新建的slice依旧指向原底层数组,新创建的slice不能超出原slice

的容量,但是不受其长度限制,并且如果修改新建slice的值,对所有关联的切片都有影响,比如:

{
    s := []string{"a","b","c","d","e","f","g"}

    s1 := s[1:3]        //b,c
    fmt.Println(s1, len(s1),cap(s1))
    s1_1 := s1[2:5]        //c,d,e
    fmt.Println(s1_1, len(s1_1),cap(s1_1))
}
输出:
[b c] 2 6
[d e f] 3 4

append

向切片尾部追加数据,返回新的切片对象; 数据被追加到原底层数组,如果超出cap限制,则为新切片对象重新分配数组,新分配的数组cap是原数组cap的2倍,比如:

{
    s := make([]int,0,5)
    s = append(s , 1)
    s = append(s , 2,3,4,5)
    fmt.Printf("%p, %v, %d\n", s,s, cap(s))
                                                                                                                                                                                                                   
    s = append(s , 6)       //重新分配内存
    fmt.Printf("%p, %v, %d\n", s, s, cap(s))
}
输出:
0xc420010210, [1 2 3 4 5], 5
0xc4200140a0, [1 2 3 4 5 6], 10

如果是向nil切片追加数据,则会高频率的重新分配内存和数据复制,比如:

{
    var s []int
    fmt.Printf("%p, %v, %d\n", s,s, cap(s))
    for i:= 0; i < 10;i++{
        s = append(s, i)
        fmt.Printf("%p, %v, %d\n", s, s, cap(s))
    }
}

所以为了避免程序运行中的频繁的资源开销,在某些场景下建议预留出足够多的空间。

copy

两个slice之间复制数据时,允许指向同一个底层数组,并允许目标区间重叠。最终复制的长度以较短的切片长度(len)为准,比如:

{
    s1 := []int{0, 1, 2, 3, 4, 5 ,6}
    s2 := []int{7, 8 ,9}
    copy(s1,s2)
    fmt.Println(s1,len(s1),cap(s1))
    s1 = []int{0, 1, 2, 3, 4, 5 ,6}
    s2 = []int{7, 8 ,9} 
    copy(s2,s1)
    fmt.Println(s2,len(s2),cap(s2))
}

那么可不可以在同一切片之间复制数据呢?

在项目开发过程中,如果slice长时间引用一个大数组中很小的片段,那么建议新建一个独立的切片,并复制出所需的数据,以便原数组内存可以被gc及时释优化回收。

时间: 2024-10-10 05:11:57

golang 数据二 (切片)的相关文章

boost::asio async_write也不能保证一次发完所有数据 二

只有看boost源码才能弄明白发生了什么.首先我是将vector里面写入了数据,然后用boost::asio::buffer将vector构造成了mutable_buffer_1对象. 参考该文档的重载形式:http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/reference/buffer/overload24.html [cpp] view plaincopyprint? buffer (24 of 28 overloads) C

golang 数据建模json解析

1.0 controller get请求 type MainController struct { beego.Controller } func (this *MainController) Get() { this.Data["IsHome"] = true this.Data["Website"] = "s.me" this.Data["Email"] = "[email protected]" th

php 在同一个表单中添加和修改数据 二

好吧,其实我这人不看重访问量的,但是今天一天的访问量比我去年发的一篇还要多. 我还是有点小小的惊讶的.作为一个做技术的屌丝.不,我不认为自己是屌丝,我觉得编程是一件高大山的职业.虽然很累,但是确实能让你每天的生活很充实. No matter whether you believe it or not,anyhow I believe it. 今天接着上一篇来说,我们只是简单的文章的上传和修改. 如果涉及到附件呢.在修改的时候如果需要更换附件的时候,这真的是一件很头痛的事情. 我不知道大家是怎样实

golang数组与切片

golang中坑的慢慢踩! 我们都知道golang中的切片是引用类型,但是在函数中使用append给切片追加元素是行不通的,需要使用切片的指针类型才能增加元素 数组的使用: package main import ( "fmt" ) func ChangeArr(arr [5]int) { arr[0] = 6 } func ChangeArr2(arr *[5]int) { arr[0] = 6 } func main() { a := [5]int{1, 2, 3, 4, 5} P

pytorch张量数据索引切片与维度变换操作大全(非常全)

(1-1)pytorch张量数据的索引与切片操作1.对于张量数据的索引操作主要有以下几种方式:a=torch.rand(4,3,28,28):DIM=4的张量数据a(1)a[:2]:取第一个维度的前2个维度数据(不包括2):(2)a[:2,:1,:,:]:取第一个维度的前两个数据,取第2个维度的前1个数据,后两个维度全都取到:(3)a[:2,1:,:,:]:取第一个维度的前两个数据,取第2个维度的第1个索引到最后索引的数据(包含1),后两个维度全都取到:(4)a[:2,-3:]:负号表示第2个维

Golang(二)基本概念

类型 18个基本类型:bool.string.rune.byte.int.uint.int8.uint.int8.int16.uint16.int32.uint32.int64.uint64.float64.complex64.complex128 7个复合类型:array.struct.function.interface.slice.map.channel 其中,slice.map 和 channel 都是引用类型 最后,还有一种无符号的整数类型 uintptr,没有指定具体的 bit 大小

golang 数据一 &nbsp; (字符串、数组和数组指针)

从如下几个方面介绍GO语言的数据 1. 字符串 2. 数组 3. 切片 4. 字典 5. 结构 字符串 Go语言中的字符串是由一组不可变的字节(byte)序列组成从源码文件中看出其本身是一个复合结构 string.go  type stringStruct struct {     str unsafe.Pointer         len int } 字符串中的每个字节都是以UTF-8编码存储的Unicode字符字符串的头部指针指向字节数组的开始但是没有NULL或'\0'结尾标志. 表示方式

传感器从 “组网成功 -&gt; 上报数据“ “二次上电” “删除节点”整个数据流

//一. 出厂首次上电, 组网之后报文 //1. 网关主动上报新 F3EF 节点(网关收到之后,并查询设备类型,类型确定之后,取消网关上报节点)cpp:111# 0x44 0x55 0x00 0x12 0x4B 0x00 0x08 0xC6 0xF2 0x4E 0x08 0x10 0x00 0x0D 0x00 0x01 0x0B 0x00 0x12 0x4B 0x00 0x08 0xC6 0xF3 0xEF 0xE9 0xF3 //2. 中控主动查询设备类型cpp:111# 0x44 0x55

获取百度地图POI数据二(准备搜索关键词)

上篇讲到  想要获取尽可能多的POI数据 需要准备尽可能多的搜索关键字   那么这些关键字如何得来呢?   本人使用的方法是通过一些网站来获取这些关键词   http://poi.mapbar.com/这个网站有全国各地的POI数据  对各个城市的POI数据都有归类  我便是从这个网站上面获取了上海市的各个类别的关键词  比如上海市所有的门牌号码  公路名称  地铁名称等等  下面介绍如何获取这些信息 和获取百度POI数据所用的方法一样,都是通过分析这个网站的url然后替换其中的参数获取不同的数