go数据结构之slice与map

1. 切片

1. 切片结构定义

type slice struct {array unsafe.Pointerlen   intcap   int
}
  • array:引用的底层数组,动态数组,可以修改
    • 如果多个切片的array指针指向同一个动态数组,则它们都可以对底层这个动态数组元素进行修改。
  • len::长度
  • cap:可以理解为底层动态数组的容量
    • 当切片添加的元素超过cap后,会引发切片底层数组扩容。扩容后array指针指向可能会发生更改

2. 切片有长度

package mainimport "fmt"func main() {slice1 := make([]int, 0, 4)fmt.Println(len(slice1))// panicslice1[0] = 1
}
  • 使用下标访问切片,访问范围不能超过切片len

2. 底层动态数组可能会被多个切片引用

package mainimport "fmt"func main() {slice1 := make([]int, 4, 8)// 4   8fmt.Println(len(slice1), " ", cap(slice1))slice2 := slice1[2:]// 2   6fmt.Println(len(slice2), " ", cap(slice2))
}
  • 上述例子中两个切片引用的是同一个底层动态数组,slice1是从动态数组头部开始引用,而slice2则是从动态数组第二个元素开始引用
  • 所以slice1能对底层动态数组全部八个位置都可以修改,而slice2则只能修改底层数组的后六位下标的位置
  • 当底层动态数组只要有一个切片引用,则整个动态数组就不会被回收,即使这个切片引用的是动态数组的局部
    • 如slice2引用会导致整个动态数组八位不能回收,虽然它只能访问和修改后六位

3. 函数中,切片是值传递

package mainimport "fmt"func main() {s1 := make([]int, 4, 8)fmt.Printf("s1的指针是 %p \n", &s1)printPoint(s1)fmt.Printf("s1的底层数组是 %p \n", s1)printArrPoint(s1)
}
func printPoint(s2 []int) {fmt.Printf("s2的指针是 %p \n", &s2)
}func printArrPoint(s2 []int) {fmt.Printf("s2的底层数组是 %p \n", s2)
}
//s1的指针是 0xc000008078
//s2的指针是 0xc000008090 
//s1的底层数组是 0xc000014240 
//s2的底层数组是 0xc000014240 
  • 函数传递切片的时候,其实是把切片复制了一遍
  • 但是两个切片指向了同一个底层动态数组

4. 当切片扩容时,(可能)会指向新的底层数组

package mainimport "fmt"func main() {s1 := make([]int, 4, 4)s2 := s1fmt.Printf("s1的底层数组是 [%p], 容量是 [%v]\n", s1, cap(s1))fmt.Printf("s2的底层数组是 [%p], 容量是 [%v]\n", s2, cap(s2))s1 = append(s1, 1)fmt.Printf("s1的底层数组是 [%p], 容量是 [%v]\n", s1, cap(s1))fmt.Printf("s2的底层数组是 [%p], 容量是 [%v]\n", s2, cap(s2))
}
//s1的底层数组是 [0xc000150020], 容量是 [4]
//s2的底层数组是 [0xc000150020], 容量是 [4]
//s1的底层数组是 [0xc0001200c0], 容量是 [8]
//s2的底层数组是 [0xc000150020], 容量是 [4]
  • 当指向同一个底层数组时,s1对数组元素的修改对s2是可见的,当s1指向新的底层数组时,s1则对数组元素的修改则对s2是不可见了,因为它俩指向了不同的底层动态数组

5. 与空比较

package mainimport "fmt"func main() {var a []intb := make([]int, 0)fmt.Printf("a==nil? %v \n", a == nil)fmt.Printf("b==nil? %v \n", b == nil)fmt.Printf("len(a)==0? %v \n", len(a))fmt.Printf("len(b)==0? %v \n", len(b))fmt.Printf("a的指针是 [%p]\n", &a)fmt.Printf("b的指针是 [%p]\n", &b)fmt.Printf("a的底层数组是 [%p]\n", a)fmt.Printf("b的底层数组是 [%p]\n", b)
}//a==nil? true
//b==nil? false 
//len(a)==0? 0 
//len(b)==0? 0 
//a的指针是 [0xc000008078]
//b的指针是 [0xc000008090]
//a的底层数组是 [0x0]
//b的底层数组是 [0xe6b438]
  • 切片只声明未初始化则不会分配底层数组
  • 切片可以和 nil 进行比较,只有当切片底层数据指针为空时切片本身为 nil,这时候切片的长度和容量信息将是无效的。如果有切片的底层数据指针为空,但是长度和容量不为 0 的情况,那么说明切片本身已经被损坏了。

2. map

1. map底层数据结构

type hmap struct {count     int              // map 中键值对的数量flags     uint8            // map 的标志位,如是否为引用类型等B         uint8            // map 的桶大小的对数noverflow uint16           // 溢出桶的数量hash0     uint32           // 哈希种子值buckets   unsafe.Pointer   // 存储桶的指针oldbuckets unsafe.Pointer   // 旧桶的指针,用于扩容时的过渡nevacuate uintptr          // 扩容时,已迁移的桶的数量extra     *mapextra        // 用于存储特殊情况下的扩展信息
}
type mapextra struct {overflow    *[]*bmap        // 溢出桶的数组,当哈希表中的键值对数量超过某个阈值时会使用溢出桶oldoverflow *[]*bmap        // 旧溢出桶的数组,用于扩容时的过渡nextOverflow *bmap          // 链接下一个溢出桶的指针
}
type bmap struct {tophash [bucketCnt]uint8
}//在编译期间会产生新的结构体
type bmap struct {tophash [8]uint8 //存储哈希值的高8位keys    [8]keytype  //key数组values  [8]valuetype // value数组pad     uintptroverflow *bmap   //溢出bucket的地址
}

在这里插入图片描述

  1. map的底层结构时 hmap
  2. 桶数组中每个桶可以存储8个元素,超过了则使用overflow链接到下一个桶(溢出桶),所以使用的是链接法

2. get操作

在这里插入图片描述

  1. 首先通过hash函数计算key的哈希值
  2. 通过后B位来定位到哪个桶
  3. 定位到桶之后,首先通过hash值高8位去便利tophash数组(起到一种加速的作用),找到对应的下标i。
  4. 然后比较keys[i]==key,如果为true,则返回values[i]
  5. 如果没有找到,则继续便利tophash数组,找到下一个符合的下标i,重复3,4两步
  6. 如果都没有找到,则去溢出桶里寻找

3. tophash数组的作用

  • 主要起到加速的作用
  • tophash数组是值数组,每个元素物理位置连续
  • 而key数组不一定是值数组,如果key是字符串,则key数组里的元素其实存的不是字符串内容本身,而是一个引用。我们需要再根据这个引用(指针)找到字符串去比较。
  • 所以如果只根据key数组去比较的话,则这个过程中访问的内存地址其实不是连续的,速度会慢很多。

4. put操作

在这里插入图片描述

  • 先根据key进行查找(过程跟get差不多),如果找到了,则替换value
  • 如果没找到,定位到对应的桶,找到一个空闲的位置i,进行插入(三个数组对应下标i处都要插入)
  • 如果对应桶没有空闲位置,则通过overflow插入到溢出桶中
  • 必要时可能会引发扩容

5. 等量扩容

在这里插入图片描述

  • 当不停地在map中put和delete操作,导致一个桶的溢出桶链很长,但是每个桶里面key存的断断续续也不是很多的时候,会出发等量扩容。
  • 所谓等量扩容本不是真的扩容了,而是碎片整理,将key都整理到一起去
  • 这种情况下元素会发生重排,但不会产生新的桶(正常桶和溢出桶)

6. 二倍扩容

在这里插入图片描述

  • 正常桶的桶数组大小会翻倍
  • 存储的元素会重排,元素所在的桶数组下标可能会改变
  • 比如扩容前B=2,扩容后B=3,之前根据key的hash后两位定位,现在看hash的后三位定位。所以对于一个key它新的同下标要么还是原来的 i,要么就是i+len(原来桶数组)

7. 扩容条件

  • 当装载因子大于6.5时扩容(ladFactor=count/(2^B)
  • 当溢出桶过多时会扩容
    • B<15时,溢出桶数量超过2^B
    • B>=15时,溢出同数量超过2^15

8. 在函数中,也是值传递

  • 跟切片一样,在函数中也是值传递
  • 但是由于底层共享存储结构,所以函数中对map内容修改,出了函数仍是可见

9. map只能与nil比较

package mainimport "fmt"func main() {a := make(map[string]int, 0)// falsefmt.Println(a == nil)var b map[string]int// truefmt.Println(b == nil)
}
  • 且只有当map底层数据结构未初始化的时候map==nil才为true

参考:https://juejin.cn/post/7029679896183963678#heading-1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/14002.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简要介绍 | 两阶段点云目标检测:理论与实践

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对两阶段点云目标检测进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 两阶段点云目标检测&#xff1a;理论与实践 在这篇博客中&#xff0c;我们将探索两阶段点云目标检测的理论基础和实际应用…

mac与pd虚拟机之间不能粘贴文字或粘贴文件

首先确保共享打开&#xff1a; 然后检查虚拟机的Parallels Tools是否正常 一个简单的判断方式就是&#xff0c;退出虚拟机全屏之后&#xff0c;如果能够正常进入融合模式&#xff0c;那么Parallels Tools可用&#xff0c;否则就要排查问题 检查Parallels Tools是否随系统正常启…

基于微信小程序学校部门年终绩效考核自动评分系统(源码+文档+数据库+PPT)

基于微信小程序的部门年终绩效考核系统&#xff0c;为加强学校运营队伍建设提高学校管理力&#xff0c;合理评价教师及部门年度工作计划完成情况&#xff0c;促进整体绩效改进&#xff0c;鼓励管理团队注重对下属进行帮助、提升&#xff0c;促进团队扩张和发展&#xff0c;特制…

MFC学习日记(二)——VS2012应用程序工程中文件的组成结构

上一篇我们用应用程序向导生成框架程序后&#xff0c;我们可以打开工程所在的文件夹看到以下以解决方案命名的文件夹&#xff0c;此文件夹中包含了几个文件和一个以工程名命名的子文件夹&#xff0c;这个子文件夹中又包含了若干个文件和一个res文件夹&#xff0c;创建工程时的选…

Linux性能优化实践——平均负载

平均负载&#xff08;Load Average&#xff09; 当我们输入uptime命令时&#xff0c; 这里有几个参数&#xff0c;解释如下 0:54 &#xff1a;当前时间&#xff1b;up 50 mins&#xff1a;系统运行时间&#xff1b;2 users&#xff1a;正在登录用户数&#xff1b;load avera…

UDP客户端和服务器

UDP客户端&#xff0c;也就是首先主动发送数据的一方&#xff0c;也就是发起服务请求的一方。 UDP服务器&#xff0c;也就是首先等待接收数据&#xff0c;并对接收的数据进行处理&#xff0c;返回计算结果的一方&#xff0c;也就是提供服务的一方。 在下面实验中使用到的函数 …

mysql单表查询,排序,分组查询,运算符

CREATE TABLE emp (empno int(4) NOT NULL, --员工编号ename varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,--员工名字job varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,--员工工作mgr int(4) NULL DEFAULT NU…

【CSS】悬浮动画

文章目录 效果展示代码实现 效果展示 代码实现 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>一颗不甘坠落的流星</title></head><style type"text/css">.bth {/* 添加背景颜色 */backgr…

【周末闲谈】关于计算机的二三事

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 文章目录 前言一、第一台计算机的诞生&#x1f4bb;二、计算机发展历史&#x1f4da;第一代计算机&#x1f4bb;第二代计算机&#x1f4bb;第三代计算机&#x1f4bb;第四代计算机&#x…

chatgpt 与传统3D建模对比分析

推荐&#xff1a;将NSDT场景编辑器加入你的3D工具链 随着人工智能技术的发展&#xff0c;越来越多的领域正逐渐被AI模型所取代。ChatGPT作为一种自然语言处理技术&#xff0c;越来越为人们所熟悉。最近&#xff0c;一些3D建模领域的专家想知道ChatGPT是否可以取代传统的手动3D建…

单机模型并行最佳实践

单机模型并行最佳实践 模型并行在分布式训练技术中被广泛使用。 先前的帖子已经解释了如何使用DataParallel在多个 GPU 上训练神经网络&#xff1b; 此功能将相同的模型复制到所有 GPU&#xff0c;其中每个 GPU 消耗输入数据的不同分区。 尽管它可以极大地加快训练过程&#x…

JVM理论(二)类加载子系统

类加载流程 类加载流程 类加载器子系统负责从文件系统或者网络中加载class文件,class文件的文件头有特定的文件标识(CAFEBABE是JVM识别class文件是否合法的依据)classLoader只负责文件的加载,而执行引擎决定它是否被执行加载类的信息存放在运行时数据区的方法区中,方法区还包括…