iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang切片Slice底层源码简介
  • 360
分享到

Golang切片Slice底层源码简介

2023-06-06 18:06:26 360人浏览 八月长安
摘要

这篇文章将为大家详细讲解有关golang切片Slice底层源码简介,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。数组说切片前先说下数组。数组的两个特性一段连续内存地址,每个元素都是连续的元素的类型相同,并

这篇文章将为大家详细讲解有关golang切片Slice底层源码简介,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

数组

说切片前先说下数组。数组的两个特性

  • 一段连续内存地址,每个元素都是连续的

  • 元素的类型相同,并且元素个数固定

Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。

arr := [2]int{1,2}arr2 := arrfmt.Printf("%p %p",&arr ,&arr2)//切片slice1 := []int{1,2}slice2 := slice1fmt.Printf("%p %p",slice1 ,slice2)

切片

切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型.切片是一个长度可变的数组。

Slice 的数据结构定义如下:

runtime/slice.go#L13

type slice struct {    array unsafe.Pointer    len   int    cap   int}
  • array 就是底层数组的地址

  • len 切片的长度

  • cap 切片的容量

创建切片

src/runtime/slice.go#L83

func makeslice(et *_type, len, cap int) unsafe.Pointer {    mem, overflow := math.MulUintptr(et.size, uintptr(cap))    ....    return mallocGC(mem, et, true)}

基本逻辑就是根据容量申请一块内存。

切片扩容

扩容是当切片的长度大于容量的时候,底层数组已经装不下时

func growslice(et *_type, old slice, cap int) slice {    ...    // 如果新要扩容的容量比原来的容量还要小,直接报panic    if cap < old.cap {        panic(errorString("growslice: cap out of range"))    }    // 如果当前切片的大小为0,还调用了扩容方法,那么就新生成一个新的容量的切片返回    // []struct{}    if et.size == 0 {        return slice{unsafe.Pointer(&zerobase), old.len, cap}    }    newcap := old.cap    doublecap := newcap + newcap    //要扩容的容量大于2 *oldcap 新切片容量 = 该容量    if cap > doublecap {        newcap = cap    } else {    // 旧容量 小于1024,新容量= 旧容量 * 2 也就是扩容1倍        if old.cap < 1024 {            newcap = doublecap        } else {            // 扩容容量 = 旧容量 +旧容量*1/4            for 0 < newcap && newcap < cap {                newcap += newcap / 4            }            //溢出之后 新容量=要扩容的容量            if newcap <= 0 {                newcap = cap            }        }    }    var overflow bool    // 计算新的切片的容量,长度。    var lenmem, newlenmem, capmem uintptr    ....    var p unsafe.Pointer    if et.ptrdata == 0 {        p = mallocgc(capmem, nil, false)        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)    } else {        p = mallocgc(capmem, et, true)        if lenmem > 0 && writeBarrier.enabled {            bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)        }    }    //移动到p    memmove(p, old.array, lenmem)    //返回slice结构,让slice.array指向p    return slice{p, old.len, newcap}}
  • 新申请容量cap,如果大于2倍旧容量(oldcap),要扩容的容量(newcap)=新申请容量cap

  • 如果旧容量(oldcap)< 1024, 要扩容的容量(newcap)在旧容量(oldcap)基础上扩容1倍,否则则扩容 1/4

  • 如果数值溢出,要扩容的容量 = 新申请的容量

  arr := make([]int,1024)  arr = append(arr,1)  fmt.Println(len(arr),cap(arr))// 1025,1280  arr1 := make([]int,10)  arr1 = append(arr1,1)  fmt.Println(len(arr1),cap(arr1))//11 20
  • 注意事项: 切片共享底层数组,所以在切片赋值的时候,修改切片会导致底层数组改变,而产生BUG

arr := []int{1,2,3,4}arr1 := arr[:2] //[1,2]arr1 = append(arr1,5)fmt.Println(arr[3]) //5 修改了底层数组//例子2arr3 := []int{1,2,3,4}arr4 := arr3[2:]arr4 = append(arr4,10)//扩容 不会影响arr3fmt.Println(arr3)

切片复制

src/runtime/slice.go#L247

//toPtr 目标地址 toLen目标长度// width 元素大小func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {    //判断长度    if fromLen == 0 || toLen == 0 {        return 0    }    n := fromLen    if toLen < n {        n = toLen    }    //切片大小等于0    if width == 0 {        return n    }    size := uintptr(n) * width    //特殊处理 如果只有一个元素并且大小是1byte,那么指针直接转换即可    if size == 1 {        *(*byte)(toPtr) = *(*byte)(fromPtr)    } else {        //从 fm.array 地址开始,拷贝到 to.array 地址之后        memmove(toPtr, fromPtr, size)    }    return n}

关于“Golang切片Slice底层源码简介”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

您可能感兴趣的文档:

--结束END--

本文标题: Golang切片Slice底层源码简介

本文链接: https://www.lsjlt.com/news/248048.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作