参考文章:
- hashmap的hash算法:https://www.cnblogs.com/eycuii/p/12015283.html
- hashmap和hashtable的区别:https://www.zhihu.com/question/20581065/answer/334969310
- hashmap源码:https://zhuanlan.zhihu.com/p/79219960
- 负载因子:https://zhuanlan.zhihu.com/p/103449454
HashMap简介
- 特点
-
初始大小为16,每次扩容以2的指数增长
-
HashMap是基于哈希表来实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足时,同样会自动增长。
-
HashMap是非线程安全的,只能用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrenHashMap。
-
HashMap实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
-
存储过程原理:
内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表,当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,任何通过indexfor(hash,length)求该key-value的存储位置,计算方法时先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入hashmap中。
- 扩容后hash值
第三部分同样也很复杂,就是把旧数据复制到新数组里面。这里面需要注意的有下面几种情况:
A:扩容后,若hash值新增参与运算的位=0,那么元素在扩容后的位置=原始位置
B:扩容后,若hash值新增参与运算的位=1,那么元素在扩容后的位置=原始位置+扩容后的旧位置。
hash值新增参与运算的位是什么呢?我们把hash值转变成二进制数字,新增参与运算的位就是倒数第五位。
这里面有一个非常好的设计理念,扩容后长度为原hash表的2倍,于是把hash表分为两半,分为低位和高位,如果能把原链表的键值对, 一半放在低位,一半放在高位,而且是通过e.hash & oldCap == 0来判断,这个判断有什么优点呢?
举个例子:n = 16,二进制为10000,第5位为1,e.hash & oldCap 是否等于0就取决于e.hash第5 位是0还是1,这就相当于有50%的概率放在新hash表低位,50%的概率放在新hash表高位。
- HashMap中key和value都允许为null,key为null的键值对永远都放在以table[0]为头结点的链表中。
存储结构图解
HashTable简介
-Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
-
Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。
-
Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
HashTable和HashMap区别
- 继承的父类不同
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map,Cloneable(可复制)、Serializable(可序列化)这三个接口。
-
线程安全性不同
javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。 -
是否提供contains方法
- hashmap把hashtable的contains方法去掉了,改成了cotainsValue和containskey,因为contains容易让人误解。
- hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
- key和value是否允许null值
其中key和values都是对象,并且不能包含重复可以。
- hashmap可以有一个null的键值对(null,null),只能一个,所以,当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
- ==hashtable不允许出现null值,==但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
-
内部遍历方式不同
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 -
hash值不同
Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。
Hashtable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高,而计算位运算为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。当然了,为了高效,HashMap只做了一些简单的位处理。从而不至于把使用2 的幂次方带来的效率提升给抵消掉。
-
初始容量和大小不同
Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说Hashtable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。
hashmap和hashset的区别
HashMap HashSet
1.HashMap实现了Map接口 HashSet实现了Set接口
2.HashMap储存键值对 HashSet仅仅存储对象
3.使用put()方法将元素 使用add()方法将元素放入set中放入map中
4.HashMap中使用键对象来计算 HashSet使用成员对象来计算hashcode值,对于hashcode值
5.HashMap比较快,因为它使用 HashSet较HashMap来说比较慢
1、继承不同。public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
2、Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
3、Hashtable中,key和value都不允许出现null值。
在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
4、两个遍历方式的内部实现上不同。
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
5、哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
6、Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数