3. 平方取中法 --( 了解 )
假设关键字为 1234 ,对它平方就是 1522756 ,抽取中间的 3 位 227 作为哈希地址; 再比如关键字为 4321 ,对 、它平方就是18671041 ,抽取中间的 3 位 671( 或 710) 作为哈希地址 平方取中法比较适合:不知道关键字的分 布,而位数又不是很大的情况
4. 折叠法 --( 了解 )
折叠法是将关键字从左到右分割成位数相等的几部分( 最后一部分位数可以短些 ) ,然后将这几部分叠加求和, 并按散列表表长,取后几位作为散列地址。 折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5. 随机数法 --( 了解 )
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random(key), 其中 random 为随机数函数。 通常应用于关键字长度不等时采用此法
6. 数学分析法 --( 了解 )
设有 n 个 d 位数,每一位可能有 r 种不同的符号,这 r 种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
4.冲突的避免--负载因子调节
散列表的载荷因子(负载因子)=填入表中的元素/散列表的长度
由于表长是定值,所以填入的元素越多,负载因子越大,产生冲突的可能性就越大。一般要将载荷因子控制在0.75以下,当超过0.75时,就应该对哈希表中的数组进行扩容
5.冲突的解决之闭散列
闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 把 key 存放到冲突位置中的 “ 下一个 ” 空位置中去。那么如何找到下一个空位置呢?
法一:线性探测
从发生冲突的位置开始,依次向后进行探测,直到找到下一个空位置。缺陷是产生冲突的元素会堆积在一块,例如:
想要插入11,21,31,41,就会依次放到2,3,8,0下标
法二:二次探测
找下一个空位置的方法为:Hi = (H0+ i^2)% m, 或者: Hi= (H0-i^2 )% m。其中:H0是通过哈希函数计算出的下标, i = 1,2,3… ,表示的是发生冲突的次数,例如
想要放21,通过哈希函数计算出来是1,即H0=1,这是第一次发生冲突,所以i=1,所以 Hi= (H0+i^2 )% m即Hi=(1+1)%10=2。
6.冲突的解决之开散列(哈希桶)
开散列法又叫链地址法 ( 开链法 ) ,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子 集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
比如要放11,通过散列函数计算出下标为1,所以可以通过头插法或者尾插法将11查到对应的链表里
这就是我们所说的哈希表实际上是数组+链表+红黑树(当数组长度>=64&&链表长度>=8以后,就会将其变成一棵红黑树)
java的HashMap就是用这种哈希表的方式来解决哈希冲突的
7.哈希桶的实现
首先注意,哈希桶是一个数组,数组的元素其实就是每个链表的头节点

放置元素:
首先要找到对应链表,然后遍历该链表,如果某个结点的key等于待插入结点的key,就更新该节点的value值,然后结束该方法即可;如果没有对应的key,就通过头插法或者尾插法插入该节点,代码如下:

但这样是有缺陷的,我们之前提到了负载因子最好不超过0.75,所以我们再加一个方法返回当前的负载因子,如果大于0.75就进行扩容,代码如下:

首先添加一个成员变量,即默认最大负载因子

然后是计算负载因子

最后是在put方法的最后进行扩容。
但这样的扩容不对!!!因为数组长度变了,对应的key所在的下标就会变!!!,所以代码应为(我用的头插法扩容):

获得value:

HashMap,HasSet的实现
1.Map不支持迭代器遍历,因为它没有实现Iterable接口,要想用迭代器,就必须将Map转化成Set
2.Map中的对象不需要必须可比较,因为他是通过Hash函数来计算所处位置,而不是通过大小比较。并且key或value可以是null,如下:

3.HashMap和HashSet一样是可以天然去重的,(TreeSet,TreeMap也一样)如下:

4.HashMap,HashSet对象不一定可比较,key也不一定是一个整数,那么系统是怎么找到对应的下标从而将键值对放进去的呢?这就用到了HashCode方法来生成一个整数。看源码:


当key不是null时,就会调用key的hashCode方法,如果key本身没有hashCode方法,就会调用Object类的方法:


这段话的意思是,在合理情况下,不同的对象会返回不同的整数,这通常是将对象的内部地址转换为整数实现的,所以下面的情况,即使Student的id是相同的,产生的hashCode也是不同的,如下


要想产生同样的整数,就可以重写hashCode方法:

这是我们根据上面的源码自己重写的方法,调用了hash方法,传参时直接传入id,但最好是通过编译器直接生成hashCode方法,同时一并重写equals方法

如果不重写equals方法,相同id的对象jinxingequals方法后产生的结果是false,因为源码中equals是根据对象的地址比较的
哈希桶的优化代码
我们将哈希桶写成一个泛型类,并让其可以通过各种类型的key生成对应的整数,代码如下:

1.放置键值对:

2.根据key得到对应的value

看下面的一段代码:

student1和student2的id是一样的,而且我们重写了hashCode方法,所以说,虽然我们只在哈希桶里面放置了student1,但在根据student2取元素时,得到的也应该是10,但结果却如下:

这是因为put函数和getval函数有一句是错的,即if(cur.key==key),应该用equals方法,代码如下:

