-- 作者:菜籽
-- 发布时间:10/27/2008 8:51:00 PM
-- Hashmap与Hashtable的选择使用
1.问题 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) { throw new NullPointerException(); } // 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) { if ((e.hash == hash) && e.key.equals(key)) { Object old = e.value; e.value = value; return old; } } } 从这段代码可以看出,在调用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) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { if (e.hash == hash && eq(k, e.key)) { Object oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, k, value, i); return null; } 在put方法的开始,没有像Hashtable那样对value为空进行判断并抛出异常。另外Object k = maskNull(key); 这句代码中的maskNull方法也对put的key进行如下处理。 static final Object NULL_KEY = new Object(); /** * Returns internal representation for key. Use NULL_KEY if key is null. */ static Object maskNull(Object key) { return (key == null ? NULL_KEY : 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) { Map map = new HashMap(); map.put("1", null); System.out.println(map.get("1")); System.out.println(map.get("2")); System.out.println(map.containsKey("1")); System.out.println(map.containsKey("2")); } 输出结果 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。
|