1.HashMap存储(k,v)键值对实现快速存取,key值不可重复,若key值重复就会覆盖,允许key值为null。
2.HashMap底层结构是数组+链表+红黑树,HashMap中的元素是无序的。
3.是非同步,非线程安全。
1.主要由于并发扩容引起。java7中每次扩容会变成原来的2倍,如果原来是16扩容后是32。扩容完成了,原来map中的值会被迁移到新的map。java7采用头插法,所以原来索引中的节点迁移到新索引位置,顺序会倒序。原来顺序为1,2,3、现在的顺序会变成3,2,1。当容量从32再次扩容变成64时,又因为节点倒序,3,2,1的节点顺序会再次变成1,2,3所以这个时候会形成环状结构,造成死锁。因此HashMap中的put()是非线程安全的。
2.java8 对HashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在并发执行put操作时会发生数据覆盖的情况,在多线程的情况下仍然不安全。
基于哈希的原理,jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们向HashMap put()一个(key,value)时,会先对key做一个hashCode()的计算来得到一个索引值index,通过索引值可以明确Entry对象存储在数组中的位置。当获取对象时,通过get()获取到bucket数组中的位置,再通过key对象的equals()方法找到对应的键值对,然后返回value对象。
1.计算key的hashcode值(与Key.hashCode的高16位做异或运算)
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断
4.1:若key地址相同或者equals后内容相同,则替换旧值
4.2:如果是红黑树结构,就调用树的插入方法
4.3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
5.如果桶满了大于阀值,则resize进行扩容
调用场景:
1.初始化数组table
2.当数组table的size达到阙值时即++size > load factor * capacity 时 ++size > 0.75 x容量
实现过程:(细讲)
1.通过判断旧数组的容量是否大于0来判断数组是否初始化过
否:进行初始化
是,进行扩容,扩容成两倍(小于最大值的情况下),之后在进行将元素重新进行与运算复制到新的散列表中
概括的讲:扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。
PS:可见底层数据结构用到了数组,到最后会因为容量问题都需要进行扩容操作
对key的hashCode值进行哈希运算,与运算计算索引获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。
对key的hashCode做hash操作,与高16位做异或运算
还有平方取中法,除留余数法,伪随机数法
因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16位做异或运算使得在做&运算确定数组的插入位置时,此时的低位实际是高位与低位的结合,增加了随机性,减少了哈希碰撞的次数。
HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,必须是2的幂。
https://blog.csdn.net/sidihuo/article/details/78489820
https://blog.csdn.net/eaphyy/article/details/84386313
1.为了数据的均匀分布,减少哈希碰撞。因为确定数组位置是用的位运算,若数据不是2的次幂则会增加哈希碰撞的次数和浪费数组空间。(PS:其实若不考虑效率,求余也可以就不用位运算了也不用长度必需为2的幂次)
2.输入数据若不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字
会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储
HashCode相同,通过equals比较内容获取值对象
超过阙值会进行扩容操作,扩容后的数组大小是原数组的2倍,将原来的元素重新哈希放入到新的散列表中去。
负载因子表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认负载因子等于0.75,可以自定义负载因子。当HashMap里面的元素已经达到数组长度的75%时,表示HashMap太挤了,需要扩容,扩容后的数组大小是原数组的2倍。
JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),查询效率非常低,完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。
选择Integer,String这种不可变的类型,像对String的一切操作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的重写了hashCode()以及equals()方法。作为不可变类天生是线程安全的,
== 比较基本类型是判断数值是否相等 ,比较引用类型是判断地址是否相等
equals() 比较重写过的equals()方法是判断数值是否相等,例如String 它重写了equals方法,比较数值。
如果没有重写equals方法,就是object自带的equals方法,就是比较地址是否相等。
equals方法不能比较基本类型。
相同点:都是存储key-value键值对的
不同点: