HashMap详解

1.map接口和List接口的关系

 

两者没有关系,都是集合,Map接口没有父类,而List的接口关系在上一篇博客中有介绍,

2.Map有哪些常用的实现类

  HashMap:底层是数组+链表+红黑树,默认 初始容量是16、扩容因子为0.75,每次采用2 倍的扩容。

  HashTable:线程安全,遗留类,不建议使用。

  ConcurrentHashMap:现阶段使用使用比较 多的一种线程安全的Map实现类。在1.7以前 使用的分段锁机制实现的线程安全的。1.8以 后使用synchronized关键字实现的线程安全

 

1.HashMap的put过程

16*0.75 达到 12个之后 map数组就开始扩容;

当进行put的时候,节点存放的是key和value的形式,先根据key计算hash值放在HashMap中的哪一个位置,如果存在hash冲突的话,则会生成链表的结构当每个“桶”的位置的数据达到了数量8的时候则生成红黑树结构(红黑树 结构 特点就是节点除了黑色就是红色节点红黑树的节点所有的左侧子节点都要比根节点值小,所有的右侧节点都要比根节点值大一些),当执行删除操作时,数量减少到数量6以下的时候会进行"链化"操作转化成链表数据结构;

6和8之间的中间差值:中间的差值是为了减少再某些场景使用HashMap的时候频繁进行树化链化的频率,等到"桶"中的数值达到了8的时候进行"树化"如果树化之后的数据结构的数据被删除了,到了7之后不会进行链化,到了6之后才会再新的线程下进行"链化 ";)

上图所示的左侧竖形链表数据的的插入顺序:1.7版本之前是从“头部”插入,1.8版本之后从“尾部”最后插入数据

hash(key)是如何实现的?

 根据key得到一个桶的位置、得到桶的位置必须分散---
1、有一个int值(0-15之间未扩容之前,扩容按照2倍进行扩容)
hash(key) 底层使用的是位运算完成 :

     如下:
          int hashCode = key.hashCode()  -- int 类型的数据在内存中2^16次方
         16-1 = 15 ------------------01111     初始 的map数组大小
         key.hashCode()  --------0101010101XXXXX 一个32位的数据 
  key.hashCode()&(n*15-1)   ---------   hash(key)方法的核心算法 (初步版本) 
  但是上面的方法不够离散原因:
        int类型的最大值是2^16-1的,然后参与位运算的只能是hashCode后半部份,前半部分不参与运算,所以为了尽可能离散 所以:

      将hashCode的前16位数字和低16数数字进行了一次异或(如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。 )的运算,这样的结果就和前16位数字和后16位数字都有关系,将得到的新的二进制数字hash(key)                             hash(key)=key.hashCode()高16位^低16位&(n*16-1)    (最终版)
  


为什么2倍扩容,如何解决hash冲突? 

下方这个公式的(n*16+16 -1)n表示扩容倍数  的值只有是 15,31,63...
二进制之后全部都是1,那么进行操作的时候才可以进行分散匹配,否则容易造成hash冲突,所以需要2倍扩容,并且需要有科学的hash(key)的计算方式

hash(key)=key.hashCode()高16位^低16位&(n*16-1)    (最终版)说明:

在进行put的操作时 放的是hash(key)值的操作,右侧图片的代码表示将key.hashCode()的值进行位运算 将二进制数向右平移16位置然后进行异或操作

 


 

HashMap是如何扩容的??是如何初始化的?
   第一次初始化的时候执行resize方法,进入方法之后执行红框中的内容,然后直接返回newTab初始化完成,当map中的元素达到了大小*0.75个时,将开始扩容,扩容使用2倍开始扩容。
 

 


扩容以后,数据元素如何分配的?
    
    扩容之后 数组需要重新排序,重新排序后的数据只能在原下标位置 或者 新下标(新下标位置 = 原下表+原容量)
    相当于平移 


jdk1.7和jdk1.8对HashMap的比较
  1.底层数据结构不相同
     前者是没有引入红黑树的概念,后者引入了红黑树的概念  
  2.链表的插入方式不同
     前者的插入数据的方式是从头部的链条插入,后者的插入数据的方式是从尾部的链条插入
  3.hash(key)计算方式不同
      jdk1.8和jdk1.7对于hash(key)d的实现是不同的:
      1.8 、2次扰动 = key.hashCode()高16位^低16位&(n*16-1) n为大于0的整数
      1.7 、9次扰动 = 4次位运算+5次异或运算 
4.扩容后数据存储位置的计算方式也不相同
      前者是吧所有的数据打散,然后重新根据hash(key)进行计算,
       后者是根据两层循环  数据只能在原下标位置 或者 新下标(新下标位置 = 原下表+原容量)


扩展:二进制和十进制之间的相互转换:

十进制的168转为二进制 
  168%2 = 84----0
  84%2  = 42----0
  42%2  = 21----0
  21%2  = 10----1
  10%2  = 5 ----0
  5%2   = 2 ----1
  2%2   = 1 ----0
  1%2   = 0 ----1 
  当商为0的时候开始从下往上进行读数:10101000
  二级制 10101000 转换为十进制
  方法:按权相加法,即将二进制每位上的数乘以权,然后相加之和即是十进制数。例
将二进制数10101000转换为十进制数(从左往右数 
2^7*1 + 
2^6*0 +
2^5*1 +
2^4*0 +
2^3*1 +
2^2*0 +
2^1*0 +
2^0*0 = 128+0+32+0+8+0+0+0=168  

HashMap和HashTable的取别

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值