首页 > 代码库 > 几个集合类的比较

几个集合类的比较

1.Hashtable和HashMap

不同点总结如下

① Hashtable是Dictionary的子类,实现了Map接口;HashMap是AbstractMap的子类,是Map接口的一个实现类;


② Hashtable中的方法是同步的,大多数方法如put, get都用用synchronized关键字修饰。而HashMap是线程不安全的。在多线程程序中,可以不添加额外操作就可以安全的使用Hashtable,而对HashMap,则需要额外的同步机制,可以使用Collections的一个静态方法解决:

Map Collections.synchronizedMap(Map m)
这个方法返回一个支持同步(线程安全)的Map对象,实质上是用synchronized封装了HashMap的所有方法。


③在Hashtable中,key和value均不允许为null;而HashMap中,null可以作为key,也可以作为value,null作为key的时候,这样的键只能有一个,但是可以有一个或多个键所对应的值为null,当调用HashMap的get方法的时候如果返回null,可以表示HashMap中没有该键,也可以表示该键所对应的值为null,因此,在HashMap中不能由get方法来判断是否存在某个键,而应该用containsKey方法来判断。


④Hashtable中hash数组默认大小是11,增加方式是old*2+1;HashMap中的hash数组默认大小是16,新的容量是原来的2倍;


⑤哈希值的使用不同,Hashtable使用hashSeed与key.hashCode的异或运算的方法:

int hash = hashSeed ^ key.hashCode()
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap并不是直接将对象的hashCode作为哈希值的,而是要把key的hashCode作一些运算以得到最终的哈希值,并且得到的哈希值再和数组长度按位与运算才得到在数组中的位置。

内部实现

在Java中,Hashtable和HashMap的内部结构都是采用Entry数组来实现,一个Entry类包含四个成员变量:hash、key、value、next。


从上图中我们可以看到,哈希表是有数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点,这些元素通过hash(key)%len,也就是key的哈希值对数组长度取模得到应该放入的数组位置下标。例如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12,所以12、28、108、140都存储在数组下标为12的位置。

总结下HashMap新增put和获取get操作:

//存储时:
int hash = key.hashCode();
int i = hash % Entry[].length;
Entry[i] = value;

//取值时:
int hash = key.hashCode();
int i = hash % Entry[].length;
return Entry[i];


注意:

如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值。HashMap的初始默认容量是16,默认加载因子是0.75,也就是说,如果采用HashMap的默认构造函数,当增加数据时,数据实际容量超过16*0.75=12时,HashMap就扩容,扩容带来一系列的运算,新建一个是原来容量2倍的数组,对原有元素全部重新哈希,如果你的数据有几千几万个,而用默认的HashMap构造函数,那结果是非常悲剧的,因为HashMap不断扩容,不断哈希,


2.Hashtalbe与ConcurrentHashMap区别

相同点:Hashtable和ConcurrentHashMap都是线程安全的,可以在多线程环境中运行;key和value都不能为null

区别:两者主要是性能上的差异,Hashtable的所有操作都会锁住整个对象,虽然能够保证线程安全,但是性能较差;ConcurrentHashMap内部使用Segment数组,其实和Entry类似,它不是加synchronized关键字来保证同步,而是基于lock操作(例如put()方法中调用了ReentrantLock类的lock()方法),这样能避免锁住整个对象。事实上ConcurrentHashMap可以满足concurrentLevel个线程并发无阻塞的操作集合对象。

ConcurrentHashMap基于concurrentLevel划分出了多个Segment来对key-value进行存储,从而避免每次锁定整个数组,在默认的情况下,允许16个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。


下面引用一段话来说明ConcurrentHashMap和Hashtable:

Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处 是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分 数据。此外,使用默认构造器创建的ConcurrentHashMap比较占内存,如果程序需要创建巨量ConcurrentHashMap,应该在构造时指定concurrencyLevel 


更详细参考资料:

http://pi88dian88.iteye.com/blog/2008160

http://blog.csdn.net/zhangerqing/article/details/8193118


几个集合类的比较