golang 中的 slice 非常强大,让数组操作非常方便高效。在开发中不定长度表示的数组全部都是 slice 。但是很多同学对 slice 的模糊认识,造成认为golang中的数组是引用类型,结果就是在实际开发中碰到很多坑,以至于出现一些莫名奇妙的问题,数组中的数据丢失了。

下面我们就开始详细理解下 slice ,理解后会对开发出高效的程序非常有帮助。

这个是 slice 的数据结构,它很简单,一个指向真实 array 地址的指针 ptrslice 的长度 len 和容量 cap



其中 lencap 就是我们在调用 len(slice)cap(slice) 返回的值。

我们来按照 slice 的数据结构定义来解析出 ptr, len, cap

// 按照上图定义的数据结构
type Slice struct {
ptr unsafe.Pointer // Array pointer
len int // slice length
cap int // slice capacity
}

下面写一个完整的程序,尝试把golang中slice的内存区域转换成我们定义的 Slice 进行解析

package main

import (
"fmt"
"unsafe"
) // 按照上图定义的数据结构
type Slice struct {
ptr unsafe.Pointer // Array pointer
len int // slice length
cap int // slice capacity
} // 因为需要指针计算,所以需要获取int的长度
// 32位 int length = 4
// 64位 int length = 8
var intLen = int(unsafe.Sizeof(int(0))) func main() {
s := make([]int, 10, 20) // 利用指针读取 slice memory 的数据
if intLen == 4 { // 32位
m := *(*[4 + 4*2]byte)(unsafe.Pointer(&s))
fmt.Println("slice memory:", m)
} else { // 64 位
m := *(*[8 + 8*2]byte)(unsafe.Pointer(&s))
fmt.Println("slice memory:", m)
} // 把slice转换成自定义的 Slice struct
slice := (*Slice)(unsafe.Pointer(&s))
fmt.Println("slice struct:", slice)
fmt.Printf("ptr:%v len:%v cap:%v \n", slice.ptr, slice.len, slice.cap)
fmt.Printf("golang slice len:%v cap:%v \n", len(s), cap(s)) s[0] = 0
s[1] = 1
s[2] = 2 // 转成数组输出
arr := *(*[3]int)(unsafe.Pointer(slice.ptr))
fmt.Println("array values:", arr) // 修改 slice 的 len
slice.len = 15
fmt.Println("Slice len: ", slice.len)
fmt.Println("golang slice len: ", len(s))
}

运行一下查看结果

$ go run slice.go

slice memory: [0 64 6 32 200 0 0 0 10 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0]
slice struct: &{0xc820064000 10 20}
ptr:0xc820064000 len:10 cap:20
golang slice len:10 cap:20
array values: [0 1 2]
Slice len: 15
golang slice len: 15

看到了,golang slice 的memory内容,和自定义的 Slice 的值,还有按照 slice 中的指针指向的内存,就是实际 Array 数据。当修改了 slice 中的len, len(s) 也变了。

接下来结合几个例子,了解下slice一些用法

声明一个Array通常使用 make ,可以传入2个参数,也可传入3个参数,第一个是数据类型,第二个是 len ,第三个是 cap 。如果不穿入第三个参数,则 cap=lenappend 可以用来向数组末尾追加数据。

这是一个 append 的测试

// 每次cap改变,指向array的ptr就会变化一次
s := make([]int, 1) fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s))) for i := 0; i < 5; i++ {
s = append(s, i)
fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
} fmt.Println("Array:", s)

运行结果

len:1 cap: 1 array ptr: 0xc8200640f0
len:2 cap: 2 array ptr: 0xc820064110
len:3 cap: 4 array ptr: 0xc8200680c0
len:4 cap: 4 array ptr: 0xc8200680c0
len:5 cap: 8 array ptr: 0xc82006c080
len:6 cap: 8 array ptr: 0xc82006c080
Array: [0 0 1 2 3 4]

看出来了吧,每次cap改变的时候指向array内存的指针都在变化。当在使用 append 的时候,如果 cap==len 了这个时候就会新开辟一块更大内存,然后把之前的数据复制过去。

实际go在append的时候放大cap是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap 。所以上面的测试中cap变化是 1, 2, 4, 8

在实际使用中,我们最好事先预期好一个cap,这样在使用append的时候可以避免反复重新分配内存复制之前的数据,减少不必要的性能消耗。

创建切片

s := []int{1, 2, 3, 4, 5}
fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
fmt.Println("Array:", s) s1 := s[1:3]
fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s1), cap(s1), *(*unsafe.Pointer)(unsafe.Pointer(&s1)))
fmt.Println("Array", s1)

运行结果

len:5 cap: 5 array ptr: 0xc820012210
Array: [1 2 3 4 5]
len:2 cap: 4 array ptr: 0xc820012218
Array [2 3]

在一个切片基础上创建新的切片 s1 ,新切片的 ptr 指向的就是 s1[0] 数据的内存地址。可以看到指针地址 0xc8200122100xc820012218 相差 8byte 正好是一个int类型长度,cap也相应的变为4

就写到这里了,总结一下,切片的结构是指向数据的指针,长度和容量。复制切片,或者在切片上创建新切片,切片中的指针都指向相同的数据内存区域。

知道了切片原理就可以在开发中避免出现错误了,希望这篇博客可以给大家带来帮助。

参考:https://blog.golang.org/go-slices-usage-and-internals

附上 go 源码中 slice 的数据结构定义

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

golang slice 切片原理的更多相关文章

  1. golang slice切片的原理以及内置函数cap, len

    golang中slice(切片)是常用的类型, slice是对数组进行封装 package main import ( "fmt" "strconv") fun ...

  2. golang:slice切片

    一直对slice切片这个概念理解的不是太透彻,之前学习python的就没搞清楚,不过平时就用python写个工具啥的,也没把这个当回事去花时间解决. 最近使用go开发又遇到这个问题,于是打算彻底把这个 ...

  3. golang基础---Slice切片

    切片Slice在go语言中是单独的类型(指向底层的数组),不同于python(对可迭代对象操作的工具),注意区分数组和slice的区别 定义一个空slice,格式var s []int,这种既没有长度 ...

  4. Go语言核心之美 3.2-slice切片

    Slice(切片)是长度可变的元素序列(与之相应,上一节中的数组是不可变的),每一个元素都有同样的类型.slice类型写作[]T.T是元素类型.slice和数组写法非常像,差别在于slice没有指定长 ...

  5. 3.9 Go Slice切片

    3.9 Go Slice切片 Go语言切片(Slice) 切片是可动态变化的序列,是对数组的引用,引用类型,遵循引用传递的机制 slice类型写作[ ]T,T是slice元素类型,var s1 []i ...

  6. Go(03) slice切片的使用

    原文链接 http://www.limerence2017.com/2019/05/08/golang05/#more golang 的引用类型和内置类型变量 golang 中变量类型分为引用类型和值 ...

  7. Golang Slice 总结

    数组 Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要要理解数组.数组类型由指定和长度和元素类型定义.数组不需要显式的初始化:数组元素会自动初始化为零值:Go的数组是值语义.一个数组变量 ...

  8. golang slice 源码解读

    本文从源码角度学习 golang slice 的创建.扩容,深拷贝的实现. 内部数据结构 slice 仅有三个字段,其中array 是保存数据的部分,len 字段为长度,cap 为容量. type s ...

  9. slice 切片实现 Slice object interface

    1.Python切片对象可以为任意类型 https://github.com/python/cpython/blob/master/Include/sliceobject.h /* Slice obj ...

随机推荐

  1. MySQL多线程复制故障(slave_pending_jobs_size_max)

    MySQL多线程复制故障(slave_pending_jobs_size_max) http://www.xuchanggang.cn/archives/1079.html

  2. [How to] 使用Xib来创建view

    1.简介 代码库 正如之前博客介绍的,xib可定义页面的某个部分,特别当此部分区域的view集中并且还有一些相互关联性(如隐藏等)是i特别适合使用xib来进行封装. 本文为[How to]使用自定义c ...

  3. vim的各种tips

    centos系统,修改vim的配置文件 /etc/vimrc 添加如下内容: 1) 打开 vimrc ,添加以下语句来使得语法高亮显示: syntax on 2) 如果此时语法还是没有高亮显示,那么在 ...

  4. vue 同页面不同参数

    项目:详情页中有一个模块为更多产品,点击也是跳转到详情页,也就是相同路由,不同参数. 试过的方法:用this.$router.push,并没有任何反应,没有任何请求,页面也未重新加载,用this.$e ...

  5. 同步方法-java

    除了同步代码块能实现同步执行外,同步方法也可以. 先看下同步代码块实现同步执行: public class Demo4 { public static void main(String[] args) ...

  6. JWT认证不通过导致不能访问视图的解决方案

    在做商城项目的购物车模块时,发现了一个问题. 需求:当用户登录时,添加商品到购物车的数据保存在redis.当用户未登录时,添加商品到购物车的数据保存在cookies.两个功能都写在一个视图里面.以JW ...

  7. SQL 分页通用存储过程

    USE [DB] GO /****** Object: StoredProcedure [dbo].[SP_AspNetPager] Script Date: 10/23/2015 16:37:33 ...

  8. C++编译常见错误

    error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To di ...

  9. 【hdoj_2391】FilthyRich

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=2391 题目大意:给定一个矩阵,从左上角第一个元素开始到右下最后一个元素,寻找一条路线,使得路线经过的矩阵元 ...

  10. 赤峰项目目前的mysql配置项目

    #BEGIN CONFIG INFO #DESCR: 4GB RAM, InnoDB only, ACID, few connections, heavy queries #TYPE: SYSTEM ...