Hash算法和HashMap hashmap和hashtable的区别 fail-fast概念

重点是这:hashMap:https://blog.csdn.net/u012512634/article/details/72

hash表:java中哈希表及其应用详解_xiaoxik的博客-CSDN博客

hash算法:关于Java的Hash算法的深入理解_千年道士的博客-CSDN博客_hash算法

HashMap:底层是用(Entry键值对)数组+链表实现的,用链地址法来解决冲突;

HashTable:底层是用Hash表实现的,Hash表底层也是数组+链表;解决冲突方法有开放定址法,再散列函数法,链地址法;

当容量不够的时候,会进行扩容(HashMap初始长度是16):

 HashMap的索引计算方法
HashMap通过put(key,value)来执行存储数据操作,通过get(key)来获取数据,而在这过程中存储的索引是非常重要的,它相当于一个坐标,有了它,就能保证我们在使用hashMap操作时的准确性。

假如调用hashMap.put(“money”,1000)方法,将会在HashMap的table数组中插入一个key是"money"的元素;这时需要通过hash()函数来确定该entry的具体插入位置,而hash()方法内部会调用hashCode()函数得到"money"的hashCode;然后putVal()方法经过一定计算得到最终的插入位置index,最终将这个entry插入到table的index位置。

key的hash值计算是通过hashCode的高16位异或低16位实现的:

h = (key.hashCode()) ^ (h>>16)

使用位运算代替了取模运算,在table的长度比较小的情况下,也能保证hashCode的高位参与到地址映射的计算当中,同时不会有太大的开销。

以下是hashmap索引过程:
在这里插入图片描述

 拿到hash值之后,最后通过 hashmap容器长度(初始长16)length, 计算hash & (length-1)的值得到下标index;

这个方法中h代表hashcode,length代表数组长度。我们发现它是用的逻辑与操作,那么问题就来了,逻辑与操作能准确的算出来一个数组下标?我们来算算,假设hashcode是01010101(二进制表示),length为00010000(16的二进制表示),那么h & (length-1)则为:

h:  0101 0101
15: 0000 1111
  &
    0000 0101


对于上面这个运行结果的取值方法我们来讨论一下:因为15的高四位都是0,低四位都是1,而与操作的逻辑是两个运算位都为1结果才为1,所以对于上面这个运算结果的高四位肯定都是0,而低四位和h的低四位是一样的,所以结果的取值范围就是h的低四位的一个取值范围:0000-1111,也就是0至15,所以这个结果是符合数组下标的取值范围的


那么假设length为17呢?那么h & (length-1)则为:

h:  0101 0101
16: 0001 0000
  &
    0001 0000


当length为17时,上面的运算的结果取值范围只有两个值,要么是0000 0000,要么是0001 000,这是不太好的
所以我们发现,如果我们想把HashCode转换为覆盖数组下标取值范围的下标,跟我们的length是非常相关的,length如果是16,那么减一之后就是15(0000 1111),正是这种高位都为0,低位都为1的二级制数才保证了可以对任意一个hashcode经过逻辑与操作后得到的结果是我们想要的数组下标。这就是为什么在真初始化HashMap的时候,对于数组的长度一定要是二次方数,二次方数和算数组下标是息息相关的,而这种位运算是要比取模更快的,用(length-1)保证下标index一定落在数组范围内。

装载因子α = 插入元素个数/哈希表中的数组个数,表示哈希表的装满程度,同时也能代表链表的平均长度

如图,如果在长度为5,装载因子为0.6的哈希表中插入3个数据,接下来再插入第4个数据的时候,就会进行rehash操作;

这里的数据计算是通过计算总的数据量,即后面链表中的数据也会计算进去;

HashMap扩容是2倍    arraylist扩容1.5倍

1.  当发生地址冲突的时候(不是容量不够的时候) 链地址法:

 以上是JDK1.7之前采用位桶+链表,缺点是:当冲突频繁发生时,查找时间复杂度变成了O(n);

java7及以前采用的是头插法插入节点,在扩容时会改变链表中元素的顺序,以及并发线程下链表成环问题;头插法链表成环问题见: HashMap头插法为什么会出现死循环 产生循环链表的影响是什么_littlehaes的博客-CSDN博客_头插法为什么会死循环

成环的流程图可以参考这个: HashMap并发时造成死循环问题解析 - 腾讯云开发者社区-腾讯云

   JDK1.8采用位桶+链表/红黑树,当一个格子内的数据长度超过了8,就会转化为红黑树,这样查找时间变成了O(logn);

java8之后采用尾插法插入节点,扩容将保持链表元素顺序,并解决了链表成环问题

根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。

hash 冲突除了链地址法, 有那些解决办法?

开放定址法、再哈希法。

2.开放地址法

线性探测法:如果键值对a在index中插入了,键值对b也需要插入index,那么b会去找相邻的位置(index+1,+2,+n...)进行存放;

缺点:键值对增多之后,可能会在连续的位置形成键值对,发生冲突需要很长时间才能探测到合适位置

平方探测法:如果键值对a在index中插入了,键值对b也需要插入index,b先在index+1^2位置看是否能存放,然后再到index+2^2,+3^2..,+n^2进行存放;好处是能解决线性探测的缺点;

缺点:对于落在同一index的键值对,后面重试index一直都一样,越到后面就越需要很久探测到合适位置;

双散列:首先可以构造一个虚拟的键值对c的散列坐标indexFake,b如果在index位置上冲突了,那么后面可能移动到的位置是index+indexFake,+2*indexFake..+n*indexFake

3.再hash法

即重新hash一次,算出index值;

Q: 如果两个键的hashcode相同,你如何获取值对象?
重点在于理解hashCode()与equals()。
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或红黑树中去查找对应的节点。

重写equals方法需同时重写hashCode方法

hashMap的源码:

举个例子说明为什么hashcode要重写: 往一个HashMap中放一个自定义数据结构的对象,以这个对象作为key值,put存入value;但是由于下一次get是通过hashcode来计算index值,那put和get拿不到同一个对象了;

HashCode是先用来进行存储数据用的,由key值算出hashcode作为下角标,存入value;

equal是用来get()时找数据用的,先使用hashCode()计算index, 然后通过value值和链表中的每一个值进行比较equal,相等就取出;

所以equal在自定义数据中是一定要重写的,hashcode是有必要重写的;

1、这里利用key的hashcode方法和equals方法,所以在使用HashMap的时候,如果使用对象作为key,最好覆写key的hashcode和equals方法
不然可能出put到HashMap的时候,成功了,但是get的时候却没有找到数据
2、如果key hash冲突太多,会造成链表过长,在链表中查找元素的时候,会比较

 HashMap和HashTable比较:

不同:

1.HashMap支持null值和null键的,而HashTable则会抛出空指针。原因是类内部做了处理。

2.HashMap不是线程安全的,而HashTable是线程安全的。因为HashTable中的方法的是用Synchronize同步过的

 如果HashMap想进行同步的话,可以用ConcurrentHashMap,采用了锁分段技术,不同的竞争资源采用不同的锁:

相较于hashtable和用了Synchronize同步的hashmap,比如一个线程再进行putall写入大量数据,期间调用线程B去get,那么线程B就会阻塞,唯一的好处就是get的时候能获取到完整更新后的全部数据

ConcurrentHashMap:一个ConcurrentHashMap包含了一个Segment数组,维护了一个HashEntry数组,每次读取数组中的数据的时候都需要获取segment对应的锁;java8之后采用cas+sychronize来代替锁分段实现;

在Java 8中,整个哈希表被分成若干个段,每个段都被实现为一个数组,每个数组元素都是一个链表或红黑树。每个元素都是一个桶,通过哈希函数将键值对映射到桶中。每个桶内部都通过synchronized来实现同步,以保证线程安全性而对于读操作,使用了无锁的CAS操作。 相比于分段锁,这种实现方式有以下优点:
减少锁竞争:由于每个桶内部采用了synchronized来实现同步,不同的线程可以同时访问不同的桶,从而减少了锁竞争。
更高的并发度:由于不再受限于固定数量的段,ConcurrentHashMap 可以根据需要动态调整大小,并支持更高的并发度。
更好的扩展性:由于不再需要维护多个段的锁,因此在扩展时可以更容易地添加或删除桶,而不需要重构整个数据结构。
更好的性能:使用CAS操作替代了分段锁,避免了分段锁中的自旋等待开销,提高了并发性能。

3.初始化的hashMap长度是16,而hashTable初始化长度是11;

相同:HashTable除了和HashMap有上述不同,其他基本相同,比如都用哈希表来Entry对象(含键值的对象)

fail-fast:指的就是多个线程在arraylist等非线程安全的集合中进行增删等操作时,造成的异常;

HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?
  1. 对key的hashCode做hash操作(高16bit不变,低16bit和高16bit做了一个异或);
  2. h & (length-1); //通过位操作得到下标index。

  还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。?

Hash函数的其他构造方法(算hashcode):

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值