HashMap、HashTable、ConcurrentHashMap总结

1、HashMap特点?

HashMap的特性:HashMap存储键值对,实现快速存取数据;允许null键/值;非同步无序(比如插入的顺序)。实现map接口。

2、HashMap的原理,内部数据结构?

HashMap是基于hashing的原理

jdk1.7及以前

底层使用哈希表(数组 + 链表)实现。

使用一个Entry链表数组来存储数据。

Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
loadFactor:负载因子,默认为 0.75。
threshold:扩容的阈值,等于 capacity * loadFactor

put分析:
1. 当插入第一个元素的时候,需要先初始化数组大小,然后获取 key 的 hash 值。
2. 通过hash和数组长度相与计算,即hash & (length-1),找到对应的数组下标。
3. 遍历对应下标处的链表,看是否有重复的 key 已经存在,如果有,直接覆盖,put 方法返回旧值就结束了
4. 不存在重复的 key,将此 entry 添加到链表中

即:当我们往hash表中添加一个对象时,会调用对象的hash code方法,根据hash算法算出对应的数组的索引值,再根据索引值查找数组,数组中是否存在对象,如果不存在对象直接存进去。如果存在对象,则通过equals比较两个对象的key值是否相等,如果相等则覆盖value值。

扩容分析:

在插入新值的时候,如果当前的 size 已经达到了阈值,并且要插入的数组位置上已经有元素,那么就会触发扩容,扩容后,数组大小为原来的 2 倍。扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值迁移到新的数组中。
由于是双倍扩容,迁移过程中,会将原来 table[i] 中的链表的所有节点,分拆到新的数组。

遍历时需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。

get分析:

1.根据 key 计算 hash 值。
2.找到相应的数组下标:hash & (length – 1)。
3.遍历该数组位置处的链表,直到找到相等(==或equals)的 key。

 

 

jdk1.8及后

底层使用哈希表(数组 + 链表或红黑树)实现。

使用一个Node数组来存储数据,不过,Node 只能用于链表的情况,红黑树的情况需要使用 TreeNode。

当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

put分析:

 

扩容:

resize() 方法用于初始化数组或数组扩容,每次扩容后,容量为原来的 2 倍,并进行数据迁移。

 

get分析:

1.计算 key 的 hash 值,根据 hash 值找到对应数组下标: hash & (length-1)
2.判断数组该位置处的元素是否刚好就是我们要找的,如果不是,走第三步
3.判断该元素类型是否是 TreeNode,如果是,用红黑树的方法取数据,如果不是,走第四步
4.遍历链表,直到找到相等(==或equals)的 key

 

3、HashMap的结构,1.7和1.8有哪些区别

(1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

(2)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)

 

4、HashMap 的put 方法引发的问题
HashMap 并不是线程安全的类,所以在resize() 操作时,并发下可能会造成的问题

1.形成环形链表
2.并发插入:丢失值

5、为什么HashMap具备下述特点:键-值(key-value)都允许为空、线程不安全、不保证有序、存储位置随时间变化?

(1)键-值都允许为空null

键:唯一,可为null,因为当key为空时,hash值默认设置为0,只等有1个key为null。

键:不需唯一,可以有多个为null。

(2)线程不安全

原因是没有同步锁保护,多线程下容易出现resize()死循环,并发执行put操作导致触发扩容行为,从而导致环形链表在获取数据遍历链表时形成死循环,在多线程情况下建议使用线程安全的ConcurrentHashMap。

(3)不保证有序

插入顺序和存储顺序不同,插入顺序是用户操作的信息,存储顺序是根据hash算法计算而来的数组下标顺序,该算法讲究随机性、均匀性,不具备某种规律规则,所以是无须的。

(4)存储位置随时间变化

由于存在扩容操作,当哈希表的大小>=扩容阈值时,就会扩容哈希表(即扩充HashMap的容量),从而导致存储位置重新计算,而存储位置也发生变化。

6、为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键

String、Integer等包装类的特性(final类型、内部已重写了equals()、hashCode()方法)保证了Hash值的不可更改性和计算准确性,有效减少了发生Hash冲突的几率。

 

7、HashMap 中的 key若 为Object类型, 则需实现哪些方法?

需要重写hashCode()、equals()方法。

具体描述:hashCode()方法用于计算需要存储数据的存储位置(实现不当会导致严重的Hash碰撞(冲突));equals()用来比较存储位置上是否存在需要存储数据的键key,若存在,则直接替换更新value,若不存在则插入数据,比较目的都是为了保证key在哈希表中的唯一性。

 

 

 


参考文章;https://www.jianshu.com/p/8324a34577a0?utm_source=oschina-app

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值