HashMap是存储Key-Value键值对的集合,每个键值对也叫做Entry,这些Entry分散存储在一个数组中,这个数组可以成称是HashMap的主干。
为什么用HashMap?
-
HashMap 是一个散列桶(数组和链表),它存储的内容是键值对 key-value 映射
-
HashMap 采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改
-
HashMap 是非 synchronized,所以 HashMap 很快
-
HashMap 可以接受 null 键和值,而 Hashtable 则不能(原因就是 equlas() 方法需要对象,因为 HashMap 是后出的 API 经过处理才可以)
HashMap中的每一个元素的初始值都是NULL,且默认数组长度是16

HashMap有两个方法:GET 和 PUT。
当使用HashMap的put方法的时候,有两个问题要解决:
1、长度为16的数组中,元素存储在哪个位置
2、如果key出现hash冲突,如何解决
第一个问题,HashMap 是使用下面的算法来计算元素的存放位置的。
int hash = hash(key);
int i = indexFor(hash, table.length);
散列函数确定元素位置,取模法。
第二个问题
利用Entry类的next变量来实现链表,把最新的元素放到链表头,旧的数据则被最新的元素的next变量引用着。
什么是hash冲突?
哈希冲突是指哈希函数算出来的地址被别的元素占用了。
举个例子
最常见的bai哈希算法是取模法。
下面简单讲讲取模法的计算过程。
比如:数组的长度是5。这时有一个数据是6。那么如何把这个6存放到长度只有5的数组中呢。按照取模法,计算6%5,结果是1,那么就把6放到数组下标是1的位置。那么,
7就应该放到2这个位置。到此位置,哈斯冲突还没有出现。这时,有个数据是11,按照取模法,11%5=1,也等于1。那么原zhi数组下标是1的地方已经有数了,是6。这时又计算出1这个
位置,那么数组1这个位置,就必须储存两个数了。这时,就叫哈希冲突。冲突之后就要按照顺序dao存放了。
如何解决hash冲突
如果出现了大量hash冲突,那么遍历链表的时候,会比较慢。JDK 1.8里面,当链表的长度大于阀值(默认为8)的时候,会使用红黑树来存储数据,以便加快key的查询速度。
1 开放定址法:按照一定规则向其它地址探测,直到遇到空桶.比如线性探测,假如现在的索引是3,3有值就向4探测,如果还有就继续向下,还比如平方探测.就是1 2 3 ...的平方进行探测.
2:再哈希法,设计多个哈希函数
3:链地址法,HashMap就是这样操作的
JDK1.8是怎么解决哈希冲突的
默认使用的是单身链表将元素串起来
在添加元素时,可能会由单向链表转为红黑树来存储元素
比如当哈希表的容量>=64且单向链表的节点数量大于8时就会由单向链表转为红黑树来存储元素
当红黑树节点数量少到一定程度时,又会转为单向链表
所以JDK1.8中的哈表是由链表加上红黑树来解决冲突的.
jdk1.8中hashmap:
Java - hashMap 链表是头插还是尾插
JDK8以前是头插法,JDK8后是尾插法
为什么要从头插法改成尾插法?
A.因为头插法会造成死链
B.JDK7用头插是考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到)
HashMap扩容导致死循环的主要原因在于扩容过程中使用头插法将oldTable中的单链表中的节点插入到newTable的单链表头中,所以newTable中的单链表会倒置oldTable中的单链表。那么在多个线程同时扩容的情况下就可能导致扩容后的HashMap中存在一个有环的单链表,从而导致后续执行get操作的时候,会触发死循环,引起CPU的100%问题。所以一定要避免在并发环境下使用HashMap。
HashMap的结构都是这么简单,基于一个数组以及多个链表的实现,hash值冲突的时候,就将对应节点以链表的形式存储。
这样子的HashMap性能上就抱有一定疑问,如果说成百上千个节点在hash时发生碰撞,存储一个链表中,那么如果要查找其中一个节点,
那就不可避免的花费O(N)的查找时间,这将是多么大的性能损失。这个问题终于在JDK8中得到了解决。再最坏的情况下,链表查找的时间复杂度为O(n),而红黑树一直是O(logn),这样会提高HashMap的效率。
JDK7中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而JDK8中采用的是位桶+链表/红黑树(有关红黑树请查看红黑树)的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
JDK1.7和JDK1.8中HashMap为什么是线程不安全的?
首先需要强调一点,HashMap的线程不安全体现在会造成死循环、数据丢失、数据覆盖这些问题。其中死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而1.8中仍会有数据覆盖这样的问题。
HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。
本文围绕Java的HashMap展开,介绍了使用HashMap的原因,如采用数组和链表结构,查询修改方便且速度快等。阐述了散列函数确定元素位置及解决hash冲突的方法,包括开放定址法、再哈希法、链地址法等。还对比了JDK1.7和JDK1.8中HashMap链表插入方式及线程安全问题。

1140

被折叠的 条评论
为什么被折叠?



