教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang slice 切片原理

Golang slice 切片原理

发布时间:2021-12-08   编辑:jiaochengji.com
教程集为您提供Golang slice 切片原理等资源,欢迎您收藏本站,我们将为您提供最新的Golang slice 切片原理资源

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

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

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


其中 <em>len</em> 和 <em>cap</em> 就是我们在调用 <em>len(slice)</em> 和 <em>cap(slice)</em> 返回的值。

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

<pre><code class="lang-go hljs">// 按照上图定义的数据结构 type Slice struct { ptr unsafe.Pointer // Array pointer len int // slice length cap int // slice capacity }</code></code></pre>

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

<pre><code class="lang-go hljs">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)) }</code></code></pre>

运行一下查看结果

<pre><code class="lang-go hljs">$ 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 </code></code></pre>

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

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

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

这是一个 <em>append</em> 的测试

<pre><code class="lang-go hljs">// 每次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) </code></code></pre>

运行结果

<pre><code class="lang-go hljs">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]</code></code></pre>

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

<blockquote>

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

</blockquote>

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

创建切片

<pre><code class="lang-go hljs">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) </code></code></pre>

运行结果

<pre><code class="lang-go hljs">len:5 cap: 5 array ptr: 0xc820012210 Array: [1 2 3 4 5] len:2 cap: 4 array ptr: 0xc820012218 Array [2 3] </code></code></pre>

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

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

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

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

附上 go 源码中 <em>slice</em> 的数据结构定义

<pre><code class="lang-go hljs">type slice struct { array unsafe.Pointer len int cap int }</code></code></pre>
到此这篇关于“Golang slice 切片原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
[go语言]-slice实现的使用和基本原理
go(golang)之slice的小想法1
golang slice 最后一个元素_Go 常见的数据结构 Slice
Golang语言slice实现原理及使用方法
golang-数组和切片的区别
【Go语言】【7】GO语言的切片
Golang 切片(slice)扩容机制源码剖析
Go语言之父详述切片与数组的不同
golang切片内存应用技巧
Go语言slice切片详解

[关闭]
~ ~