1. Hash函数算法
Hash,通常翻译作“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(关键字),经过散列算法,变换成固定长度的输出,该输出就是散列值。 这种转换是一种压缩映射(输入的长度小于输出的长度)。常见的Hash函数有如下几个:除留余数法、直接寻址法、数字分析法、平方取中法、分段折叠法、随机数法。数组
1.1 除留余数法 : hash(k) = k % a. [其中,a为正整数]安全
直接以余数做为散列值。【HashMap中,a为HashMap的表长】函数
1.2 直接寻址法:hash(k) = a * k + c .[其中,a,c为常数 & a <> 0]性能
直接以关键字的某个线性函数做为散列值。spa
1.3 数字分析法 : 线程
提取关键字中取值比较均匀(重复几率比较小)的数字做为散列值。例如:同一个班级的学生的生日,年份的重复率比较高,冲突的几率大,因此取值的时候就尽可能避开年份。翻译
1.4 平方取中法:hash(k) = (k^2) / (10^a) % (10^b) [其中,a,b为正整数 & a > b]code
取关键字平方后的中间几位为哈希地址。对象
1.5 分段折叠法 :
将关键字分红长度相等的几段(最后一段能够短一些),将几部分相加,舍弃最高位的结果做为散列值。
1.6 随机数法 :
采用一个随机数做为散列值。(计算机中没有真正的随机数,所谓的随机数都是经过某种算法获取。既然有算法,就不可能真正的随机)
2.Hash碰撞
对不一样的关键字可能获得同一散列地址,即key1≠key2,而hash(key1)=hash(key2),这就是hash碰撞。衡量一个哈希函数的好坏的重要指标就是发生碰撞的几率以及发生碰撞的解决方案(任何哈希函数基本都没法完全避免碰撞)。常见的解决碰撞的方法有如下几种:链地址法、开放地址法、再哈希法、创建公共溢出区。
2.1 链地址法:将哈希表的每一个单元做为链表的头结点,全部散列值(即哈希地址)相同的元素添加到一个链表里。
2.2 开放地址法:一旦发生Hash碰撞,就去寻找下一个空的哈希地址。
2.3 再哈希法:当Hash碰撞时,就再次经过hash函数计算出新的哈希地址,直到碰撞再也不发生。
2.4 创建公共溢出区:将哈希表分为基本表和溢出表两部分,放生Hash碰撞的元素都存入溢出表中。
3. HashMap
3.1 HashMap由数组和链表组成(数组是主体,数组中存放的对象是链表对象),即采用链地址法解决Hash碰撞(采用除留余数法肯定数组下标)。对于哈希地址相同的key-value对象,依次被添加到链表的尾端。
3.2 HashMap中,链表对象(Entry<K,V>)的存储地址(即:索引)是经过hash(int h)和indexFor(int h, int length)联合获取的。
/** * 该方法是为了获取高质量的hashCode,即:减小碰撞的几率,尽可能作到任何一位的变化都能对最终获得的结果产生影响。 * 算法思想:把高位的特征和低位的特征组合起来 */ //Java 7 static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } //Java 8 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
/** * 该方法是为了获取数组的下标。 * 算法思想:将hashcode对数组的长度取模 */ static int indexFor(int h, int length) { //等价于: h % length //由于数组的长度恒等于2^n,因此能够经过位运算符计算(直接对内存操做,不用转换成十进制,效率高) //位运算还能够很好的解决负数的问题(负数的模运算结果仍为负数,可是位运算返回的结果为正数). return h & (length-1); }
3.3 Java 7中使用单向链表存储冲突元素,Java 8中使用平衡树来替代链表存储冲突的元素。在最坏的状况下,Java7将HashMap的get方法的性能为 O(n),Java 8的性能为O(logn) 。若是恶意程序知道咱们用的是Hash算法,则在单向链表状况下,它可以发送大量请求致使哈希碰撞,而后不停访问这些key致使HashMap忙于进行线性查找,最终陷入瘫痪,即造成了拒绝服务攻击(DoS)。
3.4 HashMap中,key、value均可觉得null。
3.5 HashMap的数组是有长度的,Java中规定这个长度只能是2的倍数(即:size = 2^n),初始值为16。每次扩容为原来的二倍。
3.6 HashMap 的实现不是线程同步的,这意味着它不是线程安全的。
3.7 加载因子:是哈希表在其容量自动增长以前能够达到多满的一种尺度,默认值为0.75。【当哈希表的数据量 >=(容器*加载因子)时,哈希表将进行扩容】
4. 参考资料
4.1 百度百科 : 《Hash(散列函数)》
4.2 Hollis : 《全网把 Map 中的 hash() 分析的最透彻的文章,别无二家》
4.3 JDK源码