Jdk1.8之前:
jdk1.8之前HashMap底层是数组跟链表,结合以前就是链表散列HashMap通过key的hashCode经过扰动函数处理的到hash值,然后通过(数组长度-1)&hash值判断当前元素存放的位置,如果当前元素存在的话,就判断该元素与要存入的元素的hash值以及key是否相同,如果相同直接覆盖,如果不相同就通过拉链法解决冲突。
扰动函数是指的是HashMap的hash方法。使用hash方法也就是扰动函数是为了防止一些实现比较差的hashCode()方法换句话说使用扰动函数是可以减少碰撞。
Jdk1.8的hash方法相比较于jdk1.7的hash方法更加简化,但是原理不变。
static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位异或 // >>>:⽆符号右移,忽略符号位,空位都以0补⻬ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
对比一下Jdk1.7的HashMap的hash方法源码
static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
相比于jdk1.8的hash方法,jdk1.7的hash方法的性能会比较差一点毕竟扰动了4次。
所谓“拉链法”就是:将数组和链表相结合。就是创建一个链表数组,数组中每一各就是一个链表。若遇到哈希冲突,则将冲突的值加入到链表就行。
jdk1.8之后
相比较于之前的版本,jdk1.8之后在解决哈希冲突有了比较大的变化,当链表长度大于阈值(默认8)时候
将链表转化为红黑树,减少搜索时间。
TreeMap,TreeSet以及jdk之后的HashMap底层都用到了红黑树,红黑树是为了解决二叉树的缺陷,因为二叉树在某种情况会退化成一个线性结构。
HashMap的长度为什么是2的幂次方
为了能让HashMap存储高效,尽量减少碰撞,也就是把数据分配均匀,Hash值得范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射的比较松散均匀,一般很难出现碰撞,但是问题是40亿的数组 内存是放不下的所以这个散列值是不能直接拿来用的。用之前还要先做对数组长度的取模运算,得到的余数才能用来存放的位置也就是对应数组的下标。这个数组的计算方法"(n-1)&hash"。(n代表数组的长度)。这也就是解释了Hash的长度为什么是2的幂次方。
这个算法如何设计的呢?
我们首先可能会想到%取余的操作来实现。但是重点:“取余(%)会导致如果除数2的幂次方等价于与除数建议的与(&)操作(也就是说hash%length==hash&(length-1)的前提是length是2的n次方;”并且采用二进制位操作&相对于%能够提高运算效率,这就是解释了HashMap的长度为什么2的幂次方了
hashMap多线程操作导致死循环的问题:
主要原因在于并发的情况下的Rehash会造成元素之间造成循环链表,不过jdk1.8解决了这个问题,但是建议多线程的情况下使用HashMap,因为多线程下使用Hash还会造成数据丢失的情况。并发的情况下推荐使用ConcurrentHashMap。
博客到此,分享一下我学习编程的途径希望大家循序渐进,共同进步:
How2J 的 Java教程:当下Java小白的引路人,以有趣和好理解的方式展示Java和Web的内容拥有当今流行的java路线。
哔哩哔哩 (゜-゜)つロ 干杯~-bilibili:中国最大的学习平台没有之一,拥有海量的资源有时间的小伙伴可以免去重金花钱培训。
廖雪峰的官方网站:廖雪峰,十年软件开发经验,业余产品经理,精通Java/Python/Ruby/Scheme/Objective C等,对开源框架有深入研究..
菜鸟教程 - 学的不仅是技术,更是梦想!:提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识。