HashMap和ConCurrentHashMap各自的相关特性和区别(浅谈)

来源:互联网 发布:仙界网络直播间无弹窗 编辑:程序博客网 时间:2024/05/21 19:50

浅谈一下HashMap和conCurrentHashMap的区别和各自的相关特性。主要是为了自己复习一下这个集合。打好基础。



首先。HashMap。众所周知,他是键值对<K , V>集合框架。

注意一点的是:hashMap是可以使用null作为键(K)和值(V)的。


hashMap的底层本质上是数组和链表。此话怎么说呢?

首先我们得了解hashMap是怎么存储数据的。”hashMap“,那么自然跟哈希离不开关系了。

我们先取键(K)的hash值。然后根据hash值计算得出它相应的存储位置。所以说。HashMap的查询效率也是相当高的。

那么既然说到存储位置,就不得不提HashMap中两个非常重要的参数:

static final int DEFAULT_INITIAL_CAPACITY     //默认桶数组长度
static final float DEFAULT_LOAD_FACTOR        //默认负载因子的大小
看到上边两行参数。可能有些朋友就疑问了,桶数组是什么东西,为什么叫”桶“。看来程序员的审美也不咋地。好吧。开个玩笑,我开头说了,HashMap集合的本质是数组和链表,这个数组指的就是桶数组。至于他为什么叫桶数组,我们就不要纠结这个问题了。


桶数组的默认大小是16,负载因子的默认大小是0.75f。

在我还没有了解HashMap之前,当然,现在我同样不算很了解它。

新手总是喜欢这么使用HashMap:

                               Map< K , V > map = new HashMap< K , V >()   //创建一个map对象。

这也没错。但是HashMap它可是提供了以下三种构造方法设置参数来创建HashMap的。

(1)new HashMap(int capacity)  //设置桶数组大小
(2)new HashMap(int capacity,int 
LOAD_FACTOR) //设置桶数组大小,负载因子
    
(3)new HashMap()  //默认


所以说,在实际开发的时候,要根据具体业务具体需求设置合适的桶数组大小和负载因子。这是为了防止在项目实际上线运行中,HashMap多次rehash带来的负担。

讲到这里,那么有人会问了,rehash是什么?

在这里若想理解rehash是什么。

首先,桶数组大小默认为16,举个例子。现在有一栋贸易办公大厦,这栋大厦的一楼处有16个电梯,并且假设电梯内的空间无限大(这栋大楼真有钱,啧啧...)。这16个楼梯分别只能上不同的具有不同股票代码(这里的股票代码暂且比拟做hashCode值)的公司。

那么现在有20人来到这准备搭电梯去上班,假设A白领去A电梯,他通过他自身的公司证件号(这里的公司证件号可以比拟为通过键去到的hashCode值)正确验证并定位到A电梯,那么他就成功进入A电梯了。

B白领这时候通过他的证件号也是定位到了A电梯,然后怎么办呢?我刚开始说了,HahMap的底层本质是数组和链表,所以说,B直接就进入A电梯中,在A电梯里面,B白领排到A白领的后面。

这样比喻,大概了解了吧。16个电梯就是16个数组,数组里面,是个链表。


这时候,我要提出一个新问题了。假设现在这栋贸易大厦上某个公司分裂了。变成两个公司了。并且赌气说以后不一起搭同一个电梯上班。那么这时候,是不是16个电梯已经满足不了需求了。依照我们编程的经验,既然16个不够,那就扩大到17个呗。     

其实,说到这里,大家不知道还记得没有,我在一开始的时候说HashMap有两个很重要的参数,可是写到现在我只讲了桶数组大小。。对。还有一个负载因子load_factor。它的默认值是0.75f。它的作用又是什么呢?

它就像是一个"预警器"。此话怎讲。还是讲回刚才那个例子。作为这栋贸易大厦的管理者。他不可能等到16个都满了才想到要去增加。那时候增加的话建造电梯还要时间,但是需求就是现在要。所以说,这个0.75f就是预警器。16*0.75=12。也就是说。当12个数组都有数据填入了。那么这个hashMap就知道了,桶数组大小可能不够用了。要扩容了。所以说。不会等到16个数组都填了才扩容。

hashMap的扩容和许多java对象一样,翻倍增加。也就是说第一次扩容。会从16增加到32,第二次64,...


既然扩容了,假设前面放入的12个数据都依次放在了不同的数组中,则在第13个放入之前,桶数组增大变为32个,数据通过hashCode定位的信息全部失效。之前放入的12个数据全都要重新计算后再定位存储。这个过程就叫做rehash。

说白了,如果说要放入hashMap中的数据量少的话,还能接受,若是说,放入hashMap中的值是上百万个键值对呢。那么在这个过程中,他就要无数次扩容、rehash、扩容、rehash、扩容、rehash。这对于服务器来说负担是巨大的。所以说,在创建HashMap时根据具体业务需求指定适合的桶数组大小和负载因子大小是多么重要。

--------------------------------------优雅的分割线------------------------------------------



同时。HashMap又是一个无序的集合。如果想要使用这种键值对的结构存储数据,又想要有序。可以使用LinkedHashMap。你们可以查一下API文档看看。


----------------------------------又是一道分割线------------------------------------------------


再者,HashMap是线程不安全的。及如果设计到写操作时,会很容易发生数据丢失的情况。这一点要格外注意。那么如果我们在这种背景下。还想要使用这种键值对的结构,但是又想要线程安全这种两全其美的办法有没有呢?


办法是有的。这里有三种解决方案。

(1)Map map = Collections.synchronizedMap(new HashMap( ));

(2)Map map = new HashTable( );

(3)Map map = new ConCurrentHashMap( );

第一种无非就是额外给HashMap加个锁就是了。

第二种的话。是在其底层就已经有写入了锁这个数据结构了。但是,遗憾的是,HashTable是只有一把锁,所以说它的效率非常低下。已经被淘汰。

第三种就是目前来说,主流的一种线程安全的Map。它和HashTable的区别就是。ConCurrentHashMap具有很多把锁。我们称它为分段锁。它的底层有很多个类对象,叫做Segment。这个类对象是什么呢?我们可以理解为它相当于HashTable,每个Segment中都有一把锁。当一个线程访问其其中一个Segment时,其他线程可以访问ConCurrentHashMap中的其他Segment,且不会受影响,彼此之间互相独立。ConCurrentHashMap和HashTable的区别就在于细粒度的控制上。



笔记完毕。这是我一点浅显的了解。若是哪里说得不对,或者说得不好的地方。大家谅解。敬请大家一起讨论交流。

阅读全文
0 0
原创粉丝点击