热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

GolangMap数据结构和mapbuckets的数据组织结构

hash表是什么从大学的课本里面,我们学到:hash表其实就是将key通过hash算法映射到数组的某个位置,然后把对应的val存放起来。如果出现了hash冲突(也就是说,不同的ke





hash 表是什么


从大学的课本里面,我们学到:hash 表其实就是将key 通过hash算法映射到数组的某个位置,然后把对应的val存放起来。

如果出现了hash冲突(也就是说,不同的key被映射到了相同的位置上时),就需要解决hash冲突。解决hash冲突的方法还是比较多的,比如说开放定址法,再哈希法,链地址法,公共溢出区等(复习下大学的基本知识)。


其中链地址法比较常见,下面是一个链地址法的常见模式:



position 指通过key 计算出的数组偏移量。例如当 position = 6 的位置已经填满kv后,再次插入一条相同position的数据将通过链表的方式插入到该条位置之后。


在php的array 中是这么实现的,golang中也基本是这么实现。下面我们学习下golang中map的实现。



golang map 实现的数据结构


golang的map中,首先把kv 分在了n个桶中,每个桶中的数据有8条(bucketcnt)。如果一个桶满了(overflow),也会采用链地址法解决hash 的冲突。


下面是定义一个hashmap的结构体:

type hmap struct {
// 长度
count int
// map 的标识, 下方做了定义
flags uint8
// 实际buckets 的长度为 2 ^ b
b uint8
// 从bucket中溢出的数量,(存在extra 里面)
noverflow uint16
// hash 种子,做key 哈希的时候会用到
hash0 uint32
// 存储 buckets 的地方
buckets unsafe.pointer
// 迁移时oldbuckets中存放部分buckets 的数据
oldbuckets unsafe.pointer
// 迁移的数量
nevacuate uintptr
// 一些额外的字段,在做溢出处理以及数据增长的时候会用到
extra *mapextra
}
const (
// 有一个迭代器在使用buckets
iterator = 1
// 有一个迭代器在使用oldbuckets
olditerator = 2
// 并发写,通过这个标识报panic
hashwriting = 4
samesizegrow = 8
)
type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextoverflow *bmap
}
type bmap struct {
tophash [bucketcnt]uint8
}


表中除了对基本的hash数据结构做了定义外,还对数据迁移、扩容等操作做了定义,这里我们可以忽略,等学习到时我们再深入了解。



深入 桶列表 (buckets)


buckets 字段中是存储桶数据的地方。正常会一次申请至少2^n长度的数组,数组中每个元素就是一个桶。n 就是结构体中的b。这里面要注意以下几点:




  1. 为啥是2的幂次方 为了做完hash后,通过掩码的方式取到数组的偏移量, 省掉了不必要的计算。


  2. b 这个数是怎么确定的 这个和我们map中要存放的数据量是有很大关系的。我们在创建map的时候来详述。


  3. bucket 的偏移是怎么计算的 hash 方法有多个,在 runtime/alg.go 里面定义了。不同的类型用不同的hash算法。算出来是一个uint32的一个hash 码,通过和b取掩码,就找到了bucket的偏移了。下面是取对应bucket的例子:

// 根据key的类型取相应的hash算法
alg := t.key.alg
hash := alg.hash(key, uintptr(h.hash0))
// 根据b拿到一个掩码
m := bucketmask(h.b)
// 通过掩码以及hash指,计算偏移得到一个bucket
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))


深入 桶 (bucket)


一个桶的示意图如下:


每个桶里面,可以放8个k,8个v,还有一个overflow指针(就是上面的next),用来指向下一个bucket 的地址。在每个bucket的头部,还会放置一个tophash,也就是bmap 结构体。这个数组里面存放的是key的hash值,用来对比我们key生成的hash和存出的hash是否一致(当然除了这个还有其他的用途,后面讲数据访问的时候会讲到)。 tophash中的数据,是从计算的hash值里面截取的。获取bucket 是用的低bit位的hash,tophash 使用的是高bit位的hash值(8位)




  1. 为啥bucket 一次要存8个kv,而不是一个kv放一个bucket,然后链地址法做处理就ok了 据我分析,有几点原因: a, 一次分配8个kv的空间,可以减少内存的分配频次; b,减少了overflow指针的内存占用,比如说8个kv,采用一个一个存储的话,需要8 * 8b (64位机) = 64b的数据存下一个的地址,而采用go实现的这种方式,只需要 8b + 8b (bmap的大小) = 16b 的数据就可以了。


  2. 为啥需要用tophash 一般的hash 实现逻辑是直接和key比较,如果比较成功,这找到相应key的数据。但是这里用到了tophash,好处是可以减少key的比较成本(毕竟key 不一定都是整数形式存在的)


  3. 为啥是8个 8 * 8b = 64b 整好是64位机的一个最小寻址空间,不过可以通过修改源码自定义吧。


  4. 为什么key 和val 要分开放 这个也比较好理解,key 和val 都是用户可以自定义的。如果key是定长的(比如是数字,或者 指针之类的,大概率是这样。)内存是比较整齐的,利于寻址吧。



技术总结


golang 实现的map比朴素的hashmap 在很多方面都有优化。




  1. 使用掩码方式获取偏移,减少判断。


  2. bucket 存储方式的优化。


  3. 通过tophash 先进行一次比较,减少key 比较的成本。


  4. 当然,有一点是不太明白的,为啥 overflow 指针要放在 kv 后面? 放在tophash 之后的位置岂不是更完美?


原文连接:https://www.cnblogs.com/-lee/p/12777241.html



推荐阅读
  • 使用GDI的一些AIP函数我们可以轻易的绘制出简 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 解决Element UI中Select组件创建条目为空时报错的问题
    本文介绍如何在Element UI的Select组件中使用allow-create属性创建新条目,并处理创建条目为空时出现的错误。我们将详细说明filterable属性的必要性,以及default-first-option属性的作用。 ... [详细]
  • 深入理解Java泛型:JDK 5的新特性
    本文详细介绍了Java泛型的概念及其在JDK 5中的应用,通过具体代码示例解释了泛型的引入、作用和优势。同时,探讨了泛型类、泛型方法和泛型接口的实现,并深入讲解了通配符的使用。 ... [详细]
author-avatar
gbn3312168
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有