本篇文章我们来看下Go的高级数据结构,因文章偏长分为两篇,此为下篇。本系列的其他文章可到「对比Python学习Go」-开篇[1]查看,下面我们开始今天的分享。
上篇说道,Go和Python的数据结构可分为类数组和哈希结构。本篇我们来看下哈希结构相关的类型。
哈希结构哈希结构又叫做散列表(hashtable),它是数组的一种扩展。它通过散列函数把元素的键值映射为数组的下标,然后将数据存储在数组中对应下标的位置。当我们按照键值查询元素时,我们用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。通过散列函数,我们可以类似数组下标一样直接定位数据,时间复杂度可以到O(1)。
哈希结构中,最重要的两个知识点是「哈希函数的构建」和「散列冲突的解决」。
哈希函数构建的好坏直接影响到数据结构的性能,哈希的key分布均匀的话,会减少散列冲突的发生。散列冲突是哈希结构不可避免的,解决散列冲突的方法主要有两种,是「开放寻址法(openaddressing)」和「列表法(chaining)」。
开发寻址法,即利用一些算法查找下一个为空的数组位置。列表法,是在当前key的数组位置,以链表的形式,增加额外空间。
更多哈希知识,可参考我整理的有关散列表的笔记数据结构与算法-散列表[2]。
了解了上边列出的哈希结构的基本知识后,我们来看看Go和Python的哈希结构是如何的。
GoGo语言的中的哈希结构为map结构,根据map的源码[3]分析,map的底层结构大致如下:
最外层为一个hmap的结构体,使用一个[]bmap数组存放了bmap的地址,bmap用来存储数据,每个bmap最多可存储8个kv对,另外还有一个overflow,存储后一个bmap的地址。oldbuckets用来存放老的buckets数组地址,在扩容的时候会使用来暂存还没有移到新buckets数组的bmap地址。mapextra用来存放非指针数据,用于优化存储和访问。
关于map内存的增长扩容,则主要是[]bmap数组的扩容。当数据越来越多时,[]bmap数组后边挂的bmap也会越来越多,bmap的数量越多,在查找时,则大部分时间会花费在链表的查找上。这里有个标准,通常是在装填因子(填入表中的元素个数/散列表的长度)[大于6.5](