HashMap和HashTable

HashMap

hashmap的数据结构
在这里插入图片描述
HashMap的本质是一个数组,数组的每个索引被称为桶,每个桶里放着一个单链表 (jdl1.6,1.7中hashmap由位桶+链表实现;jdk1.8以后HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树)

Java中HashMap底层实现原理(JDK1.8)源码分析:https://blog.csdn.net/tuke_tuke/article/details/51588156

Jdk1.8(hashmap不再由唯一的数组+链表组成,还可以以红黑树的形式存在)
在这里插入图片描述

通过key的hashcode计算得到bucketIndex来确认插入的位置,在每个位置有一个链表,如果链表还没有元素,则直接插入,如果有,则发生碰撞,用equal来比较key是否存在,如果存在则更新,不存在则在链表末尾插入,并把插入前的最后那个元素的next指向新插入的元素。
如果HashMap里的元素大于等于容量*加载因子,则会进行扩容,扩容就会产生rehash,rehash重新计算所有元素的位置,重新整理HashMap的元素的位置

Java 集合深入理解(17):HashMap 在 JDK 1.8 后新增的红黑树结构:https://blog.csdn.net/u011240877/article/details/53358305
重温数据结构:深入理解红黑树 https://blog.csdn.net/u011240877/article/details/53329023

hashmap 的成员变量在这里插入图片描述

size:记录了Map中KV对的个数(而不是桶的个数)
loadFactor:负载因子,用来衡量HashMap满的程度(0.75)
threshold:cap*loadFcctor
DEFAULT_LOAD_FACTOR:0.75
DEFAULT_INITIAL_CAPACITY:16(默认数组的长度,即桶的个数)

全网把Map中的hash()分析的最透彻的文章,别无二家。http://www.hollischuang.com/archives/2091

hashmap中链表的形势下的put,get

hashMap的put和get实现方法分析https://blog.csdn.net/xp2234/article/details/80119076

1.hashmap的容量为什么是2的幂

HashMap中的数据结构是数组+单链表的组合,我们希望的是元素存放的更均匀,最理想的效果是,Entry数组中每个位置都只有一个元素,这样,查询的时候效率最高,不需要遍历单链表,也不需要通过equals去比较K,而且空间利用率最大。那如何计算才会分布最均匀呢?我们首先想到的就是%运算,哈希值%容量=bucketIndex,SUN的大师们是否也是如此做的呢?

 1.8以前的
    static int indexFor(int h, int length) {  
        return h & (length-1);  
    }  

h是通过key的hashCode最终计算出来的哈希值,并不是hashCode本身,而是在hashCode之上又经过一层运算的hash值,length是目前容量。
当容量一定是2^n时,h& (length - 1) == h % length,它俩是等价不等效的,位运算效率非常高,实际开发中,很多的数值运算以及逻辑判断都可以转换成位运算,但是位运算通常是难以理解的,因为其本身就是给电脑运算的,运算的是二进制,而不是给人类运算的,人类运算的是十进制,这也是位运算在普遍的开发者中间不太流行的原因(门槛太高)。(CPU处理除法和取余运算是最慢的)
以16为例:16=10000;15=1111
根据&位运算的规则,都为1(真)时,才为1,那0≤运算后的结果≤15,假设h <= 15,那么运算后的结果就是h本身,h >15,运算后的结果就是最后三位二进制做&运算后的值,最终,就是%运算后的余数

2.初始容量为什么是16

16是个折中的大小。如果桶初始化桶数组设置太大,就会浪费内存空间;太小则频繁扩容

3.hashmap为什么要扩容

因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,因为链表的长度很大(当然最新版本使用了红黑树后会改进很多),扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率

4.hashmap中负载因子为什么是0.75

0.75是 空间与时间的折中
设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,设置过小也不合适,如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了。
超过域值后会扩容并rehash
在这里插入图片描述
一个bucket空和非空的概率为0.5,通过牛顿二项式等数学计算,得到这个loadfactor的值为log(2),约等于0.693. 同回答者所说,可能小于0.75 大于等于log(2)的factor都能提供更好的性能,0.75这个数说不定是 pulled out of a hat。

HashMap的loadFactor为什么是0.75?:https://www.jianshu.com/p/64f6de3ffcc1

5.jdk8中,为什么域值为8的时候转红黑树。

根据源码中的注释以及给出的数据
Ideally, under random hashCodes, the frequency of
nodes in bins follows a Poisson distribution
(http://en.wikipedia.org/wiki/Poisson_distribution) with a
parameter of about 0.5 on average for the default resizing
threshold of 0.75, although with a large variance because of
resizing granularity. Ignoring variance, the expected
occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
factorial(k)). The first values are:

0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006
more: less than 1 in ten million
在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,同时给出了桶中元素个数和概率的对照表。 (桶中元素,针对的是每个桶,所以这个泊松分布是针对的转树操作)
从上面的表中可以看到当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。

6.HashMap::get() 可以获取 bucket 里面的第一个元素,那其它元素又怎么获取?

get 是取得和你 的key 匹配的那个 value。bucket 链表里面存储的是 hash 值相同的 (key,value) 对,比如 key1 和 key2 计算得到的 hash 值相同,那么就把他们扔到一个bucket里面,当你 get(key1) 时,那么遍历对应的 bucket 链表,直到找到对应的 key1,返回 value1.

两者区别

主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。
要注意的一些重要术语:

  1. sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
  2. Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。
  3. 结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值