golang工程——常用数据结构底层原理【mao、slice、func、string】

字符串

其实就是字符数组

注意

字节数组与字符串可以相互转换

a := "hello world"
b := []byte(a)
c := string(b)

字节数组转换为字符串在运行时调用了slicebytetostring函数。需要注意的是,字节数组与字符串的相互转换并不是简单的指针引用,而是涉及了复制。当字符串大于32字节时,还需要申请堆内存,因此在涉及一些密集的转换场景时,需要评估这种转换带来的性能损耗

当字符串转换为字节数组时,在运行时需要调用stringtoslicebyte函数,其和slicebytetostring函数非常类似,需要新的足够大小的内存空间。当字符串小于32字节时,可以直接使用缓存buf。当字符串大于32字节时,rawbyteslice函数需要向堆区申请足够的内存空间。最后使用copy函数完成内存复制。

切片

概要

有data、len 、cap 三个元素。 分别指向数据,长度,容量,底层是一个数组

切片是一种简化版的动态数组。切片的在go中的定义为如下,在对切片赋值,就是修改指向数组的指针,len,cap的值。而在拷贝的时候,如果直接使用=,则会复制被拷贝的切片的数组指针,cap,len值,因此会指向同一个地址,而使用copy的话会把被拷贝的切片中的数组的值复制到拷贝的切片的数组中。即地址是不同的

切片在被截取时的另一个特点是,被截取后的数组仍然指向原始切片的底层数据。 要真正复制切片,需要用copy

slice:= make(int[], 4, 6)

在这里插入图片描述
Go语言中,切片的复制其实也是值复制,但这里的值复制指对于运行时SliceHeader结构的复制。如图底层指针仍然指向相同的底层数据的数组地址,因此可以理解为数据进行了引用传递。切片的这一特性使得即便切片中有大量数据,在复制时的成本也比较小,这与数组有显著的不同

切片扩容

cap增长的策略:

  1. 如果新申请容量(cap)大于2倍的旧容量(old.cap),则最终容量(newcap)是新申请的容量(cap)。
  2. 如果当前大小小于1024,则两倍增长;
  3. 否则每次增长25%,直到满足期望。
  4. 如果新申请容量(cap)大于2倍的旧容量(old.cap),则最终容量(newcap)是新申请的容量(cap)。
// slice 扩容伪代码:
{newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.len < 1024 {newcap = doublecap} else {for newcap < cap {newcap += newcap / 4}}}

map

源码
// Map contains Type fields specific to maps.
type Map struct {Key  *Type // Key typeElem *Type // Val (elem) typeBucket *Type // internal struct type representing a hash bucketHmap   *Type // internal struct type representing the Hmap (map header object)Hiter  *Type // internal struct type representing hash iterator state
}// A header for a Go map.
type hmap struct {// 元素个数,调用 len(map) 时,直接返回此值count     intflags     uint8 // flags代表当前map的状态(是否处于正在写入的状态等B         uint8					// buckets 的对数 log_2// overflow 的 bucket 近似数 noverflow为map中溢出桶的数量。当溢出的桶太多时,map会进行same-size map growth,其实质是避免溢出桶过大导致内存泄露noverflow uint16// 计算 key 的哈希的时候会传入哈希函数hash0     uint32buckets    unsafe.Pointer	// 指向内存的指针,可以看作是:[]bmap。   其大小为 2^B. 如果元素个数为0,就为 nil// 扩容的时候,buckets 长度会是 oldbuckets 的两倍oldbuckets unsafe.Pointer// 指示扩容进度,小于此地址的 buckets 迁移完成nevacuate  uintptrextra *mapextra // optional fields
}// buckets指向的结构体
type bmap struct {tophash [bucketCnt]uint8				// bucketCnt值固定为8个,也就是每个bmap最大能存储8个key-value对。
}// go编译器在编译时,会扩展bmap为如下的结构:
type bmap struct {topbits  [8]uint8keys     [8]keytypevalues   [8]valuetypepad      uintptroverflow uintptr
}type mapextra struct {// If both key and elem do not contain pointers and are inline, then we mark bucket// type as containing no pointers. This avoids scanning such maps.// However, bmap.overflow is a pointer. In order to keep overflow buckets// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.// overflow and oldoverflow are only used if key and elem do not contain pointers.// overflow contains overflow buckets for hmap.buckets.// oldoverflow contains overflow buckets for hmap.oldbuckets.// The indirection allows to store a pointer to the slice in hiter.overflow    *[]*bmapoldoverflow *[]*bmap// nextOverflow holds a pointer to a free overflow bucket.nextOverflow *bmap
}
/*
当一个 map 的 key 和 elem 都不含指针并且他们的长度都没有超过 128 时(当 key 或 value 的长度超过 128 时, go 在 map 中会使用指针存储), 该 map 的 bucket 类型会被标注为不含有指针, 这样 gc 不会扫描该 map, 这会导致一个问题, bucket 的底层结构 bmap 中含有一个指向溢出桶的指针(uintptr类型, uintptr指针指向的内存不保证不会被 gc free 掉), 当 gc 不扫描该结构时, 该指针指向的内存会被 gc free 掉, 因此在 hmap 结构中增加了 mapextra 字段, 其中 overflow 是一个指向保存了所有 hmap.buckets 的溢出桶地址的 slice 的指针, 相对应的 oldoverflow 是指向保存了所有 hmap.oldbuckets 的溢出桶地址的 slice 的指针, 只有当 map 的 key 和 elem 都不含指针时这两个字段才有效, 因为这两个字段设置的目的就是避免当 map 被 gc 跳过扫描带来的引用内存被 free 的问题, 当 map 的 key 和 elem 含有指针时, gc 会扫描 map, 从而也会获知 bmap 中指针指向的内存是被引用的, 因此不会释放对应的内存。
*/

在这里插入图片描述

溢出桶

在这里插入图片描述

Go语言选择将key与value分开存储而不是以key/value/key/value的形式存储,是为了在字节对齐时压缩空间

hmap 结构相当于 go map 的头, 它存储了哈希桶的内存地址, 哈希桶之间在内存中紧密连续存储, 彼此之间没有额外的 gap, 每个哈希桶最多存放 8 个 k/v 对, 冲突次数超过 8 时会存放到溢出桶中, 哈希桶可以跟随多个溢出桶, 呈现一种链式结构, 当 HashTable 的装载因子超过阈值(6.5) 后会触发哈希的扩容

冲突检测

Go语言中的哈希表采用的是开放寻址法中的线性探测(Linear Probing)策略,线性探测策略是顺序(每次探测间隔为1)的

插入过程

例如:m1 map[string]string插入一条数据的过程如下:

insert “key1 name”:“乔布斯”hashvalue = hash(“key1 name”)slot = hashvalue的低8bit % len(m1),例如m1的槽位是4个,则slot = hashvalue % 4。假设slot = 2
hashvalue的高8bit这条数据应该插入到bmap中的第几个子槽。如果bmap已经写满8个,则读取overflow指向的下一个紧邻着的bmap(溢出桶)去插入这条数据
删除过程

其核心代码位于runtime.mapdelete函数中,删除操作同样需要根据key计算出hash的前8位和指定的桶,同样会一直寻找是否有相同的key,如果找不到,则会一直查找当前桶的溢出桶,直到到达溢出桶链表末尾。如果查找到了指定的key,则会清空该数据,将hash位设置为emptyOne。如果发现后面没有元素,则会将hash位设置为emptyRest,并循环向上检查前一个元素是否为空

扩容

当插入的元素越来越多,导致哈希桶慢慢填满,导致溢出桶越来越多,所以发生哈希碰撞的频率越来越高,就需要进行扩容,

若装载因子过大, 说明此时 map 中元素数目过多, 此时 go map 的扩容策略为将 hmap 中的 B 增一, 即将整个哈希桶数目扩充为原来的两倍大小, 而当因为溢出桶数目过多导致扩容时, 因此时装载因子并没有超过 6.5, 这意味着 map 中的元素数目并不是很多, 因此这时的扩容策略是等量扩容, 即新建完全等量的哈希桶, 然后将原哈希桶的所有元素搬迁到新的哈希桶中。

需要注意的几点

3.1 Go map遍历为什么是无序的
使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同。这是 Go 语言的设计者们有意为之,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,不要依赖 range 遍历结果顺序。
主要原因有2点:

  • map在遍历时,并不是从固定的0号bucket开始遍历的,每次遍历,都会从一个随机值序号的bucket,再从其中随机的cell开始遍历
  • map遍历时,是按序遍历bucket,同时按需遍历bucket中和其overflow bucket中的cell。但是map在扩容后,会发生key的搬迁,这造成原来落在一个bucket中的key,搬迁后,有可能会落到其他bucket中了,从这个角度看,遍历map的结果就不可能是按照原来的顺序了。

因此如果不加入随机数,在不发生扩容情况下,一些不熟悉该原理的开发者会认为map是有序的,一旦依赖这个特性,就会引发bug。所以golang直接通过加随机数(在初始化迭代器时会生成一个随机数,决定从哪一个bucket开始迭代)避免问题的发生。这就是map为什么每次遍历顺序是不一样的原因。

3.2 如何让map有序
把key取出来进行排序,再通过key依次从map中取值。
3.3 map并发读写会产生什么情况
map在默认情况下时不支持并发的,这是由于golang的设计者考虑到使用map的场景都不是并发访问,如果map并发读写会产生什么呢?如果并发时写入,则会产生panic。runtime.map 代码判断:

//赋值时检查是否在写入
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {if h.flags&hashWriting != 0 {throw("concurrent map writes")}
}
//读取数据时检查是否在写入
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {if h.flags&hashWriting != 0 {throw("concurrent map read and map write")}}

3.4 如何安全使用map
Go map不是线程安全的,在使用过程中如果需要保证线程安全,则需要保持同步。

  • 使用sync.Mutex或sync.RWMutex进行加锁
  • 使用go官方提供的sync.Map替代map

3.5 map中的key可以取地址吗?
不可以,因为key对应的value的内存地址可能因为扩容而变化,所以不允许取地址。也正因为如此,下面代码是错误的。

type Student struct {name string
} 
func main() { m := map[string]Student{"people": {"zhoujielun"}} m["people"].name = "wuyanzu"
}

函数

闭包

一个函数捕获了和他在同一个作用域的其他常量和变量.这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量.

它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用他,这些变量就还会存在,

在go里面,所有的匿名函数都是闭包

闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上

例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。

package mainimport "fmt"func adder() func(int) int {sum := 0return func(x int) int {sum += xreturn sum}
}func main() {pos, neg := adder(), adder()for i := 0; i < 10; i++ {fmt.Println(pos(i),neg(-2*i),)}
}

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

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

相关文章

【实战详解】如何快速搭建接口自动化测试框架?Python + Requests

摘要&#xff1a; 本文主要介绍如何使用Python语言和Requests库进行接口自动化测试&#xff0c;并提供详细的代码示例和操作步骤。希望能对读者有所启发和帮助。 前言 随着移动互联网的快速发展&#xff0c;越来越多的应用程序采用Web API&#xff08;也称为RESTful API&…

分类预测 | Matlab实现GA-RF遗传算法优化随机森林多输入分类预测

分类预测 | Matlab实现GA-RF遗传算法优化随机森林多输入分类预测 目录 分类预测 | Matlab实现GA-RF遗传算法优化随机森林多输入分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现GA-RF遗传算法优化随机森林多输入分类预测&#xff08;完整源码和数据&…

如何进一步全面提高项目估算精准度?

项目估算非常重要&#xff0c;这直接关系着项目的成本和收入&#xff0c;如果估算不准确&#xff0c;将为项目带来较大风险。一般软件规模可以用多种方式进行估算&#xff0c;但是用功能点估算方式更准确&#xff0c;而自动估算让估算更快速&#xff0c;我们以CoCode开发的估算…

【Go】rsrc不是内部或外部命令、无法将“rsrc”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 解决方法

前言 想尝试用go创建一个桌面应用程序&#xff0c;然后查了下决定用 walk。 我们要先下载walk&#xff0c;这里 官方链接 按照官方文档&#xff0c;我们先用go get命令下载。 go get github.com/lxn/walk然后分别创建好了 main.go、main.manifest 文件&#xff0c;代码如下…

libtorch之tensor的使用

1. tensor的创建 tensor的创建有三种常用的形式&#xff0c;如下所示 ones创建一个指定维度&#xff0c;数据全为1的tensor. 例子中的维度是2维&#xff0c;5行3列。 torch::Tensor t torch::ones({5,3}); zeros创建一个指定维度&#xff0c;数据全为0的tensor&#xff0c;例子…

Java基于SpringBoot的民宿管理系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 开发环境&#xff1a;后端&#xff1a;前端&#xff1a;数据库&#xff1a; 系统架构&#xff1a…

nginx配置密码访问

安装htpasswd 因为需要使用到htpasswd&#xff0c;htpasswd是Apache服务器中生成用户认证的一个工具&#xff0c;如果未安装&#xff0c;则使用如下命令安装htpasswd。 yum install -y httpd-tools设置用户名和密码 htpasswd 安装成功后&#xff0c;就可以设置用户名和密码&am…

Java项目-Spring Boot的生鲜网上交易系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 系统功能4 功能设计5系统详细设计5.1系统功能模块5.2后台功能模块5\.2\.1用户功…

vscode左键无法跳转到定义的文件

之前用vscode的时候&#xff0c;明明是可以ctrl键鼠标左键跳转到定义文件的&#xff0c;突然之间就不行了&#xff0c;鼠标移到引入上根本都没有下划线&#xff0c;无法跳转 解决方法&#xff1a; 项目的根目录新建 jsconfig.json 文件&#xff0c;代码如下 {"compiler…

http基础教程(超详细)

HTTP HTTP 一 、基础概念 请求和响应报文URL 二、HTTP 方法 GETHEADPOSTPUTPATCHDELETEOPTIONSCONNECTTRACE 三、HTTP 状态码 1XX 信息2XX 成功3XX 重定向4XX 客户端错误5XX 服务器错误 四、HTTP 首部 通用首部字段请求首部字段响应首部字段实体首部字段 五、具体应用 连接管理…

敏捷发布列车初探2 ---- Agile Release Train

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 敏捷发布列车二、ART的特性2.敏捷团队为列车提供动力3.与共同节奏保持一致4.关键角色启用 三、ART的责任总结 敏捷发布列车 敏捷发布列车&#xff08;ART&#xff…

CentOS 7.5 centos failed to load selinux policy 错误解决方法

这是个 selinux 使能导致的&#xff0c; 关闭即可 在进入到内核选中界面&#xff0c;选中要启动的内核&#xff0c; 按键盘 e 就会进入启动参数界面 进入启动参数界面如图&#xff0c;按上下键找到 UTF8 UTF8如图&#xff0c; 添加 selinux0 添加完成如图&#xff0c; 按 ctr…

MQTT协议知识梳理,看完你就懂了

目录 一、MQTT简介 二、MQTT框架图 三、MQTT特点 四、MQTT协议原理 1.MQTT协议实现框图 3.网络传输与应用消息 4.MQTT客户端 5.MQTT服务器 6.MQTT协议中的订阅、主题、会话 五、MQTT优缺点 优点 缺点 一、MQTT简介 MQTT是基于TCP/IP协议栈构建的异步通信消息协议&a…

比特币 ZK 赏金系列:第 2 部分——查找哈希冲突

在我们的零知识赏金 (ZKB) 系列的第二部分中&#xff0c;我们将其应用于解决哈希冲突难题。在这样的谜题中&#xff0c;两个不同的输入散列到相同的输出。此类赏金可用于&#xff1a; 充当煤矿中的金丝雀&#xff0c;给我们一个有价值的提醒。存在冲突是散列函数较弱的标志&…

linux内网渗透

一、信息收集 主机发现&#xff1a; nmap -sP 192.168.16.0/24 端口探测 masscan -p 1-65535 192.168.16.168 --rate1000 开放端口如下 nmap端口详细信息获取 nmap -sC -p 8888,3306,888,21,80 -A 192.168.16.168 -oA ddd4-port目录扫描 gobuster dir…

19.组合模式(Composite)

意图&#xff1a;将对象组成树状结构以表示“部分&#xff0d;整体”的层次结构&#xff0c;使得Client对单个对象和组合对象的使用具有一致性。 上下文&#xff1a;在树型结构的问题中&#xff0c;Client必须以不同的方式处理单个对象和组合对象。能否提供一种封装&#xff0c…

如何使用ArcGIS Pro制作标准地图样式国界

相信大家都浏览过标准地图服务提供的标准地图&#xff0c;不知道你有没有想过尝试制作里面的国界&#xff0c;这里为大家介绍一下制作方法&#xff0c;希望能对你有所帮助。 制作已定国界 在地图数据内&#xff0c;国界分为已定国界、未定国界和海岸线&#xff0c;我们先对已定…

B2901A 是德科技keysight精密型电源

181/2461/8938Agilent B2901A精密源/测量单元(SMU)是一款单通道、紧凑且经济高效的台式SMU&#xff0c;能够采集和测量电压和电流。它功能多样&#xff0c;可以轻松、高精度地执行I/V(电流与电压)测量。四象限源和测量功能的集成使I/V测量简单易行&#xff0c;无需配置多种仪器…

如何看待著名游戏引擎 Unity 宣布将更改收费模式,收取「运行时费用」?这将造成哪些影响?

先下结论&#xff1a;Unity 的高管是不是【不友善内容&#xff0c;请于 24 小时内及时更改】&#xff1f; 简单介绍下这个收费模式&#xff1a;年收入大于 20w 美金且安装量大于 20w 的&#xff0c;每一份额外下载需要给 Unity 交 0.2 刀。 首先&#xff1a;听上去好像不会影响…

使用Process Monitor工具探测日志文件是程序哪个模块生成的

目录 1、问题描述 2、使用Process Monitor监测目标文件是哪个模块生成的思路说明 3、操作Process Monitor监测日志文件是哪个模块生成的 4、通过screenctach.dll库的时间戳&#xff0c;找到其pdb文件&#xff0c;然后去查看详细的函数调用堆栈 5、最后 VC常用功能开发汇总…