hashMap的理解

hashMap的理解

  • 一、HashMap简介
      • put原理
      • get原理
  • 二、哈希碰撞
  • 三、与HashTable的区别
      • 1. HashTable介绍
      • 2. 区别
  • 最后、常见问题
      • 一、HashMap中的“死锁”是怎么回事

一、HashMap简介

  1. HashMap是基于哈希表实现的,每一个元素是一个key-value对,无序,不可重复。
  2. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap
  3. HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
  4. hashmap集合的默认初始化容量为16(2<<3),默认加载因子为0.75,也就是说这个默认加载因子是当hashMap集合底层数组的容量达到75%时,数组就开始扩容。hashmap集合初始化容量是2的陪数,为了达到散列均匀,提高hashmap集合的存取效率。
  5. 无论我们指定的容量为多少,构造方法都会将实际容量设为不小于指定容量的2的次方的一个数,且最大值不能超过2的30次方
  6. HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
  7. JDK1.8之后当链表的长度达到 “8” 时,内部会调用 “treeifyBin” 方法,判断数组长度是否达到 “64” 达到就会自动变成红黑树的结构,达不到会调用扩容机制,当红黑树节点的个数达到 “6” 之后,又会变成链表结构。
    hashMap的理解_第1张图片

put原理

第一步首先将k,v封装到Node对象当中(节点)。
第二步它的底层会调用K的hashCode()方法得出hash值。
第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

因此,放在hashMap集合key部分的元素如果是自定义对象的话都需要重写hashCode和equal方法。

get原理

第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

二、哈希碰撞

在hashMap添加元素的时候会计算添加元素的hash值,通过hash值确定放在哪个hash桶中,当不同的元素计算出相同的hash桶的时候,这样就发生了哈希碰撞

哈希碰撞有两个解决办法:

  1. 开放地址发:
    当冲突发生时,使用某种探查技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的地址。
    开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1)
    其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。
    如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。
    如果di取值可能为伪随机数列。称伪随机探测再散列。
  2. 链地址法(拉链法)hashMap使用的就这这种方法:
    将相同hash值的对象组织成一个链表放在hash值对应的槽位;
  3. 再哈希法:
    当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
    比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止
  4. 建立一个公共溢出区:
    假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,另外设立存储空间向量OverTable[0…v]用以存储发生冲突的记录。

三、与HashTable的区别

1. HashTable介绍

Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。
Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。

2. 区别

  1. hashMap是线程不安全的,hashTable是线程安全的。
  2. 继承的父类不同:
    Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
  3. hashMap可以添加put(null,null),而hashTable不行(编译不报错,运行报错)
  4. 取hash值不同:
    hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。
    Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值。
    HashMap在求位置索引时,是计算key的hash再与tab.length-1进行与运算;HashTable则是key的hash值与0x7FFFFFFF进行与运算,然后再对tab.length取模
  5. 初始容量和扩容机制不同
    hashMap的初始容量是16,hashTable的初始容量是11;
    hashMap的扩容是原来的2倍,hashTable的扩容是原来的2倍+1

最后、常见问题

一、HashMap中的“死锁”是怎么回事

HashMap是非线程安全,死锁一般都是产生于并发情况下。我们假设有二个进程T1、T2,HashMap容量为2,T1线程放入key A、B、C、D、E。在T1线程中A、B、C Hash值相同,于是形成一个链接,假设为A->C->B,而D、E Hash值不同,于是容量不足,需要新建一个更大尺寸的hash表,然后把数据从老的Hash表中 迁移到新的Hash表中(refresh)。这时T2进程闯进来了,T1暂时挂起,T2进程也准备放入新的key,这时也 发现容量不足,也refresh一把。refresh之后原来的链表结构假设为C->A,之后T1进程继续执行,链接结构 为A->C,这时就形成A.next=B,B.next=A的环形链表。一旦取值进入这个环形链表就会陷入死循环。

解决办法:
使用线程安全的ConcurrentHashMap

你可能感兴趣的:(Java基础,java,hashmap,链表)