jdk各版本的hashmap和ConcurrentHashMap

hashmap从jdk1.2开始加入,从经典的1.6开始吧。


hashmap的底层是数组,容量始终是2的倍数2^n,初始容量16,自动扩容*2。数组中每个元素作为一个哈希桶,什么是哈希桶?不同的key可能会给出相同的哈希,如果key的hashcode算法足够好,那么哈希碰撞的几率就很小。但是存在哈希相同的不同key,所以哈希桶用来保存hash相同的key和value的键值对entry,entry会有多个,这些相同的entry以链表的形式创建,后添加的entry会放在链表的头部,因此,hashmap也就是数组+链表。


entry所在的链表的数组的下标是根据key的hashcode再hash,然后跟数组lengh-1异或(&)确定的,这样可以将下标控制在length范围之内。


如果key的hashcode太差,给出很多相同的hash,那么entry就会集中在几个链表中,数据的存取就会频繁的遍历链表,性能大打则扣。所以hashcode尽可能更好的计算,那么链表出现就会很少或者较短,那么就近似于对数组进行随机操作,性能是很好的。


hashmap每当添加新的entry之后都会判断数组元素个数size,如果超过了容量capacity的阈值threshold,则进行扩容resize,这个阈值就是负载因子load factor*capacity所得,load factor默认是0.75,增大则减少数组占用的空间,但是查询性能会下降,减少则提高了查询性能牺牲空间。


hashmap是对外线程不安全的,尤其是多个线程对其进行put操作的时候,容易出现死循环。应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:


   Map m = Collections.synchronizedMap(new HashMap(...));


这一办法其实是给get put等方法加了synchronized,性能还是比较差的。


ConcurrentHashMap,这个就是在java.util.concurrent一个常用的并发包中的hashmap,它提供了跟hashmap一样的规范,不同的是:


1、对其的更新操作是同步的,其原理就是对hashmap的数组进行分区,对各个区添加锁,从而实现不同区的操作不受影响,同区的操作同步。

2、但是其get方法是弱一致性的,也就是会收到其他操作的影响。

3、通过可选的 concurrencyLevel 构造方法参数(默认值为 16)来设置分区的值,尽可能设置一个能多容纳并发线程的值,避免线程竞争,也不要设置过大造成时空上的浪费。

4、不允许key或value为null值


不管是何种hashmap,尽可能在创建时就给定一个容量大小或分区的期望值,避免hashmap的扩容或其他调整操作,因为那都是较慢的操作。另外,你的key尽可能实现Comparable接口,这样hashmap的链表就可以按顺序插入,当出现较多的哈希碰撞,可以很大程度上减轻负担。


1.7

并无重大更新


1.8

hashmap有了重大更新,其内部实现采用了红黑树,entry链表长度超过阈值8,就会转为树结构,性能有了较大提升。

ConcurrentHashMap同样进行了巨大更新,放弃使用之前的分区锁,而是使用CAS原子操作来提供修改树节点的原子操作,其锁的粒度实际是节点,故性能比以前有了不少的提升。和hashmap一样采用树结构,但是树的根节点是不一样的,也就是数组节点不一样。

你可能感兴趣的:(JavaSE)