HashMap总结

HashMap概述

基于map实现,hashmap是由数组+链表组成的,数组是hashmap的主体,链表是为了解决hash冲突存在的。如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

hash冲突解决方案(常见的四种)

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)同步​​​​​​​​​​
    1. 开放定址解决冲突的散列存储程序
      1. 就是分离链接法:分离链接法是有一些不足的,它使用了一些链表,给新单元分配地址是需要时间的,这就导致算法的速度有些慢,同时这种方法实际上还要求对第二种数据结构的实现。
      2. 开放地址法:而开放地址法不使用链表。开放定址法的思想是当发生冲突时,在表内尝试一些另外的单元,直到找到空单元为止。用这种方式产生的表,所有的元素都在表内,所以对于相同规模的元素,开放定址法所需要散列表要比分离链接法大。我们把这样的表叫做探测散列表。
  2. 在哈希法
    1. 就是使用哈希函数去散列一个输入的时候,输出是同一个位置就再次哈希,直至不发生冲突位置(缺点:每次冲突都要重新哈希,计算时间增加。)
  3. 链地址法
    1. Hash上的每个节点都有一个next指针,多个指针就构成了单向链表
    2. 其实,Java中的HashMap采用的hash冲突解决方案就是单独链表法,也就是在hash表节点使用链表存储hash值相同的值

      不过需要知道的是JDK8之后,如果链表长度超过8将会将链表转化为红黑树以便提高在hash冲突严重情况下的查询效率,也能够避免一定的hash碰撞攻击。 

  4. 建立一个公共溢出区

HashMap是线程不安全的吗

死循环发生在HashMap的扩容函数中
在jdk1.7中 在多线程环境下 扩容是会造成环形链或数据丢失           扩容函数 死循环
在jdk1.8中  在多线程环境下  会发生数据覆盖的情况          链表慢  红黑树快  增加了平衡性
有6个元素,则HashMap的初始化容量应为(6/0.75 +1=9)即new HashMap(9),实际容量为比9大的最近的2的指数即16
当存在多个线程同时写入hahsmap时,可能会导致数据的不一致

Hashmap 与 ConcurrentHashMap

hashmap 本质是数组+链表   根据key取得hash值  然后 计算出数组下标   如果多个key对应到同一个下标 就用链表串起来   新插入的在前面

ConcurrentHashMap 在hashmap的基础上 ConcurrentHashMap 将数据分为多个segment 默认16个  然后每次操作对一个segment 加锁  避免多线程锁的几率 提高并发效率
HashMap的键值对允许有null,但是ConCurrentHashMap 都不允许。
ConcurrentHashMap 是线程安全  hashmap 不安全
jdk1.7 ConcurrentHashMap 是由segment 数组和 hashentry 数组结构组成
JDK1.8 摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 Synchronized 和 CAS 来操作,整个看起来就像是优化过且线程安全的 HashMap

HashMap 的底层原理

1.7 数组+链表    无冲突放数组,有冲突,放链表     头插法 
1.8 数组+链表+红黑树    无冲突放数组,有冲突,链表长度<8 放入链表 >8 红黑树  尾插法

链表特点:一个节点连着一个 查询时挨个查 如同火车车厢 方便插入 
在jdk1.8之前hashmap的实现是数组+链表,就算hash函数取的再好,依旧很难达到元素百分百均匀分布。
当hashmap中有大量的元素都存放在同一个桶时,这个桶下就有一个链表,这时hashmap就相当于是一个单项链表,遍历的时间复杂度提高O(n),失去了他的优势
针对于这种情况jdk1.8就引入了红黑树优化这个问题,加入红黑树后查询时间复杂度为O(log(n))

为什么到8的时候转红黑树?
涉及算法和概率
为了配合使用分布良好的hashcode,树节点很少使用。受随机分布的影响,链表中的节点遵循泊松分布,但是性能并不是很理想。
所以在这种比较罕见极端的情况下,就会把链表转为红黑树来提高性能,在8的时候偏差已经很小,不需要再改进

HashMap中put和get
当使用HashMap的put方法的时候,有两个问题要解决:
1、长度为16的数组中,元素存储在哪个位置 
第一个问题,HashMap 是使用下面的算法来计算元素的存放位置的。
2、如果key出现hash冲突,如何解决第二个问题 
则利用Entry类的next变量来实现链表,把最新的元素放到链表头,旧的数据则被最新的元素的next变量引用着
Get操作只需要根据key的hashcode算出元素在数组中的下标,之后遍历Entry对象链表,直到找到元素为止。
Put:hashmap使用put进行存入数据时,会使用hashcode计算出元素所在的地址位置。如果当我们插入数据时发现这个地址已经有值,那就把里面的元素作为新元素的指针内容,放入下一个节点中  把值 存在原先值的上面
Get:hashmap通过get获取元素的时候,通过key的hashcode值算出数组中的下标,遍历得到数据,直到找到元素为止
hashMap扩容
1.7 中整个扩容过程就是一个取出数组元素(实际数组索引位置上的每个元素是每个独立单向链表的头部,也就是发生 Hash 冲突后最后放入的冲突元素)然后遍历以该元素为头的单向链表元素,依据每个被遍历元素的 hash 值计算其在新数组中的下标然后进行交换(即原来 hash 冲突的单向链表尾部变成了扩容后单向链表的头部)。
而在 JDK 1.8 中,由于扩容数组的长度是 2 倍关系,所以对于假设初始 tableSize =4 要扩容到 8 来说就是 0100 到 1000 的变化(左移一位就是 2 倍),在扩容中只用判断原来的 hash 值与左移动的一位按位与操作是 0 或 1 就行,0 的话索引就不变,1 的话索引变成原索引加上扩容前数组,
HashMap和HashTable 区别
null值:
hashmap:可以key有一条null,value可以多条null  key为空时存放下标为0
hashtable:不允许为null  key或value为空报空指针异常
线程安全
hashmap:不安全
hashtable:安全(其中有线程锁保证线程安全,同时因为这个问题导致效率不如hashmap)
默认值和扩容
hashmap:16  每次扩大两倍
hashtable:11  每次扩大两倍+1
哈希值
hashmap:重新计算hash值
hashtable:直接使用hashcode
因为hashtable有锁所以效率不如hashmap,hashmap有一点就是不可以上锁,不是特别安全,但是我也有研究过,map中有一个 ConcurrentHashMap是安全的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值