讲一下set的原理,Java 的HashMap和 go 的map底层原理

题目来源:深信服

答案1:

1. Set原理:
Set特性: 1. 不包含重复key. 2.无序.
如何去重:
通过查看源码add(E e)方法,底层实现有一个map,map是HashMap,Hash类型是散列,所以是无序的.
如果key值相同,将会覆盖,这就是set为什么能去重的原因(key相同会覆盖).
注意:
如果new出两个对象add到set中,因为两个对象的地址不相同,所以map在计算key的hash值时,将它当成了两个不同的元素。这时要重写equals和hashcode两个方法。
这样才能保证set集合的元素不重复.

2. Java HashMap:

线程不安全 安全的map(CurrentHashMap)
HashMap由数组+链表组成,数组是HashMap的主体,
链表则是为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;
如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;
对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。
所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
假如一个数组槽位上链上数据过多(即链表过长的情况)导致性能下降该怎么办?
JDK1.8在JDK1.7的基础上针对增加了红黑树来进行优化。
即当链表超过8时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。

3. go map:

线程不安全 安全的map(sync.map)
特性: 1. 无序. 2. 长度不固定. 3. 引用类型.
底层实现:
1.hmap 2.bmap(bucket)
hmap中含有n个bmap,是一个数组.
每个bucket又以链表的形式向下连接新的bucket.
bucket关注三个字段: 1. 高位哈希值 2. 存储key和value的数组 3. 指向扩容bucket的指针
高位哈希值: 用于寻找bucket中的哪个key.
低位哈希值: 用于寻找当前key属于hmap中的哪个bucket.
map的扩容:
当map中的元素增长的时候,Go语言会将bucket数组的数量扩充一倍,产生一个新的bucket数组,并将旧数组的数据迁移至新数组。
加载因子
判断扩充的条件,就是哈希表中的加载因子(即loadFactor)。
加载因子是一个阈值,一般表示为:散列包含的元素数 除以 位置总数。是一种“产生冲突机会”和“空间使用”的平衡与折中:加载因子越小,说明空间空置率高,空间使用率小,但是加载因子越大,说明空间利用率上去了,但是“产生冲突机会”高了。
每种哈希表的都会有一个加载因子,数值超过加载因子就会为哈希表扩容。
Golang的map的加载因子的公式是:map长度 / 2^B(这是代表bmap数组的长度,B是取的低位的位数)阈值是6.5。其中B可以理解为已扩容的次数。
当Go的map长度增长到大于加载因子所需的map长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段oldbucket中。注意:并不是立刻把旧的数组中的元素转义到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中。
map删除:
并不会直接删除旧的bucket,而是把原来的引用去掉,利用GC清除内存。