HashMap的实现原理?
它主要分为了一下几个部分:
1,底层使用hash表数据结构,即数组+(链表 | 红黑树)
2,添加数据时,计算key的值确定元素在数组中的下标,key相同则替换,不同则存入链表或红黑树中
3,获取数据通过key的hash计算数组下标获取元素
HashMap的jdk1.7和jdk1.8有什么区别
-
JDK1.8之前采用的拉链法,数组+链表——拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
JDK1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树
JDK 1.8中HashMap的get方法的简要实现过程:
-
首先,需要计算键的哈希值,并通过哈希值计算出在数组中的索引位置。
-
如果该位置上的元素为空,说明没有找到对应的键值对,直接返回null。
-
如果该位置上的元素不为空,遍历该位置上的元素,如果找到了与当前键相等的键值对,那么返回该键值对的值,否则返回null。
HashMap的put方法的具体流程
-
首先,put方法会计算键的哈希值(通过调用hash方法),并通过哈希值计算出在数组中的索引位置。
-
如果该位置上的元素为空,那么直接将键值对存储在该位置上。
-
如果该位置上的元素不为空,那么遍历该位置上的元素,如果找到了与当前键相等的键值对,那么将该键值对的值更新为当前值,并返回旧值。
-
如果该位置上的元素不为空,但没有与当前键相等的键值对,那么将键值对插入到链表或红黑树中(如果该位置上的元素数量超过了一个阈值,就会将链表转化为红黑树来提高效率)。
-
如果插入成功,返回被替换的值;如果插入失败,返回null。
-
插入成功后,如果需要扩容,那么就进行一次扩容操作。
HashMap的扩容机制
-
在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到了扩容阈值(数组长度 * 0.75)
-
每次扩容的时候,都是扩容之前容量的2倍;
-
扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中
-
没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置
-
如果是红黑树,走红黑树的添加
-
如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
oldCap——老的容量
Java 1.8 HashMap的优化
-
数组+链表改成了数组+链表或红黑树;
-
链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7 将新元素放到数组中,原始节点作为新节点的后继节点,1.8 遍历链表,将元素放置到链表的最后;
-
扩容的时候 1.7 需要对原数组中的元素进行重新 hash 定位在新数组的位置,1.8 采用更简单的判断逻辑,位置不变或索引+旧容量大小;
-
在插入时,1.7 先判断是否需要扩容,再插入,1.8 先进行插入,插入完成再判断是否需要扩容;
为什么要做这几点优化;
-
防止发生 hash 冲突,链表长度过长,时间复杂度太高。改为红黑树后时间复杂度由O(n)降为O(logn);
-
因为 1.7 头插法扩容时,头插法会使链表发生反转,多线程环境下会产生环;
ConcurrentHashMap是如何保证线程安全的?
在JDK 1.7中,ConcurrentHashMap使用了分段锁技术,即将哈希表分成多个段,每个段拥有一个独立的锁。这样可以在多个线程同时访问哈希表时,只需要锁住需要操作的那个段,而不是整个哈希表,从而提高了并发性能。虽然JDK 1.7的这种方式可以减少锁竞争,但是在高并发场景下,仍然会出现锁竞争,从而导致性能下降。
在JDK 1.8中,ConcurrentHashMap的实现方式进行了改进,使用分段锁(思想)和“CAS+Synchronized”的机制来保证线程安全。在JDK 1.8中,ConcurrentHashMap会在添加元素时,如果某个段为空,那么使用CAS操作来添加新节点;如果段不为空,使用Synchronized锁住当前段,再次尝试put。这样可以避免分段锁机制下的锁粒度太大,以及在高并发场景下,由于线程数量过多导致的锁竞争问题,提高了并发性能。
HashMap和ConcurrentHashMap的区别
1、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。
2、ConcurrentHashMap在JDK 1.8之前使用分段锁保证线程安全, ConcurrentHashMap默认情况下将hash表分为16个桶(分片),在加锁的时候,针对每个单独的分片进行加锁,其他分片不受影响。锁的粒度更细,所以他的性能更好。
ConcurrentHashMap在JDK 1.8中,采用了一种新的方式来实现线程安全,即使用了CAS+synchronized,这个实现被称为"分段锁"的变种,也被称为"锁分离",它将锁定粒度更细,把锁的粒度从整个Map降低到了单个桶。
3、HashMap中,null可以作为键或者值都可以。ConcurrentHashMap中,key和value都不允许为null。
HashTable与HashMap的区别
嗯,他们的主要区别是有几个吧
第一,数据结构不一样,hashtable是数组+链表,hashmap在1.8之后改为了数组+链表+红黑树
第二,hashtable存储数据的时候都不能为null,而hashmap是可以的,key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性
第三,hash算法不同,hashtable是用本地修饰的hashcode值,而hashmap经常进行了二次hash
第四,扩容方式不同,hashtable是当前容量翻倍+1(默认容量为11),hashmap是当前容量翻倍(默认容量为16)
第五,hashtable是线程安全的,操作数据的时候加了锁synchronized,hashmap不是线程安全的,效率更高一些。在实际开中不建议使用HashTable,在多线程环境下可以使用ConcurrentHashMap类