range和for循环的区别 range在for循环中用法
淘宝搜:【天降红包222】领超级红包,京东搜:【天降红包222】
淘宝互助,淘宝双11微信互助群关注公众号 【淘姐妹】
目录
1. string字符串
1.1. 结构
1.2. string和[]byte转换
2. Slice切片
2.1.原理
2.2.slice避坑指南
2.2.1. 母子切片共享 😄
2.2.2. 切片导致内存泄露 😄
2.2.3. 遍历slice时修改slice
3. Map
3.1.?底层结构?
3.2.各种操作底层实现
3.2.1. 创建map
3.2.2. 查找key
3.2.3. 插入/更新key
3.2.4. 删除key
3.2.5. 迭代遍历
3.3. 扩容策略
3.4. 时间/空间复杂度
3.4.1.?时间复杂度
3.4.2.空间复杂度
4.sync.Map
4.1.介绍
4.2. 对比
4.3. 设计思想
4.3.1.空间换时间
4.3.2.读写分离
4.3.3.双检查机制
4.3.4.延迟删除
4.3.5.read优先
4.3.6.状态机机制
4.4.实现原理
4.4.1.读操作
4.4.2.写操作
4.4.3.删除操作
4.4.4.遍历操作
5. for和range几个“神奇”的问题
5.1. 循环永动机
5.2. 神奇的指针
5.3.?Map遍历的值是随机的
5.4. arr / slice / map能否比较
6. defer
7. 【【微信】】
7.1. 底层实现原理(数据结构)
7.2. 案例分析
7.3. 总结
7.3.1.向【【微信】】发送数据
7.3.2.从【【微信】】接收数据
7.4. 注意事项 😄
7.5. 使用案例
8. 多路Select
8.1.概述
8.2.实现原理
9. 闭包
10. context
10.1. 一个接口
10.2. 四种实现 + 六个函数
10.2.1. emptyCtx
10.2.2. cancelCtx ―?可取消的context
10.2.3. timerCtx ― 超时取消的context
10.2.4. 【【微信】】 ― 支持键值对打包
11. Go面试题
11.1. new和make的区别
11.2. Golang的内存管理 😄
11.3. 调用函数传入结构体时,应该传值还是指针?为什么?
11.4. 【【微信】】什么时候会发生阻塞?阻塞的话调度器会怎么做?
11.4.1. 协程阻塞的场景:协程无法释放的场景
11.4.2. 阻塞的话,调度器会怎么做?
11.4.3. 如果【【微信】】一直占用资源怎么办,GMP模型怎么处理这个问题?😄
11.4.4.?【【微信】】的锁Mutex机制了解过吗?Mutex有哪几种模式?Mutex锁底层如何实现?
11.5. 在GMP模型中【【微信】】有几种状态,线程几种状态
11.6. 若干线程中,有个线程OOM会怎么样?【【微信】】发生OOM呢?怎么排查呢?
11.7. defer可以捕获到子【【微信】】的panic吗?
11.8. 开发用过gin框架么?参数校验怎么做的?中间件middlewares怎么使用的?
11.8.1. 参数校验怎么做的
11.8.2. 中间件middlewares怎么使用的?
11.8.3. gin的route实现原理
11.9. 优雅退出
11.10.?怎么做的链接复用,怎么支持的并发请求的,go的netpoll是怎么实现的?像阻塞read一样去使用底层的非阻塞read
11.11. 父【【微信】】退出,如何使得子【【微信】】也退出
11.12.?热重启
11.13.?服务能开多少个m由什么决定?开多少个P有什么界定
字符串是由字符组成的数组,C 语言中的字符串使用字符数组表示。数组会占用一片连续的内存空间,而内存空间存储的字节共同组成了字符串,Go 语言中的字符串只是一个只读(只读只意味着字符串会分配到只读的内存空间)的字节数组,下图展示了字符串在内存中的存储方式:
? ? ? ? 编译报错:
1.?字符串和中的内容虽然一样,但是字符串的内容是只读的,我们不能通过下标或者其他形式改变其中的数据,而中的内容是可以读写的
2. 不过无论从哪种类型转换到另一种都需要拷贝数据,而内存拷贝的性能损耗会随着字符串和长度的增长而增长
扩容策略:在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
- 刚开始,子切片和母切片共享底层的内存空间
- 修改子切片会反映到母切片上
- 在子切片上执行append会把新元素放到母切片预留的内存空间上;当子切片不断执行append,耗完了母切片预留的内存空间,子切片跟母切片就会发生内存分离,此后两个切片没有任何关系
如上代码,假设母切片占用内存8M,child切片是在【【微信】】基础上构造的占用内存1M,子切片和母切片共享同一块内存,当函数返回后,母切片已经不在使用,本该进行释放,但是由于和子切片公用一块内存未释放母切片,造成了7M的内存泄漏
正确做法应该是给子切片开辟空间,然后for循环将需要的数据从母切片拷贝到子切片上,然后返回子切片
golang 哪些类型可以作为map key?
先说结论:可以用于比较的字段可以作为key
- 可以用于比较的类型:bool、数字、string、point指针、【【微信】】、interface接口、struct、array
- 不能用于比较的类型:slice、map、func
hmap结构
- go map底层实现方式是hash表,数据结构是hmap,包含 「1个桶数组hmap.【【微信】】s + 溢出桶链表」。
- hmap.【【微信】】s 指向桶组成的数组,每个桶元素都是 bmap,bmap存放了 8 个key、 8个 value、1 个溢出桶指针。
- 当需要分配一个溢出桶时,会优先从预留的溢出桶数组里取一个出来链接到链表后面,这时不需要再次申请内存。但当预留的溢出桶被用完了,则需要申请新的溢出桶。
bmap桶结构
?extra字段
?hmap结构体最后还有一个extra字段,指向一个mapextra结构体,里面记录的都是溢出桶相关的信息,其中,
- nexto【【微信】】指向下一个空闲溢出桶
- o【【微信】】指向已经使用的溢出桶
一般情况下,调用makemap()创建hash数组,一次性内存分配,既分配了用户预期大小的hash数组,又追加了预留的溢出桶
- 将key采用哈希函数计算出 hash_code(共 64 个 bit 位)
- 用 hash_code 的低B位,与桶数量相与,定位key所在的桶bmap
- 用 hash_doce 的高8位,与桶bmap的tophash[i]对比,相同则进一步对比key的值,
- 若匹配到,通过tophash就能找到(key,val)
- 若未匹配到,并且溢出桶不为空,还要继续去溢出桶中寻找(直到找到或是所有的 key 槽位都找遍了,包括所有的溢出桶)
- 先执行3.1.3查找key的步骤
- 若查询到,直接更新
- 若未查询到,执行插入过程
- 判断map是否需要扩容
- 如果需要扩容(后面讲解)
- 如果不需要扩容
- 若在bmap上找到了key插入位置,则直接插入
- 若未在bmap上找到key插入位置(说明桶都满了),则需要链接一个新的溢出桶进来
- 判断map是否需要扩容
- delete 操作只置删除标志位(emptyOne)且不能被使用,是标记删除,而不是真正的删除,防止被删除的元素再次插入时出现移动
如何清空整个map?
Q1: 下面代码能清空整个map么?
- map内容被清空,执行完,调用len函数,结果是0
- 内存没释放:清空只是修改了标记,底层内存还是被占用了
Q2: 如何真正的释放内存?
A2: map = nil,之后坐等GC回收就好了
结论:迭代遍历过程是随机的
hash 表中数据每次插入的位置是变化的,这是因为实现的原因
- 一方面 hash 种子是随机的,这导致相同的数据在不同的 map 变量内的 hash 值不同
- 另一方面即使同一个 map 变量内,数据删除再添加的位置也有可能变化,因为在同一个桶及溢出链表中数据的位置不分先后
所以为了防止用户错误的依赖于每次迭代的顺序,map 作者干脆让相同的 map 每次迭代的顺序也是随机的
使用哈希表的目的就是要快速查找到目标 key,然而,随着向 map 中添加的 key 越来越多,key 发生碰撞的概率也越来越大。【【微信】】 中的 8 个 cell 会被逐渐塞满,查找、插入、删除 key 的效率也会越来越低。最理想的情况是一个 【【微信】】 只装一个 key,这样,就能达到 O(1) 的效率,但这样空间消耗太大,用空间换时间的代价太高。
Go 语言采用一个 【【微信】】 里装载 8 个 key,定位到某个 【【微信】】 后,还需要再定位到具体的 key,这实际上又用了时间换空间。当然,这样做,要有一个度,不然所有的 key 都落在了同一个 【【微信】】 里,直接退化成了链表,各种操作的效率直接降为 O(n),是不行的。因此,需要有一个指标来衡量前面描述的情况,这就是装载因子。Go 源码里这样定义 装载因子:loadFactor := count / (2^B)
case1:负载因子>6.5(有效元素很多),就会发生“翻倍扩容”,分配新桶的数目是旧桶的2倍(真扩容,扩到 hash 桶数量为原来的两倍)
解释:每个 【【微信】】 有 8 个空位,在没有溢出,且所有的桶都装满了的情况下,装载因子算出来的结果是 8。因此当装载因子超过 6.5 时,表明很多 【【微信】】 都快要装满了,查找效率和插入效率都变低了。在这个时候进行扩容是有必要的。
“翻倍扩容”:元素太多,而 【【微信】】 数量太少,很简单:将 B 加 1,【【微信】】 最大数量(2^B)直接变成原来 【【微信】】 数量的 2 倍。于是,就有新老 【【微信】】 了。注意,这时候元素都在老 【【微信】】 里,还没迁移到新的 【【微信】】 来。而且,新 【【微信】】 只是最大数量变为原来最大数量(2^B)的 2 倍(2^B * 2)
【【微信】】s指向新桶,old【【微信】】s指向旧桶,ne【【微信】】=0(表示接下来要迁移编号为0的旧桶),每个旧桶的键值对都会分流到两个新桶中。
case2:负载因子<=6.5(有效元素很少),溢出桶较多(当 B 小于 15,也就是 【【微信】】 总数 2^B 小于 2^15 时,如果 o【【微信】】 的 【【微信】】 数量超过 2^B;当 B >= 15,也就是 【【微信】】 总数 2^B 大于等于 2^15,如果 o【【微信】】 的 【【微信】】 数量超过 2^15),会发生“等量扩容”(假扩容,hash 桶数量不变,只是把元素搬迁到新的 map)
解释:删除元素降低元素总数量,再插入很多元素,导致创建很多的溢出桶(溢出桶数量太多,导致 key 会很分散,查找插入效率低得吓人。这就像是一座空城,房子很多,但是住户很少,都分散了,找起人来很困难)
“等量扩容”:其实元素没那么多,但是 o【【微信】】 【【微信】】 数特别多,说明很多 【【微信】】 都没装满。解决办法就是开辟一个新 【【微信】】 空间,将老 【【微信】】 中的元素移动到新 【【微信】】,使得同一个 【【微信】】 中的 key 排列地更紧密。这样,原来,在 o【【微信】】 【【微信】】 中的 key 可以移动到 【【微信】】 中来。结果是节省空间,提高 【【微信】】 利用率,map 的查找和插入效率自然就会提升。
go map 是 hash 实现,我们先不管具体原理,江湖人人皆知基于 hash 实现的算法其时间复杂度均为 O(1)。
- 正常情况,且不考虑扩容状态,复杂度O(1):通过hash值定位桶是O(1),一个桶最多8个元素,合理的hash算法应该能把元素相对均匀散列,所以溢出链表(如果有)也不会太长,所以虽然在桶和溢出链表上定位key是遍历,考虑到数量小也可以认为是O(1)。
- 正常情况,处于扩容状态时,复杂度也是O(1):相比于上一种状态,扩容会增加搬迁最多 2 个桶和溢出链表的时间消耗,当溢出链表不太长时,复杂度也可以认为是 O(1)。
- 极端情况,散列极不均匀,大部分数据被集中在一条散列链表上,复杂度退化为O(n)。
Go 采用的 hash 算法是很成熟的算法
新版bing出错了请重试 bing响应时间过长
新版bing怎么申请,新版bing怎么聊天,新版bing网址,新版bing app 下载每轮新建对话聊天增加至15次 这类消息是从哪看的? 是150个话题还是总共150句话啊 可惜我们这些用插件的现在用不了了 现在大家每轮对话限制多少?昨天我有4~5个小时每轮15次限制,之后又下调成10次了,别是我说错话光限制我一个人 什么时候能一直对话 现在随便让它写个故事,啥提示不给都有极大概率被屏蔽,体验也太差了 GPT4凑热闹 我的edge升级到最新版有侧边栏了 侧边栏里的bing说他没有次数限制 挺方便的 我问它它说每天总数无限次,只有15的限制