HashMap和ConcurrentHashMap的原理

HashMap

  1. HashMap里面一个数组,数组中每个元素是一个单向链表,链表由一个个Entry构成,每个Entry实例包含四个属性:key,value,hash值和用于单向链表的next。
  2. 可以存储Null键和Null值,线程不安全
  3. 无序

有序的Map:TreeMap, LinkedHashMap,

TreeMap 是通过实现 SortMap 接口,能够把它保存的键值对根据 key 排序,基于红黑树,从而保证 TreeMap 中所有键值对处于有序状态。

LinkedHashMap 则是通过插入排序(就是你 put 的时候的顺序是什么,取出来的时候就是什么样子)和访问排序(改变排序把访问过的放到底部)让键值有序。

put:

  • 判断当前数组是否需要初始化;
  • 如果Key为空,则put空值;
  • 根据key计算hashcode定位出所在桶(数组下标)
  • 如果桶是链表,判重,如果没有重复,则放到链表的表头
  • 如果桶是空,新增一个entry对象写入当前位置
  • 插入新值时需要判断是否需要扩容,扩容后,数组大小为原来的 2 倍。

    扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值迁移到新的数组中。

    双倍扩容

get:

根据Key计算hash值,定位数据下标

如果是链表,遍历链表,直到Key和hashcode相等,就返回值

else return null

线程不安全:

hash碰撞:如果多个线程同时操作HashMap,进行Put,而有多个Key的hash值相同。这个时候就需要通过链表解决Hash冲突,如果两个线程恰好都取道了对应位置的插入节点,一定会有一个数据丢失;

扩容:多个线程同时检测到总数量超过门限值的时候就会同时调用resize,各自生成新的数组并且rehash后赋值给map底层的数组table,结果最终只有最后一个线程生成的新数组被赋予table变量。

resize扩容 transfer出现并发问题

Java7---Java8

Treeify-threshold:判断是否需要将链表转为红黑树的阈值;(Hash冲突后,链表会过长)

HashEntry-〉Node

二叉查找树的缺陷:二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成层次很深的问题),遍历查找会非常慢。

而红黑树在插入新数据后可能需要通过左旋、右旋、变色这些操作来保持平衡。

引入红黑树就是为了查找数据快,解决链表查询深度的问题。

我们知道红黑树属于平衡二叉树,为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少。

所以当长度大于8的时候,会使用红黑树;如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

 

红黑树:

  1. 每个节点非红即黑
  2. 根节点总是黑色的
  3. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
  4. 每个叶子节点都是黑色的空节点(NIL节点)
  5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

 

 

ConcurrentHashMap

  1. 底层是数组加链表,线程安全
  2. 通过把整个Map分成N个Segment

使用volatile 关键字

posted on 2018-10-29 10:47  samuel1  阅读(193)  评论(0)    收藏  举报

导航