HashMap与Hashtable区别及HashMap实现原理

    Map是编码过程中经常使用到的容器,而HashMap和Hashtable都实现了Map的,所以我们往往会把两者进行对比。

HashMap和Hashtable区别

  1. Hashtable是线程安全的,HashMap是非线程安全的。Hashtable是基于老的Diactionary类实现的,HashMap是Java 1.2引进Map接口后的重新实现。Hashtable的方法,进行了锁同步,可以支行于多线程环境。HashMap需要编程人员自在己为其提供同步,才能运行多线程中。常用的方法是:利用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象或者是使用ConcurrentHashMap。
  2. 由于HashMap非是线程安全的,所以性能要明显优于Hashtable。
    如果多线程情况下呢?Hashtable的实现与Collections的静态方法synchronizedMap实现有点类似,使用synchronized来保证线程安全。所以如果在线程竞争激烈的情况下,效率就会非常低下,而且线程的安全性还紧限于get、put之类的简单操作。这时ConcurrentHashMap可能是最好的选择。
  3. 对空值处理:HashMap中允许有一个空的key,和任意个空的value,对Hashtable put空的key、value则会报错。如果判断HashMap是否存在某个key值,最准确的是使用containKey,而不是使用对get的值判空,因为你put的也许是个空值。
    
    HashMap为什么会拥有较高的性能,如果你要定义一个用于类似存取的容器你会如何实现呢?或许你会这样实现:
    定义一个长键值对数组,用于存取对象,存值时,直接将第size位设置为需要保存的对象,取值时,遍历这个数组,将key等于你要值的键值对的value取出就可以了。好像挺简单的,也许你已经发现问题了。每次取值都需要遍历整个数组,性能必然不高;数组的长度需要定义成多大,如果put的值数量超过了map的总长度,怎么办?

HashMap的实现原理
    HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap预定义了一个很长的数组,每个数组元素相当于一个取放存取对象的桶。这有点类似基数排序中用到的技巧,利用数组的存取性能,达到空间换取时间的目的。但如果仅仅是这样,就需要定义一个足够长的数组,而且还要找到key与数组下标的一种映射关系。
    关于定义一个足够长的数组这好解决,可以参考ArrayList的实现,先设定一个长度默认值,当存放的值个数多了时候,再对数组进行扩容。为了在时间和空间上都能找到一个比较令人满意的处理方式,通过散列码来查询函数下标。这又带来一个问题,不同的对象的散列码可能值是一样的(不同对象可能equal,但hashcode可能一样,反之不成立)。为了解决这种碰撞问题,我们不得不让每个桶维系一个链表,用于存放key值碰撞的对象。我们来看看HashMap的源代码实现:
transient Entry[] table;//存储元素的实体数组
 
transient int size;//存放元素的个数
 
int threshold; //临界值

final float loadFactor; //加载因子
 
transient int modCount;//被修改的次数

当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量。加载因子越大,填满的元素越多,好处是,空间利用率高了,冲突的机会加大了.链表长度会越来越长,查找效率降低。反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)。冲突的机会越大,则查找的成本越高.因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷。如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。
    每个Entry对象都记录Entry链表的下一个结点。当查找的key值出现hash碰撞,我们就需要遍历链表了。
    我们再来看看,hash码是如何映射数组下标的:
static int hash(int h) {
	// This function ensures that hashCodes that differ only by
	// constant multiples at each bit position have a bounded
	// number of collisions (approximately 8 at default load factor).
	h ^= (h >>> 20) ^ (h >>> 12);
	return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length) {
	return h & (length-1);
}

    哈希表的容量一定要是2的整数次幂,这样HashMap中则通过h&(length-1)的方法来代替取模,就实现了均匀的散列。同时在数组进行扩容时,扩容前的元素的下标位置是不变的,避免了数组元素的重排。但是,我们仍然要避免数组的扩容,因为它依然很大的降低的了map的put性能,这也是HashMap定义加载因子的原因所在。
    那我们该如果初始化加载因子的值呢?这和Map值个数有关系,和Map的每个key值有关系。为了达到这种平衡,我们只能通过真实数据,场景进行,测试来获得这个最佳的加载因子。如果不确定就用默认值好了,没有更好的方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值