HashMap详解、与HashTable、ConcurrentHashMap的区别(面试干货)

HashMap(单线程推荐使用):

概述:HashMap是什么?

        HashMap是Java使用频率最高的用于映射(键值对)处理的数据类型。

简单上手:

Map map= new HashMap();    //声明                       创建
map.put("name","小明");    //"name"为key,"小明"为value  
String name = map.get("name");  //通过key取值            
map.containsKey("name");   //判断key是否存在,存在就返回true map.containsValue("小明")  //判断value是否存在,存在就返回true map.size();                //返回键值对的个数
map.remove("name");        //删除key为name的键值对       

详解:

1、底层:数组+链表实现,key和value都可以存储null键,线程不安全(高并发慎用)

2、添加元素:初始是size为16的数组,通过计算key的hashcode并且与16取余,而后会以链表方式挂载数组相同下标后(jdk1.8之前)

图解:

      注: jdk1.8后当单条链表的长度达到8之后会转化为红黑树继续挂载。

        为什么用这样的数据类型呢?

        数组的特点:查询效率高,插入,删除效率低。

        链表的特点:查询效率低,插入删除效率高

        在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,

        使得查询和插入,删除的效率都很高

3、扩容:当Map中元素总数占用初始长度为16位的数组超过加载因子(0.75)时,数组会扩容,

为什么要扩容呢?

当链表的长度超过8以后,对链表的查询就会比较耗费时间了,为了减少链表长度,元素分配更均匀,所以应该增加数组的长度来减少链表的长度

加载因子为什么是0.75?

选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,至于为什么不选择0.5或0.8,我没有找到官方的直接说明,在HashMap的源码注释中也只是说是一种折中的选择。

4、扩容的过程:newsize = oldsize*2,(简单来说就是长度由16变32,以此类推

因为数组大小是最初就声明了的,所以没办法动态扩容,这个时候就要建一个新的数组,

把旧数组的值移过去。很显然扩容后每个对象的对应的下标位置就不同了,需要重新放元素了

怎么重新放?

扩容针对整个Map,每次扩容时原来中的元素依次重新计算存放位置,并重新插入(线程不安全

5、hashmap线程不安全:

1、多线程下扩容时可能造成死循环,会造成死锁形成环形链表或者造成扩容大小不一致等问题
2 、多个线程put的时,get的值可能不一致,put的操作不是原子性的
3 、删除键值对的时候,会删除刚刚修改的位置元素

所以高并发的场景下不建议使用hashmap

HashTable(基本淘汰):

概述:

HashTable是较为远古的使用Hash算法的容器结构了,现在基本已被淘汰,单线程转为使用HashMap,多线程使用ConcurrentHashMap。(简单理解就是给HashMap的方法都上了加锁)

操作:

HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为了实现多线程安全,在几乎所有的方法上都加上了synchronized锁,锁了整个表,而加锁的结果就是HashTable操作的效率十分低下。

与hashmap区别:

1、初始容量:默认初始容量是11

2、线程安全:HashMap是线程不安全的类,多线程下会造成并发冲突,但单线程下运行效率较高;HashTable是线程安全的类,很多方法都是用synchronized修饰,但同时因为加锁导致并发效率低下,单线程环境效率也十分低

3、不能插入null:HashMap允许一个键为null,多个值为null;但HashTable不允许键或值为 null

4、扩容机制:HashMap创建一个为原先2倍的数组,然后对原数组进行遍历以及rehash;HashTable扩容将创建一个原长度2倍+1的数组,再使用头插法将链表进行反序;

5、结构区别:HashMap是由数组+链表形成,在JDK1.8之后链表长度大于8时转化为红黑树;而HashTable一直都是数组+链表;

ConcurrentHashMap(高并发推荐使用):

概述:

是一个支持高并发更新与查询的哈希表(基于HashMap)。在保证安全前提下,进行检索不需锁定。与hashtable不同,该类不依赖于synchronization去保证线程操作的安全,ConcurrentHashMap是使用了锁分段技术来保证线程安全的。

简单理解:

ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

图解:

HashMap详解、与HashTable、ConcurrentHashMap的区别(面试干货)_第1张图片HashMap详解、与HashTable、ConcurrentHashMap的区别(面试干货)_第2张图片

 如图所示:如果以整个容器为一个资源进行锁定,那么就变为了串行操作。而根据hash表的特性,具有冲突的操作只会出现在同一槽位,而与其它槽位的操作互不影响

原理:多线程并发向同一个散列桶添加元素时若散列桶为空,则触发乐观锁机制,线程获取散列桶中的版本号,在添加元素之前判断线程中的版本号与桶中的版本号是否一致

HashTable和ConcurrentHashMap保证线程安全的方式:

1、HashTable是通过给整张散列表加锁的方式来保证线程安全的.这种方式可以保证线程安全,但是并发执行效率极其低下(同步),有一些可以并发执行的操作是得不到并发的,所以并发执行效率有可提升的空间。

2、ConcurrentHashMap采用锁分段技术,基本需要加锁的时候才加,不会影响别的操作,所以在        保证线程安全的前提下尽可能的保证了效率,对效率的影响不是很大

效率比较:HashMap(很强) > ConcurrentHashMap(仅次于) > HashTable(比较差)

线程安全:ConcurrentHashMap(安全)、HashTable(安全)、HashMap(不安全)

所以单线程场景推荐用HashMap,多线程场景推荐ConcurrentHashMap,HashTable基本淘汰。

你可能感兴趣的:(Java,数据结构,哈希算法,java,面试,spring,cloud)