Hashmap与Hashtable的选择
(2009-12-13 17:05:08)Hashtable和Hashmap是我们在开发过程中经常用来映射key到value的容器,在这两者之间选择使用的时候,我们经常被有经验者建议用 Hashmap,但我们可能对其中的缘由不甚了解。本文通过对原代码的一些简单分析,来解释原理,从而在使用中能够更好地做出选择。
2.分析
Hashtable和Hashmap实现的功能基本相同,但主要有3点区别。
2.1
第一个不同之处在于它们的继承关系有所不同。
public class Hashtable extends Dictionary
public class HashMap extends AbstractMap
由上面的代码可以看出Hashtable是基于陈旧的Dictionary类的。在Java 1.2引入Map借口后,Hashtable也改进为可以实现 Map。HashMap是Map接口的一个实现,继承于较新的AbstractMap类。 Hashmap可以算作是Hashtable的升级版本,整体上Hashmap对Hashtable类优化了代码。比如说,消除了hardcoding,增加了code reuse等等。当然这点不同,并不会对我们选择使用哪个产生影响。
2.2
第二个不同,在Hashmap中,null可以作为key,这样的key只有一个,可以有一个或多个key所对应的value为null。而在 Hashtable中,null不可以作为key,也不可以作为value。否则会抛出java.lang.NullPointerException。
Hashtable的put方法的源代码如下:
public synchronized Object put(Object key, Object value) {
// Make sure the value is not null
if (value == null) {
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
}
}
从这段代码可以看出,在调用Hashtable的put方法时,首先会对put的value是否为空进行判断,如果为空,则会抛出NullPointerException,处理终止。如果对Hashtable中put一个null的key,代码在执行到
int hash = key.hashCode(); 的时候,因为key对象为空,同样会抛出NullPointerException,处理终止。
所以在编码时,诸如
hashtable.put("1", null);//value为null
或者
hashtable.put(null, "one");//key为null
都是错误的。
当然,平时在编码时,put的key或者value不会那么明显就是个直接的字符串或者其他对象类型。它们可能是从DB或DTO中取得的或者通过一些计算或转化得到的。这个时候要注意put的key或value是否有可能为null,如果有null的可能性,应该先进行判断并处理。以避免异常的产生。
看看下面这段错误代码。TABLEWORK是个hashtable
WorkData wd = null;
wd = rs.getString("HINBAN_NO");// DBから品番を取得する
TABLEWORK.put(tableName, wd); // テーブルにセットする
wd对象是从数据库取得的,有可能会为空。所以在向Hashtable中put的时候,要对wd对象是否为空进行判断并对wd对象为空的情况进行处理。
修改后的代码如下。
WorkData wd = null;
wd = rs.getString("HINBAN_NO");// DBから品番を取得する
if (wd == null) {
// 存在しなければ、新規作成
wd = ComUtils.crtHinaban();
}
// テーブルにセットする
TABLEWORK.put(tableName, wd);
接下来看看Hashmap的put方法是如何对null处理的。
public Object put(Object key, Object value) {
}
在put方法的开始,没有像Hashtable那样对value为空进行判断并抛出异常。另外Object k = maskNull(key); 这句代码中的maskNull方法也对put的key进行如下处理。
static final Object NULL_KEY = new Object();
static Object maskNull(Object key) {
}
由此可以看出,在Hashmap put的时候,maskNull()方法会对put的值是否为空进行判断,如果为空,会产生一个新的对象(Object NULL_KEY = new Object();)即NULL_KEY。
另外还要注意一个问题,因为Hashmap可以存入null。所以当get()方法返回null值时,既可以表示 Hashmap中没有该key,也可以表示该key所对应的value为null。因此,在Hashmap中不能由get()方法来判断Hashmap中是否存在某个key, 而应该用containsKey()方法来判断。
看下面这段代码
public static void main(String[] args) {
}
输出结果
null
null
true
false
从上面这段试验代码的输出结果可以看出,如果通过get方法是无法判断key值1,2是否存在的,因为它们的返回的值都是null。正确的做法应该使用containsKey()方法来判断,从输出结果看,前者为true,后者为false。
2.3
第三个不同是Hashtable的方法是同步的,而Hashmap方法不是。Hashtable是synchronized,你可以不用采取任何特殊的行为就可以在一个多线程的 应用程序中用一个Hashtable,而Hashmap的读写是unsynchronized, 在多线程的环境中要注意使用。这两者的不同是通过在读写方法上加synchronized关键字来实现的.有兴趣的话,大家可以看下Hashtable的源代码,在需要线程安全的方法前都加上了synchronized关键字。下面举了最常用的几个方法的定义。
hashtable
public synchronized Object get(Object key)
public synchronized Object put(Object key, Object value)
public synchronized Object remove(Object key)
public synchronized void clear()
从Hashmap的源代码可以看出没有这个关键字
hashMap
public V put(K key, V value)
public V get(Object key)
有人可能会问, 既然能synchronized,能线程安全好啊。为什么不要呢,这里其实还是一个效率的问题。对于线程安全的方法,系统要进行加锁,减锁操作。性能会有很大的影响。由于很多程序是在单线程或者说是线程安全的情况下工作的, 所以用synchronized就显得有些多余了。
当既要同步又要可以让null作为键或者值的时候,一个简便的方法就是利用Collections类的静态的 synchronizedMap()方法,
Map synMap = Collections.synchronizedMap(map);
它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。
3. 总结
通过上述的内容,我们了解了Hashmap和Hashtable的几个主要的不同点。
Hashmap可以使用null作为key和value,而Hashtable不行。Hashtable是同步的,Hashmap是异步的。但是,因为在需要时,Hashmap可以利用Collections类的静态的 synchronizedMap()方法来实现同步,其次Hashmap的功能比Hashtable的功能更多,而且它不是基于一个陈旧的类的,所以才有人认为,在各种情况下,Hashmap都优先于Hashtable。