HashMap,HashTable,HashSet,ConcurrentHashMap的分析比较

1. HashMap

1.1简介

HashMap是非同步的,并且允许null,即null value和null key。优点是单线程时效率高。

HashMap中元素存取的时间复杂度均为常数。

当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。


1.2数据结构

它采用了一个长度为2的n次幂(原因见1.3节)的数组,数组中每个元素可存放一个Entry列表的头结点。

数组的声明如下: 
transient Entry[] table;  

Entry声明如下: 

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
        .........
}

HashMap会根据initialCapacity和loadFactor来构造,如果initialCapacity不为2的n次幂,HashMap会取其大于 initialCapacity 的最小的 2 的 n 次方值为capacity,原因见1.3节。

// 以指定初始化容量、负载因子创建 HashMap   
public HashMap(int initialCapacity, float loadFactor)   
{   
     .........

     // 计算出大于 initialCapacity 的最小的 2 的 n 次方值。  
     int capacity = 1;   
     while (capacity < initialCapacity)   
         capacity <<= 1;            
          
     .........
}   


1.3 hash过程

java中每个对象都有一个hashCode,hashCode是一个int值。HashMap中的hash值便是根据这个hashCode计算产生。

如果两个对象内容相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同。我们一般在覆盖equals的同时也要覆盖hashcode,让他们的逻辑一致。如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。  

如果两个对象的hashCode相同,它们并不一定是同一个对象。所以在判断Entry是否相等时,除了判断e.hash == hash,还要判断((k = e.key) == key || key.equals(k))。为不相等的对象生成不同的hashCode可以提高哈希表的性能。

HashMap使用hash方法获取key的hash值:

int hash = hash(key.hashCode());  

其中hash方法对hashCode的高位和低位做了位运算,为的是将hashcode的高位和低位均考虑进去。

static int hash(int h) 
{ 
    h ^= (h >>> 20) ^ (h >>> 12); 
    return h ^ (h >>> 7) ^ (h >>> 4); 
} 
之后可用indexFor方法算出对应的数组下标。indexFor方法采用了位运算计算下标,比取余效率更高。

static int indexFor(int h, int length)   
{   
    return h & (length-1);   
}  
但这也需要数组长度length为2的n次幂,这样length-1的所有为均为1,。如length=4时,length-1用二进制表示为11,h & (length-1)的取值范围为二进制中00~11范围中的4个数。否则,如length=9时,length-1用二进制表示为1000,h & (length-1)的取值范围仅为二进制中的0000和1000两个数。

在此之后便可根据需要将Entry存到相应数组下标下对应的列表中,或遍历该列表根据key找到某个需要的Entry。

2. HashTable

Hashtable与HashMap类似,不同之处在于:无论是key还是value都不能为null ,并且是线程安全的。


3. HashSet

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。   
Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。  请注意:必须小心操作可变对象(Mutable   Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

hashset底层采用hashmap实现,使用 HashMap 的 key 保存 HashSet 中所有元素,定义一个虚拟的 Object 对象作为 HashMap 的 value,其中添加元素,查找元素,删除元素的时间复杂度都是O(1)。


4. ConcurrentHashMap

ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。


Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;
而ConcurrentHashMap中则是一次锁住一个桶,桶的数量由concurrencyLevel确定。
ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。
这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

ConcurrentHashMap的写线程需要用到锁,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。并且也比较耗内存。

Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值