返回顶部
首页 > 资讯 > 后端开发 > GO >Golang中的Slice底层如何实现
  • 720
分享到

Golang中的Slice底层如何实现

2023-07-05 06:07:18 720人浏览 独家记忆
摘要

这篇“golang中的Slice底层如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Golang中的Slice底层如何

这篇“golang中的Slice底层如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Golang中的Slice底层如何实现”文章吧。

    1 Go数组

    Go数组是值类型,数组定义的时候就需要指定大小,不同大小的数组是不同的类型,数组大小固定之后不可改变。数组的赋值和传参都会复制一份。

    func main() {    arrayA := [2]int{100, 200}    var arrayB [2]int    arrayB = arrayA    fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)    fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)    testArray(arrayA)}func testArray(x [2]int) {    fmt.Printf("func Array : %p , %v\n", &x, x)}

    结果:

    arrayA : 0xc4200bebf0 , [100 200]
    arrayB : 0xc4200bec00 , [100 200]
    func Array : 0xc4200bec30 , [100 200]

    可以看到,三个内存地址都不同,这也就验证了 Go 中数组赋值和函数传参都是值复制的。尤其是传参的时候把数组复制一遍,当数组非常大的时候会非常消耗内存。可以考虑使用指针传递。

    指针传递有个不好的地方,当函数内部改变了数组的内容,则原数组的内容也改变了。

    因此一般参数传递的时候使用slice

    2 切片的数据结构

    切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。

    切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个与指向数组的动态窗口。

    给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个长度可变的数组。

    切片数据结构定义

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

    Golang中的Slice底层如何实现

    切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。

    Golang中的Slice底层如何实现

    如果想从 slice 中得到一块内存地址,可以这样做:

    s := make([]byte, 200)ptr := unsafe.Pointer(&s[0])

    3 创建切片

    3.1 方法一:make

    使用make函数创建slice

    // 创建一个初始大小是3,容量是10的切片s1 := make([]int64,3,10)

    底层方法实现:

    func makeslice(et *_type, len, cap int) slice {    // 根据切片的数据类型,获取切片的最大容量    maxElements := maxSliceCap(et.size)    // 比较切片的长度,长度值域应该在[0,maxElements]之间    if len < 0 || uintptr(len) > maxElements {        panic(errorString("makeslice: len out of range"))    }    // 比较切片的容量,容量值域应该在[len,maxElements]之间    if cap < len || uintptr(cap) > maxElements {        panic(errorString("makeslice: cap out of range"))    }    // 根据切片的容量申请内存    p := mallocGC(et.size*uintptr(cap), et, true)    // 返回申请好内存的切片的首地址    return slice{p, len, cap}}

    3.2 方法二:字面量

    利用数组创建切片

    arr := [10]int64{1,2,3,4,5,6,7,8,9,10}s1 := arr[2:4:6] // 以arr[2:4]创建一个切片,且容量到达arr[6]的位置,即cap=6-2=4,如果不写容量则默认为数组最后一个元素

    4 nil和空切片

    Golang中的Slice底层如何实现

    nil切片的指针指向的是nil

    Golang中的Slice底层如何实现

    空切片指向的是一个空数组

    空切片和 nil 切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。

    最后需要说明的一点是。不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的

    5 切片扩容

    5.1 扩容策略

    func main() {    slice := []int{10, 20, 30, 40}    newSlice := append(slice, 50)    fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))    fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))    newSlice[1] += 10    fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))    fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))}

    输出结果:

    Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
    Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
    After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
    After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8

    Go 中切片扩容的策略是这样的:

    如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的4个翻倍到现在的8个。

    一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。

    5.2 底层数组是不是新地址

    不一定。当发生了扩容就肯定是新数组,没有发生扩容则是旧地址

    不管切片是通过make创建还是字面量创建,底层都是一样的,指向的是一个数组。当使用字面量创建时,切片底层使用的数组就是创建时候的数组。修改切片中的元素或者往切片中添加元素,如果没有扩容,则会影响原数组的内容,切片底层和原数组是同一个数组;当切片扩容了之后,则修改切片的元素或者往切片中添加元素,不会修改数组内容,因为切片扩容之后,底层数组不再是原数组,而是一个新数组。

    所以尽量避免切片底层数组与原始数组相同,尽量使用make创建切片

    range遍历数组或者切片需要注意

    func main() {// slice := []int{10, 20, 30, 40}slice := [4]int{10, 20, 30, 40}for index, value := range slice {fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index])}}

    结果:

    value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
    value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
    value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
    value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378

    从上面结果我们可以看到,如果用 range 的方式去遍历一个数组或者切片,拿到的 Value 其实是切片里面的值拷贝。所以每次打印 Value 的地址都不变。

    Golang中的Slice底层如何实现

    由于 Value 是值拷贝的,并非引用传递,所以直接改 Value 是达不到更改原切片值的目的的,需要通过 &slice[index] 获取真实的地址

    尤其是在for循环中使用协程,一定不能直接把index,value传入协程,而应该通过参数传进去

    错误示例:

    func main() {s := []int{10,20,30}for index, value := range s {go func() {time.Sleep(time.Second)fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value))}()}time.Sleep(time.Second*2)}

    结果:

    index:2,value:30
    index:2,value:30
    index:2,value:30

    正确示例:

    func main() {s := []int{10,20,30}for index, value := range s {go func(i,v int) {time.Sleep(time.Second)fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v))}(index,value)}time.Sleep(time.Second*2)}

    结果:

    index:0,value:10
    index:2,value:30
    index:1,value:20

    以上就是关于“Golang中的Slice底层如何实现”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网GO频道。

    您可能感兴趣的文档:

    --结束END--

    本文标题: Golang中的Slice底层如何实现

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

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

    猜你喜欢
    • Golang中的Slice底层如何实现
      这篇“Golang中的Slice底层如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Golang中的Slice底层如何...
      99+
      2023-07-05
    • 深入了解Golang中的Slice底层实现
      目录1 Go数组2 切片的数据结构3 创建切片3.1 方法一:make3.2 方法二:字面量4 nil和空切片5 切片扩容5.1 扩容策略5.2 底层数组是不是新地址range遍历数...
      99+
      2023-02-26
      Golang Slice实现 Golang Slice Go Slice
    • Golang中map扩容底层如何实现
      这篇文章主要讲解了“Golang中map扩容底层如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中map扩容底层如何实现”吧!map底层结构主要包含两个核心结构体hmap和...
      99+
      2023-07-05
    • golang多态底层实现
      Golang多态底层实现Golang是一种静态类型的编程语言,旨在提高编程的生产力和代码的可靠性。其中一个最受欢迎的特性是它的支持多态性,这使得代码可以更加通用、可重用。但是,很少有人探讨Golang多态底层实现的细节。在本文中,我们将讨论...
      99+
      2023-05-14
    • 深入探讨golang的底层实现
      Golang是一种高效、现代化的编程语言,以其快速、简单和安全的开发模式,在近年来越来越受到开发者的欢迎。Go语言不仅支持多线程,还具有良好的并发开发能力,同时它也是一种非常底层的语言,这使得Go语言的底层实现得到了广泛关注。考虑到语言设计...
      99+
      2023-05-14
    • 源码剖析Golang中map扩容底层的实现
      目录前言map底层结构扩容时机条件1:超过负载条件2:溢出桶太多扩容方式双倍扩容等量扩容扩容函数总结前言 之前的文章详细介绍过Go切片和map的基本使用,以及切片的扩容机制。本文针对...
      99+
      2023-03-06
      Golang map扩容实现 Golang map扩容 Golang map
    • 浅谈Golang Slice切片如何扩容的实现
      目录一、Slice数据结构是什么?二、详细代码1.数据结构2.扩容原则3.如何理解扩容规则一1.当小于1024个元素时2.当大于1024个元素时4.如何理解扩容规则二1.简单理解内存...
      99+
      2024-04-02
    • 详解Golang中NewTimer计时器的底层实现原理
      目录1.简介2.基本使用3.实现原理3.1 内容分析3.2 基本思路3.3 实现步骤3.4 NewTimer的实现4.总结1.简介 本文将介绍 Go 语言中的NewTimer,首先展...
      99+
      2023-05-18
      Golang NewTimer计时器原理 Golang NewTimer计时器 Golang 计时器
    • 教你Java中的Lock锁底层AQS到底是如何实现的
      目录前言加锁释放锁总结前言 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题。那你是不是很好奇...
      99+
      2024-04-02
    • golang map底层实现原理是什么
      Golang中的map是基于散列表(hash table)实现的。散列表是一种用于存储键值对的数据结构,它通过将键映射到数组的索引来...
      99+
      2023-10-21
      golang
    • Golang函数底层实现原理探讨
      Golang函数底层实现原理探讨Golang语言中的函数是非常重要的一个特性,但是很少有人关注其底层实现原理。本文将深入探讨Golang函数的底层实现原理,希望读者能够更好地理解和优化自己的代码。Golang函数的定义在Golang中,函数...
      99+
      2023-05-17
      函数 Golang 底层实现
    • 深入了解Golang interface{}的底层原理实现
      目录前言interface数据结构ifaceeface总结前言 在 Go 语言没有泛型之前,接口可以作为一种替代实现,也就是万物皆为的 interface。那到底 interface...
      99+
      2024-04-02
    • 深入解析Golang锁的底层实现机制
      Golang锁的底层实现原理详解,需要具体代码示例概述:并发编程是现代软件开发中非常重要的一部分,而锁是实现并发控制的一种机制。在Golang中,锁的概念被广泛应用于并发编程中。本篇文章将深入探讨Golang锁的底层实现原理,并提供具体的代...
      99+
      2023-12-28
      Golang 底层实现
    • 详解如何实现SpringBoot的底层注解
      目录一、@Configuration注解二、@Import注解导入组件三、@Conditional注解条件装配四、@ImportResource注解导入Spring配置文件五、@Co...
      99+
      2024-04-02
    • 如何实现PHP底层的异步编程
      如何实现PHP底层的异步编程,需要具体代码示例在传统的编程模型中,PHP是一种基于线程的同步编程语言,即每个请求都会在服务端被依次处理,直到一个请求的处理完成后才会继续处理下一个请求。然而,随着互联网应用的日益复杂和访问量的增加,这种同步模...
      99+
      2023-11-09
      PHP异步编程 PHP底层编程 实现PHP异步
    • 如何实现PHP底层的并发处理
      如何实现PHP底层的并发处理,需要具体代码示例在Web开发过程中,往往需要处理大量的并发请求,如果不采用并发处理的方式,会造成响应时间过长、服务器压力过大等问题。PHP是一种面向Web开发的语言,自带的多线程支持比较弱,但是可以通过其他方式...
      99+
      2023-11-09
      PHP并发编程 PHP异步编程 多线程 PHP
    • 如何实现PHP底层的负载均衡
      如何实现PHP底层的负载均衡负载均衡是指在分布式系统中将工作负载均匀地分配给多个计算资源,以提高系统的并发处理能力和可靠性。在PHP应用程序中,实现负载均衡可以有效地提高系统的性能和可扩展性。本文将介绍如何利用PHP底层技术实现负载均衡,并...
      99+
      2023-11-08
      底层 负载均衡 编程关键词:PHP
    • redis底层数据结构如何实现的
      Redis 底层数据结构的实现 redis 是一种内存中的数据结构存储,它使用高效的数据结构来实现各种数据类型。这些底层数据结构包括: 1. 哈希表(Hash Table) 哈希表用于存...
      99+
      2024-06-12
      redis 键值对
    • css如何实现弹出层覆盖底层效果
      这篇文章主要介绍“css如何实现弹出层覆盖底层效果”,在日常操作中,相信很多人在css如何实现弹出层覆盖底层效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”css如何实现弹出层覆盖底层效果”的疑惑有所帮助!...
      99+
      2023-07-04
    • golang 中slice和string如何使用
      这期内容当中小编将会给大家带来有关golang 中slice和string如何使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。slice 和 string 内部结构slice 和 string 的内部结...
      99+
      2023-06-20
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作